This commit is contained in:
parent
e43f4bd0f0
commit
040488d85f
@ -10,7 +10,7 @@ $form-label-fontsize: 1.3rem;
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
|
||||
--header-height: 60px;
|
||||
--header-height: 45px;
|
||||
|
||||
--page: white;
|
||||
--header-grad-left: #363b40;
|
||||
|
@ -2,15 +2,6 @@
|
||||
|
||||
@use "../../globals.scss";
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: calc(100vh - var(--header-height));
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a {
|
||||
color:rgb(255, 255, 255);
|
||||
|
||||
@ -22,3 +13,63 @@ a {
|
||||
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;
|
||||
}
|
@ -1,12 +1,87 @@
|
||||
@use "../../../globals.scss";
|
||||
|
||||
.submissionCard {
|
||||
@include globals.border-with-radius;
|
||||
|
||||
display: flex;
|
||||
background-color: #2020207c;
|
||||
border: 1px solid #501717;
|
||||
border-radius: 6px;
|
||||
padding: 6px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
border: 1px solid #97979783;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
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;
|
||||
}
|
@ -9,7 +9,7 @@ interface CommentersProps {
|
||||
}
|
||||
|
||||
interface CreatorAndReviewStatus {
|
||||
asset_id: SubmissionInfo["AssetID"],
|
||||
asset_id: SubmissionInfo["AssetID"],
|
||||
creator: SubmissionInfo["DisplayName"],
|
||||
review: SubmissionInfo["StatusID"],
|
||||
comments: Comment[],
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Button, ButtonOwnProps } from "@mui/material";
|
||||
import { SubmissionInfo } from "@/app/ts/Submission";
|
||||
|
||||
type Actions = "Completed" | "Submit" | "Reject" | "Revoke"
|
||||
type Review = Actions | "Accept" | "Validate" | "Upload"
|
||||
@ -12,6 +11,10 @@ interface ReviewButton {
|
||||
color: ButtonOwnProps["color"]
|
||||
}
|
||||
|
||||
interface ReviewId {
|
||||
submissionId: string
|
||||
}
|
||||
|
||||
function ReviewButtonClicked(action: Action, submissionId: string) {
|
||||
fetch(`/api/submissions/${submissionId}/status/${action}`, {
|
||||
method: "POST",
|
||||
@ -28,8 +31,8 @@ function ReviewButton(props: ReviewButton) {
|
||||
onClick={() => { ReviewButtonClicked(props.action, props.submissionId) }}>{props.name}</Button>
|
||||
}
|
||||
|
||||
export default function ReviewButtons(props: SubmissionInfo) {
|
||||
const submissionId = props.ID.toString()
|
||||
export default function ReviewButtons(props: ReviewId) {
|
||||
const submissionId = props.submissionId
|
||||
// When is each button visible?
|
||||
// Multiple buttons can be visible at once.
|
||||
// Action | Role | When Current Status is One of:
|
||||
|
@ -14,6 +14,10 @@ import { useState, useEffect } from "react";
|
||||
|
||||
import "./(styles)/page.scss";
|
||||
|
||||
interface ReviewId {
|
||||
submissionId: string
|
||||
}
|
||||
|
||||
function Ratings() {
|
||||
return (
|
||||
<Window className="rating-window" title="Rating">
|
||||
@ -35,15 +39,14 @@ function Ratings() {
|
||||
)
|
||||
}
|
||||
|
||||
function RatingArea(submission: SubmissionInfo) {
|
||||
function RatingArea(submission: ReviewId) {
|
||||
return (
|
||||
<aside className="review-area">
|
||||
<section className="map-image-area">
|
||||
<MapImage/>
|
||||
</section>
|
||||
<Ratings/>
|
||||
{/* TODO: NOT DO!!! */} {ReviewButtons(submission)}
|
||||
{/* <ReviewButtons submissionId={submission.ID}/> */}
|
||||
<ReviewButtons submissionId={submission.submissionId}/>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
@ -91,7 +94,7 @@ export default function SubmissionInfoPage() {
|
||||
<Webpage>
|
||||
<main className="map-page-main">
|
||||
<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={[]}/>
|
||||
</section>
|
||||
</main>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Rating } from "@mui/material";
|
||||
|
||||
interface SubmissionCardProps {
|
||||
displayName: string;
|
||||
@ -14,11 +15,26 @@ export default function SubmissionCard(props: SubmissionCardProps) {
|
||||
return (
|
||||
<Link href={`/submissions/${props.id}`}>
|
||||
<div className="submissionCard">
|
||||
{/* TODO: Grab image of model */}
|
||||
<Image height={200} width={200} priority={true} src="https://api.ic3.space/strafe/map-images/11222350808" style={{ width: `100%` }} alt={props.displayName} />
|
||||
<h3>{props.displayName}</h3>
|
||||
<p>By {props.author}</p>
|
||||
<p>⭐ {props.rating}</p> {/* TODO: paste the star element from submission/1 page */}
|
||||
<div className="content">
|
||||
<div className="map-image">
|
||||
{/* TODO: Grab image of model */}
|
||||
<Image height={200} width={200} priority={true} src="https://api.ic3.space/strafe/map-images/11222350808" style={{ width: `100%` }} alt={props.displayName} />
|
||||
</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>
|
||||
</Link>
|
||||
);
|
||||
|
@ -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;
|
@ -1,33 +1,53 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { SubmissionInfo } from '../ts/Submission';
|
||||
import { Grid2 as Grid } from '@mui/material';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { SubmissionInfo } from "../ts/Submission";
|
||||
import SubmissionCard from "./_card";
|
||||
import SkeletonGrid from './_loading';
|
||||
import Webpage from "@/app/_components/webpage";
|
||||
|
||||
import "./(styles)/page.scss";
|
||||
|
||||
export default function SubmissionInfoPage() {
|
||||
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() {
|
||||
const res = await fetch('/api/submissions?Page=1&Limit=100')
|
||||
if (res.ok) {
|
||||
setSubmissions(await res.json())
|
||||
}
|
||||
}
|
||||
setTimeout(() => { // testing loading screen made by chatGerbertPT
|
||||
|
||||
setTimeout(() => {
|
||||
fetchSubmissions()
|
||||
}, 250);
|
||||
}, 50);
|
||||
}, [])
|
||||
|
||||
if (!submissions) {
|
||||
if (!submissions || submissions.length == 0) {
|
||||
return <Webpage>
|
||||
<main style={{ display: 'flex', justifyContent: 'center', padding: '1rem' }}>
|
||||
<SkeletonGrid />
|
||||
<main>
|
||||
Loading...
|
||||
</main>
|
||||
</Webpage>
|
||||
}
|
||||
@ -35,43 +55,48 @@ export default function SubmissionInfoPage() {
|
||||
return (
|
||||
// TODO: Add filter settings & searchbar & page selector
|
||||
<Webpage>
|
||||
<main style={{ display: 'flex', justifyContent: 'center', padding: '1rem' }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
style={{ maxWidth: 'calc(100vw - 100px)', margin: '0 auto' }}
|
||||
>
|
||||
{submissions.map((submission) => (
|
||||
<Grid key={submission.ID}>
|
||||
<SubmissionCard
|
||||
id={submission.ID}
|
||||
assetId={submission.AssetID}
|
||||
displayName={submission.DisplayName}
|
||||
author={submission.Creator}
|
||||
rating={submission.StatusID}
|
||||
/>
|
||||
</Grid>
|
||||
<main
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: '1rem',
|
||||
width: '100%',
|
||||
maxWidth: '100vw',
|
||||
boxSizing: 'border-box',
|
||||
overflowX: 'hidden'
|
||||
}}
|
||||
>
|
||||
<div className="pagination-dots">
|
||||
{Array.from({ length: totalPages }).map((_, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={`dot ${index === currentPage ? 'active' : ''}`}
|
||||
onClick={() => setCurrentPage(index)}
|
||||
></span>
|
||||
))}
|
||||
</Grid>
|
||||
</div>
|
||||
<div className="pagination">
|
||||
<button onClick={prevPage} disabled={currentPage === 0}><</button>
|
||||
<span>
|
||||
Page {currentPage + 1} of {totalPages}
|
||||
</span>
|
||||
<button onClick={nextPage} disabled={currentPage === totalPages - 1}>></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>
|
||||
</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
|
||||
// }
|
||||
}
|
@ -22,7 +22,6 @@ const BootstrapInput = styled(InputBase)(({ theme }) => ({
|
||||
fontSize: 16,
|
||||
padding: '10px 26px 10px 12px',
|
||||
transition: theme.transitions.create(['border-color', 'box-shadow']),
|
||||
// Use the system font instead of the default Roboto font.
|
||||
fontFamily: [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
|
Loading…
x
Reference in New Issue
Block a user