This commit is contained in:
parent
e43f4bd0f0
commit
040488d85f
@ -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;
|
||||||
|
@ -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);
|
||||||
|
|
||||||
@ -22,3 +13,63 @@ a {
|
|||||||
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;
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
@ -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[],
|
||||||
|
@ -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:
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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'
|
'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}><</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>
|
</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
|
|
||||||
// }
|
|
||||||
}
|
}
|
@ -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',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user