Web: Add Mapfixes Pages #60
web/src/app
mapfixes
(styles)
[mapfixId]
_card.tsx_window.tsxpage.tsxmaps/[mapId]/fix
submit
ts
75
web/src/app/mapfixes/(styles)/page.scss
Normal file
75
web/src/app/mapfixes/(styles)/page.scss
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
@forward "./page/card.scss";
|
||||||
|
|
||||||
|
@use "../../globals.scss";
|
||||||
|
|
||||||
|
a {
|
||||||
|
color:rgb(255, 255, 255);
|
||||||
|
|
||||||
|
&:visited, &:hover, &:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
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;
|
||||||
|
}
|
87
web/src/app/mapfixes/(styles)/page/card.scss
Normal file
87
web/src/app/mapfixes/(styles)/page/card.scss
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
@use "../../../globals.scss";
|
||||||
|
|
||||||
|
.submissionCard {
|
||||||
|
display: flex;
|
||||||
|
background-color: #2020207c;
|
||||||
|
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;
|
||||||
|
}
|
19
web/src/app/mapfixes/[mapfixId]/(styles)/page.scss
Normal file
19
web/src/app/mapfixes/[mapfixId]/(styles)/page.scss
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
@forward "./page/commentWindow.scss";
|
||||||
|
@forward "./page/reviewStatus.scss";
|
||||||
|
@forward "./page/ratingWindow.scss";
|
||||||
|
@forward "./page/reviewButtons.scss";
|
||||||
|
@forward "./page/comments.scss";
|
||||||
|
@forward "./page/review.scss";
|
||||||
|
@forward "./page/map.scss";
|
||||||
|
|
||||||
|
@use "../../../globals.scss";
|
||||||
|
|
||||||
|
.map-page-main {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.by-creator {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
@use "../../../../globals.scss";
|
||||||
|
|
||||||
|
#comment-text-field {
|
||||||
|
@include globals.border-with-radius;
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
background-color: var(--comment-area)
|
||||||
|
}
|
||||||
|
|
||||||
|
.leave-comment-window {
|
||||||
|
@include globals.border-with-radius;
|
||||||
|
width: 100%;
|
||||||
|
height: 230px;
|
||||||
|
margin-top: 35px;
|
||||||
|
|
||||||
|
.rating-type {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: 35%;
|
||||||
|
|
||||||
|
.rating-right {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
margin: 6px 0 6px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 15px 0 15px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--window-header);
|
||||||
|
border-bottom: globals.$review-border;
|
||||||
|
height: 45px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 0 0 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
web/src/app/mapfixes/[mapfixId]/(styles)/page/comments.scss
Normal file
49
web/src/app/mapfixes/[mapfixId]/(styles)/page/comments.scss
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
$comments-size: 60px;
|
||||||
|
|
||||||
|
.comments {
|
||||||
|
display: grid;
|
||||||
|
gap: 25px;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.no-comments {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commenter {
|
||||||
|
display: flex;
|
||||||
|
height: $comments-size;
|
||||||
|
|
||||||
|
//BhopMaptest comment
|
||||||
|
&[data-highlighted="true"] {
|
||||||
|
background-color: var(--comment-highlighted);
|
||||||
|
}
|
||||||
|
> img {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font: {
|
||||||
|
weight: 500;
|
||||||
|
size: 1.3em;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
.date {
|
||||||
|
font-size: .8em;
|
||||||
|
margin: 0 0 0 5px;
|
||||||
|
color: #646464
|
||||||
|
}
|
||||||
|
.details {
|
||||||
|
display: grid;
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
p:not(.date) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
web/src/app/mapfixes/[mapfixId]/(styles)/page/map.scss
Normal file
15
web/src/app/mapfixes/[mapfixId]/(styles)/page/map.scss
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
@use "../../../../globals.scss";
|
||||||
|
|
||||||
|
.map-image-area {
|
||||||
|
@include globals.border-with-radius;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 350px;
|
||||||
|
height: 350px;
|
||||||
|
|
||||||
|
> p {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
@use "../../../../globals.scss";
|
||||||
|
|
||||||
|
.rating-window {
|
||||||
|
@include globals.border-with-radius;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.rating-type {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: 35%;
|
||||||
|
|
||||||
|
.rating-right {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
margin: 6px 0 6px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 15px 0 15px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--window-header);
|
||||||
|
border-bottom: globals.$review-border;
|
||||||
|
height: 45px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 0 0 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
}
|
47
web/src/app/mapfixes/[mapfixId]/(styles)/page/review.scss
Normal file
47
web/src/app/mapfixes/[mapfixId]/(styles)/page/review.scss
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
@use "../../../../globals.scss";
|
||||||
|
|
||||||
|
.review-info {
|
||||||
|
width: 650px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
p, h1 {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font: {
|
||||||
|
weight: 500;
|
||||||
|
size: 1.8rem
|
||||||
|
};
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--anchor-link-review);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-section {
|
||||||
|
display: flex;
|
||||||
|
gap: 50px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-area {
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 25px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 350px;
|
||||||
|
object-fit: contain
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
@use "../../../../globals.scss";
|
||||||
|
|
||||||
|
.review-set {
|
||||||
|
@include globals.border-with-radius;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
$UnderConstruction: "0";
|
||||||
|
$Submitted: "1";
|
||||||
|
$ChangesRequested: "2";
|
||||||
|
$Accepted: "3";
|
||||||
|
$Validating: "4";
|
||||||
|
$Validated: "5";
|
||||||
|
$Uploading: "6";
|
||||||
|
$Uploaded: "7";
|
||||||
|
$Rejected: "8";
|
||||||
|
$Released: "9";
|
||||||
|
|
||||||
|
.review-status {
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 3px 25px 3px 25px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-review-status="#{$Released}"] {
|
||||||
|
background-color: orange;
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[data-review-status="#{$Rejected}"] {
|
||||||
|
background-color: orange;
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[data-review-status="#{$Uploading}"] {
|
||||||
|
background-color: orange;
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[data-review-status="#{$Uploaded}"] {
|
||||||
|
background-color: orange;
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[data-review-status="#{$Validated}"] {
|
||||||
|
background-color: orange;
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[data-review-status="#{$Validating}"] {
|
||||||
|
background-color: orange;
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[data-review-status="#{$Accepted}"] {
|
||||||
|
background-color: rgb(2, 162, 2);
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[data-review-status="#{$ChangesRequested}"] {
|
||||||
|
background-color: orange;
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[data-review-status="#{$Submitted}"] {
|
||||||
|
background-color: orange;
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[data-review-status="#{$UnderConstruction}"] {
|
||||||
|
background-color: orange;
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
web/src/app/mapfixes/[mapfixId]/_comments.tsx
Normal file
68
web/src/app/mapfixes/[mapfixId]/_comments.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import type { MapfixInfo } from "@/app/ts/Mapfix";
|
||||||
|
import { Button } from "@mui/material"
|
||||||
|
import Window from "./_window";
|
||||||
|
import SendIcon from '@mui/icons-material/Send';
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
interface CommentersProps {
|
||||||
|
comments_data: CreatorAndReviewStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreatorAndReviewStatus {
|
||||||
|
asset_id: MapfixInfo["AssetID"],
|
||||||
|
creator: MapfixInfo["DisplayName"],
|
||||||
|
review: MapfixInfo["StatusID"],
|
||||||
|
status_message: MapfixInfo["StatusMessage"],
|
||||||
|
comments: Comment[],
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Comment {
|
||||||
|
picture?: string, //TEMP
|
||||||
|
comment: string,
|
||||||
|
date: string,
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddComment(comment: Comment) {
|
||||||
|
const IsBhopMaptest = comment.name == "BhopMaptest" //Highlighted commenter
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="commenter" data-highlighted={IsBhopMaptest}>
|
||||||
|
<Image src={comment.picture as string} alt={`${comment.name}'s comment`}/>
|
||||||
|
<div className="details">
|
||||||
|
<header>
|
||||||
|
<p className="name">{comment.name}</p>
|
||||||
|
<p className="date">{comment.date}</p>
|
||||||
|
</header>
|
||||||
|
<p className="comment">{comment.comment}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LeaveAComment() {
|
||||||
|
return (
|
||||||
|
<Window title="Leave a Comment:" className="leave-comment-window">
|
||||||
|
<textarea name="comment-box" id="comment-text-field"></textarea>
|
||||||
|
<Button variant="outlined" endIcon={<SendIcon/>}>Submit</Button>
|
||||||
|
</Window>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Comments(stats: CommentersProps) {
|
||||||
|
return (<>
|
||||||
|
<section className="comments">
|
||||||
|
{stats.comments_data.comments.length===0
|
||||||
|
&& <p className="no-comments">There are no comments.</p>
|
||||||
|
|| stats.comments_data.comments.map(comment => (
|
||||||
|
<AddComment key={comment.name} name={comment.name} date={comment.date} comment={comment.comment}/>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
<LeaveAComment/>
|
||||||
|
</>)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
type CreatorAndReviewStatus
|
||||||
|
}
|
14
web/src/app/mapfixes/[mapfixId]/_map.tsx
Normal file
14
web/src/app/mapfixes/[mapfixId]/_map.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { MapfixInfo } from "@/app/ts/Mapfix"
|
||||||
|
|
||||||
|
interface AssetID {
|
||||||
|
id: MapfixInfo["AssetID"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function MapImage() {
|
||||||
|
return <p>Fetching map image...</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
type AssetID,
|
||||||
|
MapImage
|
||||||
|
}
|
74
web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx
Normal file
74
web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Button, ButtonOwnProps } from "@mui/material";
|
||||||
|
|
||||||
|
type Actions = "Completed" | "Submit" | "Reject" | "Revoke"
|
||||||
|
type ApiActions = Lowercase<Actions> | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating"
|
||||||
|
type Review = Actions | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes"
|
||||||
|
|
||||||
|
interface ReviewButton {
|
||||||
|
name: Review,
|
||||||
|
action: ApiActions,
|
||||||
|
mapfixId: string,
|
||||||
|
color: ButtonOwnProps["color"]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReviewId {
|
||||||
|
mapfixId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ReviewButtonClicked(action: ApiActions, mapfixId: string) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/mapfixes/${mapfixId}/status/${action}`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Check if the HTTP request was successful
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorDetails = await response.text();
|
||||||
|
|
||||||
|
// Throw an error with detailed information
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}, details: ${errorDetails}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating mapfix status:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReviewButton(props: ReviewButton) {
|
||||||
|
return <Button
|
||||||
|
color={props.color}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => { ReviewButtonClicked(props.action, props.mapfixId) }}>{props.name}</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReviewButtons(props: ReviewId) {
|
||||||
|
const mapfixId = props.mapfixId
|
||||||
|
// When is each button visible?
|
||||||
|
// Multiple buttons can be visible at once.
|
||||||
|
// Action | Role | When Current Status is One of:
|
||||||
|
// ---------------|-----------|-----------------------
|
||||||
|
// Submit | Submitter | UnderConstruction, ChangesRequested
|
||||||
|
// Revoke | Submitter | Submitted, ChangesRequested
|
||||||
|
// Accept | Reviewer | Submitted
|
||||||
|
// Validate | Reviewer | Accepted
|
||||||
|
// ResetValidating| Reviewer | Validating
|
||||||
|
// Reject | Reviewer | Submitted
|
||||||
|
// RequestChanges | Reviewer | Validated, Accepted, Submitted
|
||||||
|
// Upload | MapAdmin | Validated
|
||||||
|
// ResetUploading | MapAdmin | Uploading
|
||||||
|
return (
|
||||||
|
<section className="review-set">
|
||||||
|
<ReviewButton color="info" name="Submit" action="submit" mapfixId={mapfixId}/>
|
||||||
|
<ReviewButton color="info" name="Revoke" action="revoke" mapfixId={mapfixId}/>
|
||||||
|
<ReviewButton color="info" name="Accept" action="trigger-validate" mapfixId={mapfixId}/>
|
||||||
|
<ReviewButton color="info" name="Validate" action="retry-validate" mapfixId={mapfixId}/>
|
||||||
|
<ReviewButton color="error" name="Reject" action="reject" mapfixId={mapfixId}/>
|
||||||
|
<ReviewButton color="info" name="Upload" action="trigger-upload" mapfixId={mapfixId}/>
|
||||||
|
<ReviewButton color="error" name="Reset Uploading (fix softlocked status)" action="reset-uploading" mapfixId={mapfixId}/>
|
||||||
|
<ReviewButton color="error" name="Reset Validating (fix softlocked status)" action="reset-validating" mapfixId={mapfixId}/>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
20
web/src/app/mapfixes/[mapfixId]/_window.tsx
Normal file
20
web/src/app/mapfixes/[mapfixId]/_window.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
interface WindowStruct {
|
||||||
|
className: string,
|
||||||
|
title: string,
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Window(window: WindowStruct) {
|
||||||
|
return (
|
||||||
|
<section className={window.className}>
|
||||||
|
<header>
|
||||||
|
<p>{window.title}</p>
|
||||||
|
</header>
|
||||||
|
<main>{window.children}</main>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
type WindowStruct
|
||||||
|
}
|
105
web/src/app/mapfixes/[mapfixId]/page.tsx
Normal file
105
web/src/app/mapfixes/[mapfixId]/page.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { MapfixInfo, MapfixStatusToString } from "@/app/ts/Mapfix";
|
||||||
|
import type { CreatorAndReviewStatus } from "./_comments";
|
||||||
|
import { MapImage } from "./_map";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import ReviewButtons from "./_reviewButtons";
|
||||||
|
import { Rating } from "@mui/material";
|
||||||
|
import Comments from "./_comments";
|
||||||
|
import Webpage from "@/app/_components/webpage";
|
||||||
|
import Window from "./_window";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import "./(styles)/page.scss";
|
||||||
|
|
||||||
|
interface ReviewId {
|
||||||
|
mapfixId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ratings() {
|
||||||
|
return (
|
||||||
|
<Window className="rating-window" title="Rating">
|
||||||
|
<section className="rating-type">
|
||||||
|
<aside className="rating-left">
|
||||||
|
<p>Quality</p>
|
||||||
|
<p>Difficulty</p>
|
||||||
|
<p>Fun</p>
|
||||||
|
<p>Length</p>
|
||||||
|
</aside>
|
||||||
|
<aside className="rating-right">
|
||||||
|
<Rating defaultValue={2.5} precision={0.5}/>
|
||||||
|
<Rating defaultValue={2.5} precision={0.5}/>
|
||||||
|
<Rating defaultValue={2.5} precision={0.5}/>
|
||||||
|
<Rating defaultValue={2.5} precision={0.5}/>
|
||||||
|
</aside>
|
||||||
|
</section>
|
||||||
|
</Window>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RatingArea(mapfix: ReviewId) {
|
||||||
|
return (
|
||||||
|
<aside className="review-area">
|
||||||
|
<section className="map-image-area">
|
||||||
|
<MapImage/>
|
||||||
|
</section>
|
||||||
|
<Ratings/>
|
||||||
|
<ReviewButtons mapfixId={mapfix.mapfixId}/>
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TitleAndComments(stats: CreatorAndReviewStatus) {
|
||||||
|
const Review = MapfixStatusToString(stats.review)
|
||||||
|
|
||||||
|
// TODO: hide status message when status is not "Accepted"
|
||||||
|
return (
|
||||||
|
<main className="review-info">
|
||||||
|
<div>
|
||||||
|
<h1>{stats.name}</h1>
|
||||||
|
<aside data-review-status={stats.review} className="review-status">
|
||||||
|
<p>{Review}</p>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
<p className="by-creator">by <Link href="" target="_blank">{stats.creator}</Link></p>
|
||||||
|
<p className="asset-id">Model Asset ID {stats.asset_id}</p>
|
||||||
|
<p className="status-message">Validation Error: {stats.status_message}</p>
|
||||||
|
<span className="spacer"></span>
|
||||||
|
<Comments comments_data={stats}/>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MapfixInfoPage() {
|
||||||
|
const dynamicId = useParams<{mapfixId: string}>()
|
||||||
|
|
||||||
|
const [mapfix, setMapfix] = useState<MapfixInfo | null>(null)
|
||||||
|
|
||||||
|
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)
|
||||||
|
async function getMapfix() {
|
||||||
|
const res = await fetch(`/api/mapfixes/${dynamicId.mapfixId}`)
|
||||||
|
if (res.ok) {
|
||||||
|
setMapfix(await res.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getMapfix()
|
||||||
|
}, [dynamicId.mapfixId])
|
||||||
|
|
||||||
|
if (!mapfix) {
|
||||||
|
return <Webpage>
|
||||||
|
{/* TODO: Add skeleton loading thingy ? Maybe ? (https://mui.com/material-ui/react-skeleton/) */}
|
||||||
|
</Webpage>
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Webpage>
|
||||||
|
<main className="map-page-main">
|
||||||
|
<section className="review-section">
|
||||||
|
<RatingArea mapfixId={dynamicId.mapfixId}/>
|
||||||
|
<TitleAndComments name={mapfix.DisplayName} creator={mapfix.Creator} review={mapfix.StatusID} status_message={mapfix.StatusMessage} asset_id={mapfix.AssetID} comments={[]}/>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</Webpage>
|
||||||
|
)
|
||||||
|
}
|
41
web/src/app/mapfixes/_card.tsx
Normal file
41
web/src/app/mapfixes/_card.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Rating } from "@mui/material";
|
||||||
|
|
||||||
|
interface SubmissionCardProps {
|
||||||
|
displayName: string;
|
||||||
|
assetId: number;
|
||||||
|
rating: number;
|
||||||
|
author: string;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SubmissionCard(props: SubmissionCardProps) {
|
||||||
|
return (
|
||||||
|
<Link href={`/submissions/${props.id}`}>
|
||||||
|
<div className="submissionCard">
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
18
web/src/app/mapfixes/_window.tsx
Normal file
18
web/src/app/mapfixes/_window.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
interface WindowStruct {
|
||||||
|
children: React.ReactNode,
|
||||||
|
className: string,
|
||||||
|
title: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Window(window: WindowStruct) {
|
||||||
|
return <section className={window.className}>
|
||||||
|
<header>
|
||||||
|
<p>{window.title}</p>
|
||||||
|
</header>
|
||||||
|
<main>{window.children}</main>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
type WindowStruct
|
||||||
|
}
|
110
web/src/app/mapfixes/page.tsx
Normal file
110
web/src/app/mapfixes/page.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { MapfixInfo } from "../ts/Mapfix";
|
||||||
|
import MapfixCard from "./_card";
|
||||||
|
import Webpage from "@/app/_components/webpage";
|
||||||
|
|
||||||
|
import "./(styles)/page.scss";
|
||||||
|
|
||||||
|
export default function MapfixInfoPage() {
|
||||||
|
const [mapfixes, setMapfixes] = useState<MapfixInfo[]>([])
|
||||||
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
|
const cardsPerPage = 24; // built to fit on a 1920x1080 monitor
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(mapfixes.length / cardsPerPage);
|
||||||
|
|
||||||
|
const currentCards = mapfixes.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 fetchMapfixes() {
|
||||||
|
const res = await fetch('/api/mapfixes?Page=1&Limit=100')
|
||||||
|
if (res.ok) {
|
||||||
|
setMapfixes(await res.json())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchMapfixes()
|
||||||
|
}, 50);
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!mapfixes) {
|
||||||
|
return <Webpage>
|
||||||
|
<main>
|
||||||
|
Loading...
|
||||||
|
</main>
|
||||||
|
</Webpage>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapfixes && mapfixes.length == 0) {
|
||||||
|
return <Webpage>
|
||||||
|
<main>
|
||||||
|
Mapfixes list is empty.
|
||||||
|
</main>
|
||||||
|
</Webpage>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
// TODO: Add filter settings & searchbar & page selector
|
||||||
|
<Webpage>
|
||||||
|
<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>
|
||||||
|
))}
|
||||||
|
</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((mapfix) => (
|
||||||
|
<MapfixCard
|
||||||
|
key={mapfix.ID}
|
||||||
|
id={mapfix.ID}
|
||||||
|
assetId={mapfix.AssetID}
|
||||||
|
displayName={mapfix.DisplayName}
|
||||||
|
author={mapfix.Creator}
|
||||||
|
rating={mapfix.StatusID}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</Webpage>
|
||||||
|
)
|
||||||
|
}
|
54
web/src/app/maps/[mapId]/fix/(styles)/page.scss
Normal file
54
web/src/app/maps/[mapId]/fix/(styles)/page.scss
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
@use "../../../../globals.scss";
|
||||||
|
|
||||||
|
::placeholder {
|
||||||
|
color: var(--placeholder-text)
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-spacer {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#target-asset-radio {
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: globals.$form-label-fontsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
width: 850px;
|
||||||
|
|
||||||
|
& label, & input {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
& fieldset {
|
||||||
|
border-color: rgb(100,100,100);
|
||||||
|
}
|
||||||
|
& span {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-inline: auto;
|
||||||
|
width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: grid;
|
||||||
|
gap: 25px;
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: blue
|
||||||
|
}
|
||||||
|
}
|
65
web/src/app/maps/[mapId]/fix/_game.tsx
Normal file
65
web/src/app/maps/[mapId]/fix/_game.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { FormControl, Select, InputLabel, MenuItem } from "@mui/material";
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
import InputBase from '@mui/material/InputBase';
|
||||||
|
import React from "react";
|
||||||
|
import { SelectChangeEvent } from "@mui/material";
|
||||||
|
|
||||||
|
// TODO: Properly style everything instead of pasting 🤚
|
||||||
|
|
||||||
|
type GameSelectionProps = {
|
||||||
|
game: number;
|
||||||
|
setGame: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BootstrapInput = styled(InputBase)(({ theme }) => ({
|
||||||
|
'label + &': {
|
||||||
|
marginTop: theme.spacing(3),
|
||||||
|
},
|
||||||
|
'& .MuiInputBase-input': {
|
||||||
|
backgroundColor: '#0000',
|
||||||
|
color: '#FFF',
|
||||||
|
border: '1px solid rgba(175, 175, 175, 0.66)',
|
||||||
|
fontSize: 16,
|
||||||
|
padding: '10px 26px 10px 12px',
|
||||||
|
transition: theme.transitions.create(['border-color', 'box-shadow']),
|
||||||
|
fontFamily: [
|
||||||
|
'-apple-system',
|
||||||
|
'BlinkMacSystemFont',
|
||||||
|
'"Segoe UI"',
|
||||||
|
'Roboto',
|
||||||
|
'"Helvetica Neue"',
|
||||||
|
'Arial',
|
||||||
|
'sans-serif',
|
||||||
|
'"Apple Color Emoji"',
|
||||||
|
'"Segoe UI Emoji"',
|
||||||
|
'"Segoe UI Symbol"',
|
||||||
|
].join(','),
|
||||||
|
'&:focus': {
|
||||||
|
borderRadius: 4,
|
||||||
|
borderColor: '#80bdff',
|
||||||
|
boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function GameSelection({ game, setGame }: GameSelectionProps) {
|
||||||
|
const handleChange = (event: SelectChangeEvent) => {
|
||||||
|
setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl>
|
||||||
|
<InputLabel sx={{ color: "#646464" }}>Game</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={String(game)}
|
||||||
|
label="Game"
|
||||||
|
onChange={handleChange}
|
||||||
|
input={<BootstrapInput />}
|
||||||
|
>
|
||||||
|
<MenuItem value={1}>Bhop</MenuItem>
|
||||||
|
<MenuItem value={2}>Surf</MenuItem>
|
||||||
|
<MenuItem value={3}>Fly Trials</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
98
web/src/app/maps/[mapId]/fix/page.tsx
Normal file
98
web/src/app/maps/[mapId]/fix/page.tsx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Button, TextField } from "@mui/material"
|
||||||
|
|
||||||
|
import GameSelection from "./_game";
|
||||||
|
import SendIcon from '@mui/icons-material/Send';
|
||||||
|
import Webpage from "@/app/_components/webpage";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import "./(styles)/page.scss"
|
||||||
|
|
||||||
|
interface MapfixPayload {
|
||||||
|
DisplayName: string;
|
||||||
|
Creator: string;
|
||||||
|
GameID: number;
|
||||||
|
AssetID: number;
|
||||||
|
AssetVersion: number;
|
||||||
|
TargetAssetID: number;
|
||||||
|
}
|
||||||
|
interface IdResponse {
|
||||||
|
ID: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MapfixInfoPage() {
|
||||||
|
const [game, setGame] = useState(1);
|
||||||
|
const dynamicId = useParams<{ mapId: string }>();
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const form = event.currentTarget;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
const payload: MapfixPayload = {
|
||||||
|
DisplayName: (formData.get("display-name") as string) ?? "unknown", // TEMPORARY! TODO: Change
|
||||||
|
Creator: (formData.get("creator") as string) ?? "unknown", // TEMPORARY! TODO: Change
|
||||||
|
GameID: game,
|
||||||
|
AssetID: Number((formData.get("asset-id") as string) ?? "0"),
|
||||||
|
AssetVersion: 0,
|
||||||
|
TargetAssetID: Number(dynamicId.mapId),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(payload)
|
||||||
|
console.log(JSON.stringify(payload))
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Send the POST request
|
||||||
|
const response = await fetch("/api/mapfixes", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the HTTP request was successful
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorDetails = await response.text();
|
||||||
|
|
||||||
|
// Throw an error with detailed information
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}, details: ${errorDetails}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow any HTTP status
|
||||||
|
const id_response:IdResponse = await response.json();
|
||||||
|
|
||||||
|
// navigate to newly created mapfix
|
||||||
|
window.location.assign(`/mapfixes/${id_response.ID}`)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error submitting data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Webpage>
|
||||||
|
<main>
|
||||||
|
<header>
|
||||||
|
<h1>Submit Mapfix</h1>
|
||||||
|
<span className="spacer form-spacer"></span>
|
||||||
|
</header>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{/* TODO: Add form data for mapfixes, such as changes they did, and any times that need to be deleted & what styles */}
|
||||||
|
<TextField className="form-field" id="display-name" name="display-name" label="Display Name" variant="outlined"/>
|
||||||
|
<TextField className="form-field" id="creator" name="creator" label="Creator" variant="outlined"/>
|
||||||
|
<TextField className="form-field" id="asset-id" name="asset-id" label="Asset ID" variant="outlined"/>
|
||||||
|
{/* I think this is Quat's job to figure this one out (to be set when someone clicks review(?)) */} {/* <TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/> */}
|
||||||
|
<GameSelection game={game} setGame={setGame} />
|
||||||
|
<span className="spacer form-spacer"></span>
|
||||||
|
<Button type="submit" variant="contained" startIcon={<SendIcon/>} sx={{
|
||||||
|
width: "400px",
|
||||||
|
height: "50px",
|
||||||
|
marginInline: "auto"
|
||||||
|
}}>Submit</Button>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</Webpage>
|
||||||
|
)
|
||||||
|
}
|
@ -71,7 +71,7 @@ export default function SubmissionInfoPage() {
|
|||||||
<Webpage>
|
<Webpage>
|
||||||
<main>
|
<main>
|
||||||
<header>
|
<header>
|
||||||
<h1>Submit Asset</h1>
|
<h1>Submit New Map</h1>
|
||||||
<span className="spacer form-spacer"></span>
|
<span className="spacer form-spacer"></span>
|
||||||
</header>
|
</header>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
59
web/src/app/ts/Mapfix.ts
Normal file
59
web/src/app/ts/Mapfix.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
const enum MapfixStatus {
|
||||||
|
UnderConstruction = 0,
|
||||||
|
Submitted = 1,
|
||||||
|
ChangesRequested = 2,
|
||||||
|
Accepted = 3,
|
||||||
|
Validating = 4,
|
||||||
|
Validated = 5,
|
||||||
|
Uploading = 6,
|
||||||
|
Uploaded = 7,
|
||||||
|
Rejected = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MapfixInfo {
|
||||||
|
readonly ID: number,
|
||||||
|
readonly DisplayName: string,
|
||||||
|
readonly Creator: string,
|
||||||
|
readonly GameID: number,
|
||||||
|
readonly Date: number,
|
||||||
|
readonly Submitter: number,
|
||||||
|
readonly AssetID: number,
|
||||||
|
readonly AssetVersion: number,
|
||||||
|
readonly ValidatedAssetID: number,
|
||||||
|
readonly ValidatedAssetVersion: number,
|
||||||
|
readonly Completed: boolean,
|
||||||
|
readonly TargetAssetID: number,
|
||||||
|
readonly StatusID: MapfixStatus
|
||||||
|
readonly StatusMessage: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
function MapfixStatusToString(mapfix_status: MapfixStatus): string {
|
||||||
|
switch (mapfix_status) {
|
||||||
|
case MapfixStatus.Rejected:
|
||||||
|
return "REJECTED"
|
||||||
|
case MapfixStatus.Uploading:
|
||||||
|
return "UPLOADING"
|
||||||
|
case MapfixStatus.Uploaded:
|
||||||
|
return "UPLOADED"
|
||||||
|
case MapfixStatus.Validated:
|
||||||
|
return "VALIDATED"
|
||||||
|
case MapfixStatus.Validating:
|
||||||
|
return "VALIDATING"
|
||||||
|
case MapfixStatus.Accepted:
|
||||||
|
return "ACCEPTED"
|
||||||
|
case MapfixStatus.ChangesRequested:
|
||||||
|
return "CHANGES REQUESTED"
|
||||||
|
case MapfixStatus.Submitted:
|
||||||
|
return "SUBMITTED"
|
||||||
|
case MapfixStatus.UnderConstruction:
|
||||||
|
return "UNDER CONSTRUCTION"
|
||||||
|
default:
|
||||||
|
return "UNKNOWN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
MapfixStatus,
|
||||||
|
MapfixStatusToString,
|
||||||
|
type MapfixInfo
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user