Update mod download section #201

Merged
enes merged 3 commits from extra/196-mods-refactor into staging 2025-01-21 16:26:50 +00:00
6 changed files with 253 additions and 141 deletions

View File

@ -0,0 +1,113 @@
import { createPortal } from 'react-dom'
import { DownloadUrl } from '../types'
export const DownloadDetailsPopup = ({
title,
url,
hash,
signatureKey,
malwareScanLink,
modVersion,
customNote,
mediaUrl,
handleClose
}: DownloadUrl & {
handleClose: () => void
}) => {
return createPortal(
<div className='popUpMain'>
<div className='ContainerMain'>
<div className='popUpMainCardWrapper'>
<div className='popUpMainCard'>
<div className='popUpMainCardTop'>
<div className='popUpMainCardTopInfo'>
<h3>{title || 'Authentication Details'}</h3>
</div>
<div className='popUpMainCardTopClose' onClick={handleClose}>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-96 0 512 512'
width='1em'
height='1em'
fill='currentColor'
style={{ zIndex: 1 }}
>
<path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z'></path>
</svg>
</div>
</div>
<div className='pUMCB_Zaps'>
<div className='pUMCB_ZapsInside'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTable'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Download URL</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{url}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>SHA-256 hash</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{hash}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Signature from</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{signatureKey}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Scan</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{malwareScanLink}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Mod Version</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{modVersion}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Note</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{customNote}</p>
</div>
</div>
{typeof mediaUrl !== 'undefined' && mediaUrl !== '' && (
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Media</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<img
src={mediaUrl}
className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol_Img'
alt=''
/>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</div>,
document.body
)
}

View File

@ -457,12 +457,14 @@ export const ModForm = () => {
<Fragment key={`download-${index}`}> <Fragment key={`download-${index}`}>
<DownloadUrlFields <DownloadUrlFields
index={index} index={index}
title={download.title}
url={download.url} url={download.url}
hash={download.hash} hash={download.hash}
signatureKey={download.signatureKey} signatureKey={download.signatureKey}
malwareScanLink={download.malwareScanLink} malwareScanLink={download.malwareScanLink}
modVersion={download.modVersion} modVersion={download.modVersion}
customNote={download.customNote} customNote={download.customNote}
mediaUrl={download.mediaUrl}
onUrlChange={handleDownloadUrlChange} onUrlChange={handleDownloadUrlChange}
onRemove={removeDownloadUrl} onRemove={removeDownloadUrl}
/> />
@ -524,11 +526,13 @@ export const ModForm = () => {
type DownloadUrlFieldsProps = { type DownloadUrlFieldsProps = {
index: number index: number
url: string url: string
title?: string
hash: string hash: string
signatureKey: string signatureKey: string
malwareScanLink: string malwareScanLink: string
modVersion: string modVersion: string
customNote: string customNote: string
mediaUrl?: string
onUrlChange: (index: number, field: keyof DownloadUrl, value: string) => void onUrlChange: (index: number, field: keyof DownloadUrl, value: string) => void
onRemove: (index: number) => void onRemove: (index: number) => void
} }
@ -537,11 +541,13 @@ const DownloadUrlFields = React.memo(
({ ({
index, index,
url, url,
title,
hash, hash,
signatureKey, signatureKey,
malwareScanLink, malwareScanLink,
modVersion, modVersion,
customNote, customNote,
mediaUrl,
onUrlChange, onUrlChange,
onRemove onRemove
}: DownloadUrlFieldsProps) => { }: DownloadUrlFieldsProps) => {
@ -579,6 +585,28 @@ const DownloadUrlFields = React.memo(
</svg> </svg>
</button> </button>
</div> </div>
<div className='inputWrapperMain'>
<div className='inputWrapperMainBox'>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-96 0 512 512'
width='1em'
height='1em'
fill='currentColor'
>
<path d='M320 448c0 17.67-14.31 32-32 32H64c-17.69 0-32-14.33-32-32v-384C32 46.34 46.31 32.01 64 32.01S96 46.34 96 64.01v352h192C305.7 416 320 430.3 320 448z'></path>
</svg>
</div>
<input
type='text'
className='inputMain'
name='title'
placeholder='Download Title'
value={title}
onChange={handleChange}
/>
<div className='inputWrapperMainBox'></div>
</div>
<div className='inputWrapperMain'> <div className='inputWrapperMain'>
<div className='inputWrapperMainBox'> <div className='inputWrapperMainBox'>
<svg <svg
@ -689,6 +717,40 @@ const DownloadUrlFields = React.memo(
/> />
<div className='inputWrapperMainBox'></div> <div className='inputWrapperMainBox'></div>
</div> </div>
<div className='inputWrapperMain'>
<div className='inputWrapperMainBox'>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-96 0 512 512'
width='1em'
height='1em'
fill='currentColor'
>
<path d='M320 448c0 17.67-14.31 32-32 32H64c-17.69 0-32-14.33-32-32v-384C32 46.34 46.31 32.01 64 32.01S96 46.34 96 64.01v352h192C305.7 416 320 430.3 320 448z'></path>
</svg>
</div>
<div
style={{
width: '100%'
}}
>
<ImageUpload
onChange={(values) => {
onUrlChange(index, 'mediaUrl', values[0])
}}
/>
<input
type='text'
className='inputMain'
placeholder='Media URL'
name='mediaUrl'
value={mediaUrl}
onChange={handleChange}
/>
</div>
<div className='inputWrapperMainBox'></div>
</div>
</div> </div>
) )
} }

View File

@ -41,6 +41,7 @@ import { RouterLoadingSpinner } from 'components/LoadingSpinner'
import { OriginalAuthor } from 'components/OriginalAuthor' import { OriginalAuthor } from 'components/OriginalAuthor'
import { Viewer } from 'components/Markdown/Viewer' import { Viewer } from 'components/Markdown/Viewer'
import { PostWarnings } from 'components/PostWarning' import { PostWarnings } from 'components/PostWarning'
import { DownloadDetailsPopup } from 'components/DownloadDetailsPopup'
const MOD_REPORT_REASONS = [ const MOD_REPORT_REASONS = [
{ label: 'Actually CP', key: 'actuallyCP' }, { label: 'Actually CP', key: 'actuallyCP' },
@ -70,25 +71,6 @@ export const ModPage = () => {
const [commentCount, setCommentCount] = useState(0) const [commentCount, setCommentCount] = useState(0)
const oldDownloadListRef = useRef<HTMLDivElement>(null)
const handleViewOldLinks = () => {
if (oldDownloadListRef.current) {
// Toggle styles
if (oldDownloadListRef.current.style.height === '0px') {
// Enable styles
oldDownloadListRef.current.style.padding = ''
oldDownloadListRef.current.style.height = ''
oldDownloadListRef.current.style.border = ''
} else {
// Disable styles
oldDownloadListRef.current.style.padding = '0'
oldDownloadListRef.current.style.height = '0'
oldDownloadListRef.current.style.border = 'unset'
}
}
}
return ( return (
<> <>
<RouterLoadingSpinner /> <RouterLoadingSpinner />
@ -136,27 +118,7 @@ export const ModPage = () => {
</div> </div>
)} )}
{mod.downloadUrls.length > 1 && ( {mod.downloadUrls.length > 1 && (
<> <div className='IBMSMSMBSSDownloads'>
<div className='IBMSMSMBSSDownloadsActions'>
<button
className='btn btnMain'
id='viewOldLinks'
type='button'
onClick={handleViewOldLinks}
>
View other links
</button>
</div>
<div
ref={oldDownloadListRef}
id='oldDownloadList'
className='IBMSMSMBSSDownloads'
style={{
padding: 0,
height: '0px',
border: 'unset'
}}
>
{mod.downloadUrls {mod.downloadUrls
.slice(1) .slice(1)
.map((download, index) => ( .map((download, index) => (
@ -166,7 +128,6 @@ export const ModPage = () => {
/> />
))} ))}
</div> </div>
</>
)} )}
</div> </div>
</div> </div>
@ -606,14 +567,8 @@ const Body = ({
) )
} }
const Download = ({ const Download = (props: DownloadUrl) => {
url, const { url, title, malwareScanLink } = props
hash,
signatureKey,
malwareScanLink,
modVersion,
customNote
}: DownloadUrl) => {
const [showAuthDetails, setShowAuthDetails] = useState(false) const [showAuthDetails, setShowAuthDetails] = useState(false)
const [showNotice, setShowNotice] = useState(false) const [showNotice, setShowNotice] = useState(false)
const [showScanNotice, setShowCanNotice] = useState(false) const [showScanNotice, setShowCanNotice] = useState(false)
@ -645,6 +600,9 @@ const Download = ({
return ( return (
<div className='IBMSMSMBSSDownloadsElement'> <div className='IBMSMSMBSSDownloadsElement'>
{typeof title !== 'undefined' && title !== '' && (
<span className=''>{title}</span>
)}
<div className='IBMSMSMBSSDownloadsElementInside'> <div className='IBMSMSMBSSDownloadsElementInside'>
<button <button
className='btn btnMain IBMSMSMBSSDownloadsElementBtn' className='btn btnMain IBMSMSMBSSDownloadsElementBtn'
@ -847,56 +805,10 @@ const Download = ({
Authentication Details Authentication Details
</p> </p>
{showAuthDetails && ( {showAuthDetails && (
<div className='IBMSMSMBSSDownloadsElementInsideAltTable'> <DownloadDetailsPopup
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'> {...props}
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'> handleClose={() => setShowAuthDetails(false)}
<p>Download URL</p> />
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{url}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>SHA-256 hash</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{hash}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Signature from</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{signatureKey}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Scan</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{malwareScanLink}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Mod Version</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{modVersion}</p>
</div>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRow'>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst'>
<p>Note</p>
</div>
<div className='IBMSMSMBSSDownloadsElementInsideAltTableRowCol'>
<p>{customNote}</p>
</div>
</div>
</div>
)} )}
</div> </div>
</div> </div>

View File

@ -256,6 +256,19 @@ const validateState = async (
errors.downloadUrls![i] = 'Download url must be valid and reachable' errors.downloadUrls![i] = 'Download url must be valid and reachable'
} }
if (
downloadUrl.mediaUrl &&
downloadUrl.mediaUrl.trim() !== '' &&
(!isValidUrl(downloadUrl.mediaUrl) ||
!isValidImageUrl(downloadUrl.mediaUrl) ||
!(await isReachable(downloadUrl.mediaUrl)))
) {
if (!errors.downloadUrls)
errors.downloadUrls = Array(formState.downloadUrls.length)
errors.downloadUrls![i] = 'Media URLs must be valid and reachable image'
}
} }
} }

View File

@ -11,7 +11,7 @@
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-gap: 15px; grid-gap: 15px;
border: solid 1px rgba(255,255,255,0.05); border: solid 1px rgba(255, 255, 255, 0.05);
overflow: auto; overflow: auto;
max-height: 550px; max-height: 550px;
padding: 15px; padding: 15px;
@ -27,7 +27,7 @@
} }
.IBMSMSMBSSDownloadsTitle { .IBMSMSMBSSDownloadsTitle {
color: rgba(255,255,255,0.5); color: rgba(255, 255, 255, 0.5);
} }
.IBMSMSMBSSDownloadsElement { .IBMSMSMBSSDownloadsElement {
@ -36,11 +36,11 @@
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-gap: 10px; grid-gap: 10px;
border: solid 1px rgba(255,255,255,0); border: solid 1px rgba(255, 255, 255, 0);
background: rgba(255,255,255,0.05); background: rgba(255, 255, 255, 0.05);
padding: 10px; padding: 10px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 0 8px 0 rgb(0,0,0,0.1); box-shadow: 0 0 8px 0 rgb(0, 0, 0, 0.1);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -50,7 +50,7 @@
} }
.btnMain.IBMSMSMBSSDownloadsElementBtn { .btnMain.IBMSMSMBSSDownloadsElementBtn {
background: rgba(255,255,255,0.05); background: rgba(255, 255, 255, 0.05);
border-radius: 10px; border-radius: 10px;
width: 100%; width: 100%;
} }
@ -62,8 +62,8 @@
} }
.btnMain.IBMSMSMBSSDownloadsElementBtn:hover { .btnMain.IBMSMSMBSSDownloadsElementBtn:hover {
background: rgba(255,255,255,0.1); background: rgba(255, 255, 255, 0.1);
box-shadow: 0 0 8px 0 rgb(0,0,0,0.1); box-shadow: 0 0 8px 0 rgb(0, 0, 0, 0.1);
} }
.IBMSMSMBSSDownloadsElementInside { .IBMSMSMBSSDownloadsElementInside {
@ -71,7 +71,7 @@
flex-direction: column; flex-direction: column;
justify-content: start; justify-content: start;
align-items: start; align-items: start;
color: rgba(255,255,255,0.5); color: rgba(255, 255, 255, 0.5);
grid-gap: 10px; grid-gap: 10px;
} }
@ -97,27 +97,30 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%; width: 100%;
background: rgba(255,255,255,0); background: rgba(255, 255, 255, 0);
overflow: hidden; overflow: hidden;
border-radius: 10px; border-radius: 10px;
border: solid 1px rgba(255,255,255,0.05); border: solid 1px rgba(255, 255, 255, 0.05);
cursor: pointer; cursor: pointer;
} }
.IBMSMSMBSSDEIReactionsElement:hover { .IBMSMSMBSSDEIReactionsElement:hover {
transition: ease 0.4s; transition: ease 0.4s;
background: rgba(255,255,255,0.05); background: rgba(255, 255, 255, 0.05);
color: rgba(255,255,255,0.75); color: rgba(255, 255, 255, 0.75);
border: solid 1px rgba(255,255,255,0); border: solid 1px rgba(255, 255, 255, 0);
} }
.IBMSMSMBSSDEIReactionsElement:hover > .IBMSMSMBSSDEIReactionsElementIconWrapper { .IBMSMSMBSSDEIReactionsElement:hover
> .IBMSMSMBSSDEIReactionsElementIconWrapper {
transition: ease 0.4s; transition: ease 0.4s;
background: rgba(255,255,255,0.05); background: rgba(255, 255, 255, 0.05);
border-right: solid 1px rgba(255,255,255,0); border-right: solid 1px rgba(255, 255, 255, 0);
} }
.IBMSMSMBSSDEIReactionsElement:hover > .IBMSMSMBSSDEIReactionsElementIconWrapper > .IBMSMSMBSSDEIReactionsElementIcon { .IBMSMSMBSSDEIReactionsElement:hover
> .IBMSMSMBSSDEIReactionsElementIconWrapper
> .IBMSMSMBSSDEIReactionsElementIcon {
transform: scale(1.1); transform: scale(1.1);
} }
@ -147,9 +150,9 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 100%;
background: rgba(255,255,255,0); background: rgba(255, 255, 255, 0);
padding: 10px 5px; padding: 10px 5px;
border-right: solid 1px rgba(255,255,255,0.05); border-right: solid 1px rgba(255, 255, 255, 0.05);
} }
.IBMSMSMBSSDownloadsElementInsideDetails { .IBMSMSMBSSDownloadsElementInsideDetails {
@ -160,17 +163,20 @@
} }
.IBMSMSMBSSDEIReactionsElement.IBMSMSMBSSDEIReactionsElementActive { .IBMSMSMBSSDEIReactionsElement.IBMSMSMBSSDEIReactionsElementActive {
background: rgba(255,255,255,0.05); background: rgba(255, 255, 255, 0.05);
border: solid 1px rgba(255,255,255,0); border: solid 1px rgba(255, 255, 255, 0);
} }
.IBMSMSMBSSDEIReactionsElement.IBMSMSMBSSDEIReactionsElementActive > .IBMSMSMBSSDEIReactionsElementIconWrapper { .IBMSMSMBSSDEIReactionsElement.IBMSMSMBSSDEIReactionsElementActive
background: rgba(255,255,255,0.05); > .IBMSMSMBSSDEIReactionsElementIconWrapper {
border-right: solid 1px rgba(255,255,255,0); background: rgba(255, 255, 255, 0.05);
border-right: solid 1px rgba(255, 255, 255, 0);
} }
.IBMSMSMBSSDEIReactionsElement.IBMSMSMBSSDEIReactionsElementActive > .IBMSMSMBSSDEIReactionsElementIconWrapper > .IBMSMSMBSSDEIReactionsElementIcon { .IBMSMSMBSSDEIReactionsElement.IBMSMSMBSSDEIReactionsElementActive
color: rgba(255,255,255,0.75); > .IBMSMSMBSSDEIReactionsElementIconWrapper
> .IBMSMSMBSSDEIReactionsElementIcon {
color: rgba(255, 255, 255, 0.75);
} }
.IBMSMSMBSSDownloadsActions { .IBMSMSMBSSDownloadsActions {
@ -188,7 +194,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-radius: 10px; border-radius: 10px;
border: solid 1px rgba(255,255,255,0.1); border: solid 1px rgba(255, 255, 255, 0.1);
overflow: auto; overflow: auto;
grid-gap: 1px; grid-gap: 1px;
} }
@ -202,7 +208,7 @@
.IBMSMSMBSSDownloadsElementInsideAltTableRow:hover { .IBMSMSMBSSDownloadsElementInsideAltTableRow:hover {
transition: ease 0.4s; transition: ease 0.4s;
background: rgba(255,255,255,0.05); background: rgba(255, 255, 255, 0.05);
} }
@media (max-width: 576px) { @media (max-width: 576px) {
@ -216,12 +222,17 @@
text-align: start; text-align: start;
padding: 10px 15px; padding: 10px 15px;
} }
.IBMSMSMBSSDownloadsElementInsideAltTableRowCol_Img {
width: 100%;
max-width: 300px;
border-radius: 6px;
}
.IBMSMSMBSSDownloadsElementInsideAltTableRowCol.IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst { .IBMSMSMBSSDownloadsElementInsideAltTableRowCol.IBMSMSMBSSDownloadsElementInsideAltTableRowColFirst {
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
max-width: 200px; max-width: 200px;
background: rgba(255,255,255,0.05); background: rgba(255, 255, 255, 0.05);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -237,13 +248,12 @@
transition: ease 0.4s; transition: ease 0.4s;
cursor: pointer; cursor: pointer;
font-weight: 400; font-weight: 400;
color: rgba(255,255,255,0.25); color: rgba(255, 255, 255, 0.25);
} }
.IBMSMSMBSSDownloadsElementInsideAltText:hover { .IBMSMSMBSSDownloadsElementInsideAltText:hover {
transition: ease 0.4s; transition: ease 0.4s;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
color: rgba(255,255,255,0.75); color: rgba(255, 255, 255, 0.75);
} }

View File

@ -41,11 +41,13 @@ export interface ModFormState {
export interface DownloadUrl { export interface DownloadUrl {
url: string url: string
title?: string
hash: string hash: string
signatureKey: string signatureKey: string
malwareScanLink: string malwareScanLink: string
modVersion: string modVersion: string
customNote: string customNote: string
mediaUrl?: string
} }
export interface ModDetails extends Omit<ModFormState, 'tags'> { export interface ModDetails extends Omit<ModFormState, 'tags'> {