2063 lines
56 KiB
Vue
2063 lines
56 KiB
Vue
<template>
|
|
<div class="page-container">
|
|
<div class="page-contents">
|
|
<header class="card">
|
|
<div class="columns">
|
|
<h3 class="column-grow-1">Create a project</h3>
|
|
<button
|
|
title="Save draft"
|
|
class="iconified-button column"
|
|
:disabled="!$nuxt.$loading"
|
|
@click="createDraft"
|
|
>
|
|
<SaveIcon />
|
|
Save as a draft
|
|
</button>
|
|
<button
|
|
title="Submit for review"
|
|
class="iconified-button brand-button-colors column"
|
|
:disabled="!$nuxt.$loading"
|
|
@click="createProjectForReview"
|
|
>
|
|
<CheckIcon />
|
|
Submit for review
|
|
</button>
|
|
</div>
|
|
<div v-if="showKnownErrors" class="known-errors">
|
|
<ul>
|
|
<li v-if="name === ''">Your project must have a name.</li>
|
|
<li v-if="description === ''">Your project must have a summary.</li>
|
|
<li v-if="slug === ''">Your project must have a vanity URL.</li>
|
|
<li v-if="!savingAsDraft && body === ''">
|
|
Your project must have a body.
|
|
</li>
|
|
<li v-if="!savingAsDraft && versions.length < 1">
|
|
Your project must have at least one version.
|
|
</li>
|
|
<li
|
|
v-if="
|
|
license === null || license_url === null || license_url === ''
|
|
"
|
|
>
|
|
Your project must have a license.
|
|
</li>
|
|
<li v-if="currentVersionIndex !== -1">
|
|
You must finish editing your versions
|
|
</li>
|
|
<li v-if="currentGalleryIndex !== -1">
|
|
You must finish editing your gallery images
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</header>
|
|
<section class="card essentials">
|
|
<label>
|
|
<span>
|
|
<h3>Type<span class="required">*</span></h3>
|
|
<span class="no-padding">The type of project of your project.</span>
|
|
</span>
|
|
<Multiselect
|
|
v-model="projectType"
|
|
placeholder="Select one"
|
|
:options="projectTypes"
|
|
:searchable="false"
|
|
:close-on-select="true"
|
|
:show-labels="false"
|
|
:allow-empty="false"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<h3>Name<span class="required">*</span></h3>
|
|
<span>
|
|
Be creative! Generic project names will be harder to search for.
|
|
</span>
|
|
</span>
|
|
<input
|
|
v-model="name"
|
|
:class="{ 'known-error': name === '' && showKnownErrors }"
|
|
type="text"
|
|
placeholder="Enter the name"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<h3>Summary<span class="required">*</span></h3>
|
|
<span>
|
|
Give a short description of your project that will appear on
|
|
search pages.
|
|
</span>
|
|
</span>
|
|
<input
|
|
v-model="description"
|
|
:class="{ 'known-error': description === '' && showKnownErrors }"
|
|
type="text"
|
|
placeholder="Enter the summary"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<h3>Categories</h3>
|
|
<span class="no-padding">
|
|
Select up to 3 categories that will help others <br />
|
|
find your project.
|
|
</span>
|
|
</span>
|
|
<multiselect
|
|
id="categories"
|
|
v-model="categories"
|
|
:options="
|
|
$tag.categories
|
|
.filter((x) => x.project_type === projectType.toLowerCase())
|
|
.map((it) => it.name)
|
|
"
|
|
:custom-label="
|
|
(value) => value.charAt(0).toUpperCase() + value.slice(1)
|
|
"
|
|
:loading="$tag.categories.length === 0"
|
|
:multiple="true"
|
|
:searchable="false"
|
|
:show-no-results="false"
|
|
:close-on-select="false"
|
|
:clear-on-select="false"
|
|
:show-labels="false"
|
|
:max="3"
|
|
:limit="6"
|
|
:hide-selected="true"
|
|
placeholder="Choose categories"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<h3>Vanity URL (slug)<span class="required">*</span></h3>
|
|
<span class="slug-description"
|
|
>https://modrinth.com/{{ projectType.toLowerCase() }}/{{
|
|
slug ? slug : 'your-slug'
|
|
}}
|
|
</span>
|
|
</span>
|
|
<input
|
|
id="name"
|
|
v-model="slug"
|
|
:class="{ 'known-error': slug === '' && showKnownErrors }"
|
|
type="text"
|
|
placeholder="Enter the vanity URL"
|
|
/>
|
|
</label>
|
|
</section>
|
|
<section class="card project-icon">
|
|
<h3>Icon</h3>
|
|
<img
|
|
:src="
|
|
previewImage
|
|
? previewImage
|
|
: 'https://cdn.modrinth.com/placeholder.svg'
|
|
"
|
|
alt="preview-image"
|
|
/>
|
|
<SmartFileInput
|
|
:show-icon="false"
|
|
:max-size="262144"
|
|
accept="image/png,image/jpeg,image/gif,image/webp"
|
|
class="choose-image"
|
|
prompt="Choose image or drag it here"
|
|
@change="showPreviewImage"
|
|
/>
|
|
<button
|
|
class="iconified-button"
|
|
@click="
|
|
icon = null
|
|
previewImage = null
|
|
"
|
|
>
|
|
<TrashIcon />
|
|
Reset
|
|
</button>
|
|
</section>
|
|
<section class="card game-sides">
|
|
<div class="columns">
|
|
<div class="column">
|
|
<h3>Supported environments</h3>
|
|
<span>
|
|
Let others know what environments your project supports.
|
|
</span>
|
|
</div>
|
|
<div class="labeled-control">
|
|
<h3>Client<span class="required">*</span></h3>
|
|
<Multiselect
|
|
v-model="clientSideType"
|
|
placeholder="Select one"
|
|
:options="sideTypes"
|
|
:searchable="false"
|
|
:close-on-select="true"
|
|
:show-labels="false"
|
|
:allow-empty="false"
|
|
/>
|
|
</div>
|
|
<div class="labeled-control">
|
|
<h3>Server<span class="required">*</span></h3>
|
|
<Multiselect
|
|
v-model="serverSideType"
|
|
placeholder="Select one"
|
|
:options="sideTypes"
|
|
:searchable="false"
|
|
:close-on-select="true"
|
|
:show-labels="false"
|
|
:allow-empty="false"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section class="card description">
|
|
<h3>
|
|
<label
|
|
for="body"
|
|
title="You can type an extended description of your project here."
|
|
>
|
|
Body<span class="required">*</span>
|
|
</label>
|
|
</h3>
|
|
<span>
|
|
You can type an extended description of your mod here. This editor
|
|
supports
|
|
<a
|
|
class="text-link"
|
|
href="https://guides.github.com/features/mastering-markdown/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>Markdown</a
|
|
>. HTML can also be used inside your description, not including
|
|
styles, scripts, and iframes (though YouTube iframes are allowed).
|
|
</span>
|
|
<ThisOrThat
|
|
v-model="bodyViewMode"
|
|
class="separator"
|
|
:items="['source', 'preview']"
|
|
/>
|
|
<div class="edit-wrapper">
|
|
<div v-if="bodyViewMode === 'source'" class="textarea-wrapper">
|
|
<textarea
|
|
id="body"
|
|
v-model="body"
|
|
:class="{ 'known-error': body === '' && showKnownErrors }"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-if="bodyViewMode === 'preview'"
|
|
v-highlightjs
|
|
class="markdown-body"
|
|
v-html="body ? $xss($md.render(body)) : 'No body specified.'"
|
|
></div>
|
|
</div>
|
|
</section>
|
|
<section class="card versions">
|
|
<div class="title">
|
|
<h3>Versions<span class="required">*</span></h3>
|
|
<button
|
|
title="Add a version"
|
|
class="iconified-button"
|
|
:disabled="currentVersionIndex !== -1"
|
|
@click="createVersion"
|
|
>
|
|
<PlusIcon />
|
|
Add a version
|
|
</button>
|
|
</div>
|
|
<div v-if="versions.length === 0" class="no-versions-warning">
|
|
At least 1 version is required to submit for review.
|
|
</div>
|
|
<div
|
|
v-if="currentVersionIndex !== -1"
|
|
class="new-version outlined-area"
|
|
>
|
|
<div class="header">
|
|
<h3 v-if="currentVersionNew" class="title">Add a version</h3>
|
|
<h3 v-else class="title">Edit version</h3>
|
|
<div class="controls">
|
|
<button
|
|
v-if="currentVersionNew"
|
|
class="iconified-button"
|
|
title="Cancel adding version"
|
|
@click="deleteVersion()"
|
|
>
|
|
<CrossIcon />
|
|
Cancel
|
|
</button>
|
|
<button
|
|
v-else
|
|
class="iconified-button"
|
|
title="Remove version"
|
|
@click="deleteVersion()"
|
|
>
|
|
<TrashIcon />
|
|
Remove
|
|
</button>
|
|
<button
|
|
v-if="currentVersionNew"
|
|
class="brand-button-colors iconified-button"
|
|
title="Save version"
|
|
@click="saveVersion()"
|
|
>
|
|
<CheckIcon />
|
|
Add
|
|
</button>
|
|
<button
|
|
v-else
|
|
class="brand-button-colors iconified-button"
|
|
title="Save version"
|
|
@click="saveVersion()"
|
|
>
|
|
<SaveIcon />
|
|
Save
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div v-if="showKnownVersionErrors" class="known-errors">
|
|
<ul>
|
|
<li v-if="versions[currentVersionIndex].version_number === ''">
|
|
Your version must have a unique version number.
|
|
</li>
|
|
<li v-if="versions[currentVersionIndex].loaders.length < 1">
|
|
Your version must have the supported mod loaders selected.
|
|
</li>
|
|
<li v-if="versions[currentVersionIndex].game_versions.length < 1">
|
|
Your version must have the supported Minecraft versions
|
|
selected.
|
|
</li>
|
|
<li v-if="versions[currentVersionIndex].files.length < 1">
|
|
Your version must have a file uploaded.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="main">
|
|
<label>
|
|
<span>
|
|
<h3>Title</h3>
|
|
<span>
|
|
An optional nicely formatted name for this version.
|
|
</span>
|
|
</span>
|
|
<input
|
|
v-model="versions[currentVersionIndex].version_title"
|
|
type="text"
|
|
placeholder="Enter the title of this version"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<h3>Version number<span class="required">*</span></h3>
|
|
<span>
|
|
A unique identifier for versions of your project, used for
|
|
display and in URLs.
|
|
</span>
|
|
</span>
|
|
<input
|
|
v-model="versions[currentVersionIndex].version_number"
|
|
:class="{
|
|
'known-error':
|
|
versions[currentVersionIndex].version_number === '' &&
|
|
showKnownVersionErrors,
|
|
}"
|
|
type="text"
|
|
placeholder="Enter the version number"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<h3>Release channel<span class="required">*</span></h3>
|
|
<span>
|
|
Used to indicate to users the stability of this release.
|
|
</span>
|
|
</span>
|
|
<multiselect
|
|
v-model="versions[currentVersionIndex].release_channel"
|
|
:class="{
|
|
'known-error':
|
|
versions[currentVersionIndex].release_channel === null &&
|
|
showKnownVersionErrors,
|
|
}"
|
|
placeholder="Select one"
|
|
:options="['release', 'beta', 'alpha']"
|
|
:custom-label="
|
|
(value) => value.charAt(0).toUpperCase() + value.slice(1)
|
|
"
|
|
:searchable="false"
|
|
:close-on-select="true"
|
|
:show-labels="false"
|
|
:allow-empty="false"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<h3>Mod loaders<span class="required">*</span></h3>
|
|
<span> Select all mod loaders this version supports. </span>
|
|
</span>
|
|
<multiselect
|
|
v-model="versions[currentVersionIndex].loaders"
|
|
:class="{
|
|
'known-error':
|
|
versions[currentVersionIndex].loaders.length < 1 &&
|
|
showKnownVersionErrors,
|
|
}"
|
|
:options="
|
|
$tag.loaders
|
|
.filter((x) =>
|
|
x.supported_project_types.includes(
|
|
projectType.toLowerCase()
|
|
)
|
|
)
|
|
.map((it) => it.name)
|
|
"
|
|
:custom-label="
|
|
(value) =>
|
|
value === 'modloader'
|
|
? 'Risugami\'s ModLoader'
|
|
: value.charAt(0).toUpperCase() + value.slice(1)
|
|
"
|
|
:loading="$tag.loaders.length === 0"
|
|
:multiple="true"
|
|
:searchable="false"
|
|
:show-no-results="false"
|
|
:close-on-select="true"
|
|
:clear-on-select="false"
|
|
:show-labels="false"
|
|
:limit="6"
|
|
:hide-selected="true"
|
|
placeholder="Choose mod loaders..."
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>
|
|
<h3>Minecraft versions<span class="required">*</span></h3>
|
|
<span>
|
|
Select all versions of Minecraft this version supports.
|
|
</span>
|
|
</span>
|
|
<multiselect
|
|
v-model="versions[currentVersionIndex].game_versions"
|
|
:class="{
|
|
'known-error':
|
|
versions[currentVersionIndex].game_versions.length < 1 &&
|
|
showKnownVersionErrors,
|
|
}"
|
|
:options="$tag.gameVersions.map((it) => it.version)"
|
|
:loading="$tag.gameVersions.length === 0"
|
|
:multiple="true"
|
|
:searchable="true"
|
|
:show-no-results="false"
|
|
:close-on-select="false"
|
|
:clear-on-select="false"
|
|
:show-labels="false"
|
|
:limit="6"
|
|
:hide-selected="true"
|
|
placeholder="Choose versions..."
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div class="dependencies">
|
|
<h3>Dependencies</h3>
|
|
<div class="dependency-selector">
|
|
<ThisOrThat
|
|
v-model="dependencyAddMode"
|
|
class="separator"
|
|
:items="['project', 'version']"
|
|
/>
|
|
<div class="edit-info">
|
|
<input
|
|
v-model="newDependencyId"
|
|
type="text"
|
|
:placeholder="`Enter the ${dependencyAddMode} ID${
|
|
dependencyAddMode === 'project' ? '/slug' : ''
|
|
}`"
|
|
@keyup.enter="addDependency"
|
|
/>
|
|
<Multiselect
|
|
v-model="newDependencyType"
|
|
class="input"
|
|
placeholder="Select one"
|
|
:options="[
|
|
'required',
|
|
'optional',
|
|
'incompatible',
|
|
'embedded',
|
|
]"
|
|
:custom-label="
|
|
(value) => value.charAt(0).toUpperCase() + value.slice(1)
|
|
"
|
|
:searchable="false"
|
|
:close-on-select="true"
|
|
:show-labels="false"
|
|
:allow-empty="false"
|
|
/>
|
|
<button class="iconified-button" @click="addDependency">
|
|
<PlusIcon />
|
|
Add
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="new-dependencies">
|
|
<div
|
|
v-for="(dependency, index) in versions[currentVersionIndex]
|
|
.dependencies"
|
|
:key="index"
|
|
class="dependency"
|
|
>
|
|
<img
|
|
class="icon"
|
|
:src="
|
|
dependency.project
|
|
? dependency.project.icon_url
|
|
? dependency.project.icon_url
|
|
: 'https://cdn.modrinth.com/placeholder.svg?inline'
|
|
: 'https://cdn.modrinth.com/placeholder.svg?inline'
|
|
"
|
|
alt="dependency-icon"
|
|
/>
|
|
<div class="info">
|
|
<h4 class="title">
|
|
{{
|
|
dependency.project
|
|
? dependency.project.title
|
|
: 'Unknown Project'
|
|
}}
|
|
</h4>
|
|
<p v-if="dependency.version" class="version-number">
|
|
Version {{ dependency.version.version_number }} is
|
|
{{ dependency.dependency_type }}
|
|
</p>
|
|
<div class="bottom">
|
|
<button
|
|
class="iconified-button"
|
|
@click="
|
|
versions[currentVersionIndex].dependencies.splice(
|
|
index,
|
|
1
|
|
)
|
|
"
|
|
>
|
|
<TrashIcon />
|
|
Remove
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="files">
|
|
<h3>Files<span class="required">*</span></h3>
|
|
<span>
|
|
You may upload multiple files, but this should only be used for
|
|
cases like sources or Javadocs.
|
|
</span>
|
|
<p
|
|
v-if="projectType.toLowerCase() === 'modpack'"
|
|
aria-label="Warning"
|
|
>
|
|
Modpack support is currently in alpha, and you may encounter
|
|
issues. Our documentation includes instructions on
|
|
<a
|
|
href="https://docs.modrinth.com/docs/modpacks/creating_modpacks/"
|
|
target="_blank"
|
|
class="text-link"
|
|
>creating modpacks</a
|
|
>. Join us on
|
|
<a
|
|
href="https://discord.gg/EUHuJHt"
|
|
target="_blank"
|
|
class="text-link"
|
|
>Discord</a
|
|
>
|
|
for support.
|
|
</p>
|
|
<StatelessFileInput
|
|
:class="{
|
|
'known-error':
|
|
versions[currentVersionIndex].files.length < 1 &&
|
|
showKnownVersionErrors,
|
|
}"
|
|
class="file-input"
|
|
multiple
|
|
:accept="
|
|
projectType.toLowerCase() === 'modpack'
|
|
? '.mrpack,application/x-modrinth-modpack+zip'
|
|
: projectType.toLowerCase() === 'mod'
|
|
? '.jar,application/java-archive'
|
|
: '*'
|
|
"
|
|
prompt="Choose files or drag them here"
|
|
:max-size="524288000"
|
|
@change="
|
|
(x) =>
|
|
x.forEach((y) => versions[currentVersionIndex].files.push(y))
|
|
"
|
|
/>
|
|
<div class="uploaded-files">
|
|
<div
|
|
v-for="(file, index) in versions[currentVersionIndex].files"
|
|
:key="index + 'new'"
|
|
class="file"
|
|
>
|
|
<p class="filename">{{ file.name }}</p>
|
|
<button
|
|
class="action iconified-button"
|
|
@click="versions[currentVersionIndex].files.splice(index, 1)"
|
|
>
|
|
<TrashIcon aria-hidden="true" />
|
|
Remove
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="changelog">
|
|
<h3>Changes</h3>
|
|
<span>
|
|
Let everyone know what's changed since the last version. This
|
|
editor supports
|
|
<a
|
|
class="text-link"
|
|
href="https://guides.github.com/features/mastering-markdown/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>Markdown</a
|
|
>.
|
|
</span>
|
|
<ThisOrThat
|
|
v-model="changelogViewMode"
|
|
class="separator"
|
|
:items="['source', 'preview']"
|
|
/>
|
|
<div class="edit-wrapper">
|
|
<div
|
|
v-if="changelogViewMode === 'source'"
|
|
class="textarea-wrapper"
|
|
>
|
|
<textarea
|
|
id="body"
|
|
v-model="versions[currentVersionIndex].version_body"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-if="changelogViewMode === 'preview'"
|
|
v-highlightjs
|
|
class="markdown-body"
|
|
v-html="
|
|
versions[currentVersionIndex].version_body
|
|
? $xss(
|
|
$md.render(versions[currentVersionIndex].version_body)
|
|
)
|
|
: 'No changelog specified.'
|
|
"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<table>
|
|
<thead>
|
|
<tr
|
|
v-if="
|
|
(versions.length > 0 && currentVersionIndex === -1) ||
|
|
versions.length > 1
|
|
"
|
|
>
|
|
<th>Name</th>
|
|
<th>Version</th>
|
|
<th>Mod loaders</th>
|
|
<th>Minecraft versions</th>
|
|
<th>Release channel</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr
|
|
v-for="(version, index) in versions.filter((it) =>
|
|
currentVersionIndex !== -1
|
|
? it !== versions[currentVersionIndex]
|
|
: true
|
|
)"
|
|
:key="version.id"
|
|
>
|
|
<td>{{ version.version_title }}</td>
|
|
<td>{{ version.version_number }}</td>
|
|
<td>
|
|
{{
|
|
version.loaders
|
|
.map((x) => x.charAt(0).toUpperCase() + x.slice(1))
|
|
.join(', ')
|
|
}}
|
|
</td>
|
|
<td>
|
|
{{
|
|
version.game_versions
|
|
.map((x) => x.charAt(0).toUpperCase() + x.slice(1))
|
|
.join(', ')
|
|
}}
|
|
</td>
|
|
<td>
|
|
<VersionBadge
|
|
v-if="version.release_channel === 'release'"
|
|
type="release"
|
|
color="green"
|
|
/>
|
|
<VersionBadge
|
|
v-else-if="version.release_channel === 'beta'"
|
|
type="beta"
|
|
color="yellow"
|
|
/>
|
|
<VersionBadge
|
|
v-else-if="version.release_channel === 'alpha'"
|
|
type="alpha"
|
|
color="red"
|
|
/>
|
|
</td>
|
|
<td>
|
|
<button
|
|
class="iconified-button"
|
|
title="Remove version"
|
|
@click="versions.splice(index, 1)"
|
|
>
|
|
<TrashIcon />
|
|
Remove
|
|
</button>
|
|
<button
|
|
class="iconified-button"
|
|
title="Edit version"
|
|
@click="editVersion(index)"
|
|
>
|
|
<EditIcon />
|
|
Edit
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
<section class="card gallery">
|
|
<div class="title">
|
|
<div class="text">
|
|
<h3>Gallery images</h3>
|
|
<i>— this section is optional</i>
|
|
</div>
|
|
<button
|
|
title="Add an image"
|
|
class="iconified-button"
|
|
:disabled="currentGalleryIndex !== -1"
|
|
@click="createGalleryImage()"
|
|
>
|
|
<PlusIcon />
|
|
Add an image
|
|
</button>
|
|
</div>
|
|
<div
|
|
v-if="currentGalleryIndex !== -1"
|
|
class="new-gallery-item outlined-area"
|
|
>
|
|
<div class="header">
|
|
<h3 class="title">Add an image</h3>
|
|
<div class="controls">
|
|
<button
|
|
v-if="currentGalleryNew"
|
|
class="iconified-button"
|
|
title="Cancel adding image"
|
|
@click="deleteGalleryImage()"
|
|
>
|
|
<CrossIcon />
|
|
Cancel
|
|
</button>
|
|
<button
|
|
v-else
|
|
class="iconified-button"
|
|
title="Remove image"
|
|
@click="deleteGalleryImage()"
|
|
>
|
|
<TrashIcon />
|
|
Remove
|
|
</button>
|
|
<button
|
|
v-if="currentGalleryNew"
|
|
class="brand-button-colors iconified-button"
|
|
title="Add image"
|
|
@click="saveGalleryImage()"
|
|
>
|
|
<CheckIcon />
|
|
Add
|
|
</button>
|
|
<button
|
|
v-else
|
|
class="brand-button-colors iconified-button"
|
|
title="Save image"
|
|
@click="saveGalleryImage()"
|
|
>
|
|
<SaveIcon />
|
|
Save changes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div v-if="showKnownGalleryErrors" class="known-errors">
|
|
<ul>
|
|
<li v-if="gallery[currentGalleryIndex].title === ''">
|
|
Your image must have a title.
|
|
</li>
|
|
<li v-if="gallery[currentGalleryIndex].preview === null">
|
|
Your image must have a file uploaded.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="info">
|
|
<label title="The title of the gallery item">
|
|
<span>Title</span>
|
|
<input
|
|
v-model="gallery[currentGalleryIndex].title"
|
|
:class="{
|
|
'known-error':
|
|
gallery[currentGalleryIndex].title === '' &&
|
|
showKnownGalleryErrors,
|
|
}"
|
|
type="text"
|
|
placeholder="Enter a title"
|
|
/>
|
|
</label>
|
|
<label
|
|
title="The description of what is being featured in the link."
|
|
>
|
|
<span>Description (optional)</span>
|
|
<input
|
|
v-model="gallery[currentGalleryIndex].description"
|
|
type="text"
|
|
placeholder="Enter a description"
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div class="image">
|
|
<img
|
|
:src="
|
|
gallery[currentGalleryIndex].preview
|
|
? gallery[currentGalleryIndex].preview
|
|
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
|
"
|
|
alt="preview-image"
|
|
/>
|
|
<div class="bottom">
|
|
<SmartFileInput
|
|
:max-size="5242880"
|
|
accept="image/png,image/jpeg,image/gif,image/webp,.png,.jpeg,.gif,.webp"
|
|
prompt="Upload"
|
|
:class="{
|
|
'known-error':
|
|
!gallery[currentGalleryIndex].preview &&
|
|
showKnownGalleryErrors,
|
|
}"
|
|
@change="
|
|
(files) => showGalleryPreviewImage(files, currentGalleryIndex)
|
|
"
|
|
/>
|
|
<div>
|
|
<button
|
|
class="iconified-button"
|
|
@click="
|
|
gallery[currentGalleryIndex].icon = null
|
|
gallery[currentGalleryIndex].preview = null
|
|
$forceUpdate()
|
|
"
|
|
>
|
|
<TrashIcon />
|
|
Reset
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="gallery-items">
|
|
<div
|
|
v-for="(item, index) in gallery.filter((it) =>
|
|
currentGalleryIndex === -1 || it !== gallery[currentGalleryIndex]
|
|
? it !== gallery[currentGalleryIndex]
|
|
: true
|
|
)"
|
|
:key="index"
|
|
class="gallery-item"
|
|
>
|
|
<div class="info">
|
|
<h3>{{ item.title }}</h3>
|
|
<h4 v-if="item.description">{{ item.description }}</h4>
|
|
</div>
|
|
<div class="image">
|
|
<img
|
|
:src="
|
|
item.preview
|
|
? item.preview
|
|
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
|
"
|
|
alt="preview-image"
|
|
/>
|
|
</div>
|
|
<div class="controls">
|
|
<button
|
|
class="iconified-button"
|
|
title="Edit gallery image"
|
|
@click="editGalleryImage(index)"
|
|
>
|
|
<EditIcon />
|
|
Edit
|
|
</button>
|
|
<button
|
|
class="iconified-button"
|
|
title="Remove gallery image"
|
|
@click="gallery.splice(index, 1)"
|
|
>
|
|
<TrashIcon />
|
|
Remove
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section class="card extra-links">
|
|
<div class="title">
|
|
<div class="text">
|
|
<h3>External links</h3>
|
|
<i>— this section is optional</i>
|
|
</div>
|
|
</div>
|
|
<label
|
|
title="A place for users to report bugs, issues, and concerns about your project."
|
|
>
|
|
<span>Issue tracker</span>
|
|
<input
|
|
v-model="issues_url"
|
|
type="url"
|
|
placeholder="Enter a valid URL"
|
|
/>
|
|
</label>
|
|
<label
|
|
title="A page/repository containing the source code for your project"
|
|
>
|
|
<span>Source code</span>
|
|
<input
|
|
v-model="source_url"
|
|
type="url"
|
|
placeholder="Enter a valid URL"
|
|
/>
|
|
</label>
|
|
<label
|
|
title="A page containing information, documentation, and help for the project."
|
|
>
|
|
<span>Wiki page</span>
|
|
<input
|
|
v-model="wiki_url"
|
|
type="url"
|
|
placeholder="Enter a valid URL"
|
|
/>
|
|
</label>
|
|
<label title="An invitation link to your Discord server.">
|
|
<span>Discord invite</span>
|
|
<input
|
|
v-model="discord_url"
|
|
type="url"
|
|
placeholder="Enter a valid URL"
|
|
/>
|
|
</label>
|
|
</section>
|
|
<section class="card license">
|
|
<div class="title">
|
|
<div class="text">
|
|
<h3>License<span class="required">*</span></h3>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<label for="license">
|
|
<span>
|
|
It is very important to choose a proper license for your mod. You
|
|
may choose one from our list or provide a URL to a custom license.
|
|
<br />
|
|
Confused? See our
|
|
<a
|
|
class="text-link"
|
|
href="https://blog.modrinth.com/licensing-guide/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
licensing guide</a
|
|
>
|
|
for more information.
|
|
</span>
|
|
</label>
|
|
<div class="input-group">
|
|
<Multiselect
|
|
id="license"
|
|
v-model="license"
|
|
:class="{ 'known-error': license === null && showKnownErrors }"
|
|
:custom-label="(value) => value.short.toUpperCase()"
|
|
placeholder="Choose license..."
|
|
:loading="$tag.licenses.length === 0"
|
|
:options="$tag.licenses"
|
|
track-by="short"
|
|
:multiple="false"
|
|
:searchable="true"
|
|
:close-on-select="true"
|
|
:show-labels="false"
|
|
:allow-empty="false"
|
|
/>
|
|
<input
|
|
v-model="license_url"
|
|
aria-label="License URL"
|
|
type="url"
|
|
placeholder="License URL"
|
|
:class="{
|
|
'known-error':
|
|
(license_url === null || license_url === '') &&
|
|
showKnownErrors,
|
|
}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section class="card donations">
|
|
<div class="title">
|
|
<div class="text">
|
|
<h3>Donation links</h3>
|
|
<i>— this section is optional</i>
|
|
</div>
|
|
<button
|
|
title="Add a link"
|
|
class="iconified-button"
|
|
:disabled="false"
|
|
@click="
|
|
donationPlatforms.push({})
|
|
donationLinks.push('')
|
|
"
|
|
>
|
|
<PlusIcon />
|
|
Add a link
|
|
</button>
|
|
</div>
|
|
<div
|
|
v-for="(item, index) in donationPlatforms"
|
|
:key="index"
|
|
class="outlined-area"
|
|
>
|
|
<div class="header">
|
|
<h3 class="title">Link</h3>
|
|
<div class="controls">
|
|
<button
|
|
class="iconified-button"
|
|
@click="
|
|
donationPlatforms.splice(index, 1)
|
|
donationLinks.splice(index, 1)
|
|
"
|
|
>
|
|
<TrashIcon />
|
|
Remove link
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<label>
|
|
<span class="no-padding"
|
|
>Donation link<span class="required">*</span></span
|
|
>
|
|
<input
|
|
v-model="donationLinks[index]"
|
|
type="url"
|
|
placeholder="Enter a valid URL"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span class="no-padding"
|
|
>Donation platform<span class="required">*</span></span
|
|
>
|
|
<Multiselect
|
|
id="donationPlatform"
|
|
v-model="donationPlatforms[index]"
|
|
placeholder="Choose a platform..."
|
|
:loading="$tag.donationPlatforms.length === 0"
|
|
:options="$tag.donationPlatforms"
|
|
track-by="short"
|
|
label="name"
|
|
:multiple="false"
|
|
:close-on-select="true"
|
|
:show-labels="false"
|
|
:allow-empty="false"
|
|
/>
|
|
</label>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import Multiselect from 'vue-multiselect'
|
|
import SmartFileInput from '~/components/ui/SmartFileInput'
|
|
import StatelessFileInput from '~/components/ui/StatelessFileInput'
|
|
import ThisOrThat from '~/components/ui/ThisOrThat'
|
|
import VersionBadge from '~/components/ui/Badge'
|
|
|
|
import CheckIcon from '~/assets/images/utils/check.svg?inline'
|
|
import PlusIcon from '~/assets/images/utils/plus.svg?inline'
|
|
import SaveIcon from '~/assets/images/utils/save.svg?inline'
|
|
import TrashIcon from '~/assets/images/utils/trash.svg?inline'
|
|
import EditIcon from '~/assets/images/utils/edit.svg?inline'
|
|
import CrossIcon from '~/assets/images/utils/x.svg?inline'
|
|
|
|
export default {
|
|
components: {
|
|
SmartFileInput,
|
|
StatelessFileInput,
|
|
Multiselect,
|
|
CheckIcon,
|
|
PlusIcon,
|
|
SaveIcon,
|
|
TrashIcon,
|
|
CrossIcon,
|
|
EditIcon,
|
|
ThisOrThat,
|
|
VersionBadge,
|
|
},
|
|
beforeRouteLeave(to, from, next) {
|
|
if (
|
|
this.isEditing &&
|
|
!window.confirm('Are you sure that you want to leave without saving?')
|
|
) {
|
|
return
|
|
}
|
|
next()
|
|
},
|
|
data() {
|
|
return {
|
|
previewImage: null,
|
|
compiledBody: '',
|
|
releaseChannels: ['beta', 'alpha', 'release'],
|
|
currentVersionIndex: -1,
|
|
currentVersionNew: true,
|
|
currentGalleryIndex: -1,
|
|
currentGalleryNew: true,
|
|
name: '',
|
|
slug: '',
|
|
draft: false,
|
|
description: '',
|
|
body: '',
|
|
versions: [],
|
|
categories: [],
|
|
issues_url: null,
|
|
source_url: null,
|
|
wiki_url: null,
|
|
discord_url: null,
|
|
icon: null,
|
|
license: null,
|
|
license_url: null,
|
|
|
|
projectTypes: ['Mod', 'Modpack'],
|
|
projectType: 'Mod',
|
|
|
|
sideTypes: ['Required', 'Optional', 'Unsupported'],
|
|
clientSideType: 'Required',
|
|
serverSideType: 'Required',
|
|
|
|
donationLinks: [],
|
|
donationPlatforms: [],
|
|
|
|
gallery: [],
|
|
|
|
isEditing: true,
|
|
bodyViewMode: 'source',
|
|
changelogViewMode: 'source',
|
|
|
|
dependencyAddMode: 'project',
|
|
newDependencyType: 'required',
|
|
newDependencyId: '',
|
|
|
|
showKnownErrors: false,
|
|
showKnownVersionErrors: false,
|
|
showKnownGalleryErrors: false,
|
|
savingAsDraft: false,
|
|
}
|
|
},
|
|
watch: {
|
|
license(newValue, oldValue) {
|
|
if (newValue == null) {
|
|
this.license_url = ''
|
|
return
|
|
}
|
|
|
|
switch (newValue.short) {
|
|
case 'custom':
|
|
this.license_url = ''
|
|
break
|
|
default:
|
|
this.license_url = `https://cdn.modrinth.com/licenses/${newValue.short}.txt`
|
|
}
|
|
},
|
|
},
|
|
mounted() {
|
|
function preventLeave(e) {
|
|
e.preventDefault()
|
|
e.returnValue = ''
|
|
}
|
|
|
|
window.addEventListener('beforeunload', preventLeave)
|
|
this.$once('hook:beforeDestroy', () => {
|
|
window.removeEventListener('beforeunload', preventLeave)
|
|
})
|
|
},
|
|
methods: {
|
|
checkFields() {
|
|
const reviewConditions = this.body !== '' && this.versions.length > 0
|
|
if (
|
|
this.name !== '' &&
|
|
this.description !== '' &&
|
|
this.slug !== '' &&
|
|
this.license !== null &&
|
|
this.license_url !== null &&
|
|
this.license_url !== ''
|
|
) {
|
|
if (this.savingAsDraft) {
|
|
return true
|
|
} else if (reviewConditions) {
|
|
return true
|
|
}
|
|
}
|
|
this.showKnownErrors = true
|
|
this.showKnownVersionErrors = true
|
|
this.showKnownGalleryErrors = true
|
|
return false
|
|
},
|
|
async createDraft() {
|
|
this.savingAsDraft = true
|
|
if (this.checkFields()) {
|
|
this.draft = true
|
|
await this.createProject()
|
|
}
|
|
},
|
|
async createProjectForReview() {
|
|
this.savingAsDraft = false
|
|
if (this.checkFields()) {
|
|
await this.createProject()
|
|
}
|
|
},
|
|
async createProject() {
|
|
this.$nuxt.$loading.start()
|
|
|
|
for (let i = 0; i < this.versions.length; i++) {
|
|
const version = this.versions[i]
|
|
if (!version.version_title) {
|
|
version.version_title = version.version_number
|
|
}
|
|
|
|
const newFileParts = []
|
|
for (let j = 0; j < version.files.length; j++) {
|
|
newFileParts.push(`version-${i}-${j}`)
|
|
}
|
|
|
|
version.file_parts = newFileParts
|
|
}
|
|
|
|
const formData = new FormData()
|
|
|
|
formData.append(
|
|
'data',
|
|
JSON.stringify({
|
|
title: this.name,
|
|
project_type: this.projectType.toLowerCase(),
|
|
slug: this.slug,
|
|
description: this.description,
|
|
body: this.body,
|
|
initial_versions: this.versions,
|
|
team_members: [
|
|
{
|
|
user_id: this.$auth.user.id,
|
|
name: this.$auth.user.username,
|
|
role: 'Owner',
|
|
},
|
|
],
|
|
categories: this.categories,
|
|
issues_url: this.issues_url ? this.issues_url : null,
|
|
source_url: this.source_url ? this.source_url : null,
|
|
wiki_url: this.wiki_url ? this.wiki_url : null,
|
|
discord_url: this.discord_url,
|
|
client_side: this.clientSideType.toLowerCase(),
|
|
server_side: this.serverSideType.toLowerCase(),
|
|
license_id: this.license ? this.license.short : 'arr',
|
|
license_url: this.license_url ? this.license_url : null,
|
|
is_draft: this.draft,
|
|
donation_urls: this.donationPlatforms.map((it, index) => {
|
|
return {
|
|
id: it.short,
|
|
platform: it.name,
|
|
url: this.donationLinks[index],
|
|
}
|
|
}),
|
|
gallery_items: this.gallery.map((x, index) => {
|
|
return {
|
|
item: `gallery-${index}`,
|
|
featured: false,
|
|
title: x.title,
|
|
description: x.description || null,
|
|
}
|
|
}),
|
|
})
|
|
)
|
|
|
|
if (this.icon) {
|
|
formData.append('icon', new Blob([this.icon]), this.icon.name)
|
|
}
|
|
|
|
for (let i = 0; i < this.gallery.length; i++) {
|
|
formData.append(
|
|
`gallery-${i}`,
|
|
new Blob([this.gallery[i].icon]),
|
|
this.gallery[i].icon.name
|
|
)
|
|
}
|
|
|
|
for (let i = 0; i < this.versions.length; i++) {
|
|
const version = this.versions[i]
|
|
for (let j = 0; j < version.files.length; j++) {
|
|
formData.append(
|
|
`version-${i}-${j}`,
|
|
new Blob([version.files[j]]),
|
|
version.files[j].name
|
|
)
|
|
}
|
|
}
|
|
|
|
try {
|
|
await this.$axios({
|
|
url: 'project',
|
|
method: 'POST',
|
|
data: formData,
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data',
|
|
Authorization: this.$auth.token,
|
|
},
|
|
})
|
|
|
|
this.isEditing = false
|
|
await this.$store.dispatch('user/fetchProjects')
|
|
|
|
await this.$router.replace(`/user/${this.$auth.user.username}`)
|
|
} catch (err) {
|
|
let description = !err.response.data
|
|
? 'Data is null?'
|
|
: err.response.data.description
|
|
|
|
if (description.includes('JSON')) {
|
|
description = 'Please fill in missing required fields.'
|
|
}
|
|
|
|
this.$notify({
|
|
group: 'main',
|
|
title: 'An error occurred',
|
|
text: description,
|
|
type: 'error',
|
|
duration: 10000,
|
|
})
|
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
}
|
|
|
|
this.$nuxt.$loading.finish()
|
|
},
|
|
|
|
showPreviewImage(files) {
|
|
const reader = new FileReader()
|
|
this.icon = files[0]
|
|
|
|
if (this.icon instanceof Blob) {
|
|
reader.readAsDataURL(this.icon)
|
|
|
|
reader.onload = (event) => {
|
|
this.previewImage = event.target.result
|
|
}
|
|
}
|
|
},
|
|
|
|
showGalleryPreviewImage(files, index) {
|
|
const reader = new FileReader()
|
|
this.gallery[index].icon = files[0]
|
|
|
|
if (this.gallery[index].icon instanceof Blob) {
|
|
reader.readAsDataURL(this.gallery[index].icon)
|
|
|
|
reader.onload = (event) => {
|
|
this.gallery[index].preview = event.target.result
|
|
|
|
// TODO: Find an alternative for this!
|
|
this.$forceUpdate()
|
|
}
|
|
}
|
|
},
|
|
|
|
createVersion() {
|
|
this.versions.push({
|
|
files: [],
|
|
version_number: '',
|
|
version_title: '',
|
|
version_body: '',
|
|
dependencies: [],
|
|
game_versions: [],
|
|
release_channel: 'release',
|
|
loaders: [],
|
|
featured: false,
|
|
})
|
|
|
|
this.currentVersionIndex = this.versions.length - 1
|
|
this.currentVersionNew = true
|
|
},
|
|
|
|
saveVersion() {
|
|
const version = this.versions[this.currentVersionIndex]
|
|
if (
|
|
version.version_number !== '' &&
|
|
version.releaseChannels !== null &&
|
|
version.loaders.length > 0 &&
|
|
version.game_versions.length > 0 &&
|
|
version.files.length > 0
|
|
) {
|
|
this.currentVersionIndex = -1
|
|
} else {
|
|
this.showKnownVersionErrors = true
|
|
}
|
|
},
|
|
|
|
editVersion(index) {
|
|
this.currentVersionIndex = index
|
|
this.currentVersionNew = false
|
|
},
|
|
|
|
deleteVersion() {
|
|
this.versions.splice(this.currentVersionIndex, 1)
|
|
this.currentVersionIndex = -1
|
|
this.showKnownVersionErrors = false
|
|
},
|
|
|
|
createGalleryImage() {
|
|
this.gallery.push({
|
|
title: '',
|
|
description: '',
|
|
preview: null,
|
|
})
|
|
this.currentGalleryIndex = this.gallery.length - 1
|
|
this.currentGalleryNew = true
|
|
},
|
|
|
|
saveGalleryImage() {
|
|
const image = this.gallery[this.currentGalleryIndex]
|
|
if (image.title !== '' && image.preview !== null) {
|
|
this.currentGalleryIndex = -1
|
|
this.showKnownGalleryErrors = false
|
|
} else {
|
|
this.showKnownGalleryErrors = true
|
|
}
|
|
},
|
|
|
|
editGalleryImage(index) {
|
|
this.currentGalleryIndex = index
|
|
this.currentGalleryNew = false
|
|
},
|
|
|
|
deleteGalleryImage() {
|
|
this.gallery.splice(this.currentGalleryIndex, 1)
|
|
this.currentGalleryIndex = -1
|
|
this.showKnownGalleryErrors = false
|
|
},
|
|
|
|
async addDependency() {
|
|
try {
|
|
if (this.dependencyAddMode === 'project') {
|
|
const project = (
|
|
await this.$axios.get(`project/${this.newDependencyId}`)
|
|
).data
|
|
|
|
this.versions[this.currentVersionIndex].dependencies.push({
|
|
project,
|
|
project_id: project.id,
|
|
dependency_type: this.newDependencyType,
|
|
})
|
|
} else if (this.dependencyAddMode === 'version') {
|
|
const version = (
|
|
await this.$axios.get(`version/${this.newDependencyId}`)
|
|
).data
|
|
const project = (
|
|
await this.$axios.get(`project/${version.project_id}`)
|
|
).data
|
|
|
|
this.versions[this.currentVersionIndex].dependencies.push({
|
|
version,
|
|
project,
|
|
version_id: version.id,
|
|
project_id: project.id,
|
|
dependency_type: this.newDependencyType,
|
|
})
|
|
}
|
|
} catch {
|
|
this.$notify({
|
|
group: 'main',
|
|
title: 'Invalid Dependency',
|
|
text: 'The specified dependency does not exist',
|
|
type: 'error',
|
|
})
|
|
}
|
|
|
|
this.newDependencyId = ''
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.title {
|
|
.text * {
|
|
display: inline;
|
|
}
|
|
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.page-contents {
|
|
display: grid;
|
|
grid-template:
|
|
'header header header' auto
|
|
'essentials essentials essentials' auto
|
|
'project-icon project-icon project-icon' auto
|
|
'game-sides game-sides game-sides' auto
|
|
'description description description' auto
|
|
'versions versions versions' auto
|
|
'gallery gallery gallery' auto
|
|
'extra-links extra-links extra-links' auto
|
|
'license license license' auto
|
|
'donations donations donations' auto
|
|
'footer footer footer' auto
|
|
/ 4fr 1fr 4fr;
|
|
|
|
@media screen and (min-width: 1024px) {
|
|
grid-template:
|
|
'header header header' auto
|
|
'essentials essentials project-icon' auto
|
|
'game-sides game-sides game-sides' auto
|
|
'description description description' auto
|
|
'versions versions versions' auto
|
|
'gallery gallery gallery' auto
|
|
'extra-links license license' auto
|
|
'donations donations donations' auto
|
|
'footer footer footer' auto
|
|
/ 4fr 2fr 1.5fr;
|
|
}
|
|
|
|
column-gap: var(--spacing-card-md);
|
|
row-gap: var(--spacing-card-md);
|
|
}
|
|
|
|
header {
|
|
grid-area: header;
|
|
|
|
h3 {
|
|
margin: auto 0;
|
|
color: var(--color-text-dark);
|
|
font-weight: var(--font-weight-extrabold);
|
|
}
|
|
|
|
button {
|
|
margin-left: 0.5rem;
|
|
}
|
|
}
|
|
|
|
section.essentials {
|
|
grid-area: essentials;
|
|
}
|
|
|
|
section.project-icon {
|
|
grid-area: project-icon;
|
|
|
|
img {
|
|
max-width: 100%;
|
|
margin-bottom: 0.25rem;
|
|
border-radius: var(--size-rounded-lg);
|
|
|
|
@media screen and (min-width: 420px) {
|
|
max-width: 20rem;
|
|
}
|
|
|
|
@media screen and (min-width: 1024px) {
|
|
max-width: 100%;
|
|
}
|
|
}
|
|
|
|
.iconified-button {
|
|
margin-top: 0.5rem;
|
|
}
|
|
}
|
|
|
|
section.game-sides {
|
|
grid-area: game-sides;
|
|
|
|
.columns {
|
|
flex-wrap: wrap;
|
|
|
|
div {
|
|
flex: 2;
|
|
}
|
|
|
|
.labeled-control {
|
|
margin-left: var(--spacing-card-lg);
|
|
margin-bottom: 0.5rem;
|
|
|
|
h3 {
|
|
margin-bottom: var(--spacing-card-sm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
section.description {
|
|
grid-area: description;
|
|
|
|
.separator {
|
|
margin: var(--spacing-card-sm) 0;
|
|
}
|
|
|
|
.edit-wrapper * {
|
|
min-height: 10rem;
|
|
max-height: 40rem;
|
|
}
|
|
|
|
.markdown-body {
|
|
overflow-y: auto;
|
|
padding: 0 var(--spacing-card-sm);
|
|
}
|
|
}
|
|
|
|
.outlined-area {
|
|
margin: var(--spacing-card-md) 0;
|
|
border: 1px solid var(--color-divider);
|
|
border-radius: var(--size-rounded-card);
|
|
padding: 1rem;
|
|
|
|
.header {
|
|
display: flex;
|
|
align-items: center;
|
|
padding-bottom: 1rem;
|
|
grid-area: header;
|
|
|
|
.title {
|
|
flex-grow: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
flex-direction: row;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
section.versions {
|
|
grid-area: versions;
|
|
|
|
table {
|
|
border-collapse: collapse;
|
|
margin-bottom: var(--spacing-card-md);
|
|
background: var(--color-raised-bg);
|
|
border-radius: var(--size-rounded-card);
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
|
|
* {
|
|
text-align: left;
|
|
}
|
|
|
|
th,
|
|
td {
|
|
&:first-child {
|
|
text-align: center;
|
|
width: 7%;
|
|
|
|
svg {
|
|
color: var(--color-text);
|
|
|
|
&:hover,
|
|
&:focus {
|
|
color: var(--color-text-hover);
|
|
}
|
|
}
|
|
}
|
|
|
|
&:nth-child(2),
|
|
&:nth-child(5) {
|
|
padding-left: 0;
|
|
width: 12%;
|
|
}
|
|
|
|
&:last-child {
|
|
display: flex;
|
|
}
|
|
|
|
@media screen and (max-width: 800px) {
|
|
+ &:nth-child(4),
|
|
&:nth-child(3) {
|
|
display: none;
|
|
}
|
|
&:first-child,
|
|
&:nth-child(5) {
|
|
width: unset;
|
|
}
|
|
}
|
|
|
|
@media screen and (max-width: 1024px) {
|
|
&:nth-child(2) {
|
|
display: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
th {
|
|
color: var(--color-heading);
|
|
letter-spacing: 0.02rem;
|
|
padding: 0.75rem 1rem;
|
|
}
|
|
|
|
td {
|
|
overflow: hidden;
|
|
padding: 0.75rem 1rem;
|
|
|
|
img {
|
|
height: 3rem;
|
|
width: 3rem;
|
|
}
|
|
}
|
|
}
|
|
|
|
hr {
|
|
background-color: var(--color-divider);
|
|
border: none;
|
|
color: var(--color-divider);
|
|
height: 1px;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.no-versions-warning {
|
|
@extend .required;
|
|
text-align: center;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.new-version {
|
|
display: grid;
|
|
grid-template:
|
|
'header header' auto
|
|
'known-errors known-errors' auto
|
|
'main main' auto
|
|
'dependencies dependencies' auto
|
|
'files files'
|
|
'changelog changelog'
|
|
/ 5fr 4fr;
|
|
column-gap: var(--spacing-card-md);
|
|
|
|
@media screen and (min-width: 1024px) {
|
|
grid-template:
|
|
'header header' auto
|
|
'known-errors known-errors' auto
|
|
'main dependencies' auto
|
|
'main files' 1fr
|
|
'changelog changelog'
|
|
/ 5fr 4fr;
|
|
}
|
|
|
|
.main {
|
|
grid-area: main;
|
|
}
|
|
|
|
.dependencies {
|
|
grid-area: dependencies;
|
|
|
|
.dependency-selector {
|
|
.separator {
|
|
margin: var(--spacing-card-sm) 0;
|
|
}
|
|
|
|
h4 {
|
|
margin: 0 0 0.25rem 0;
|
|
}
|
|
|
|
.edit-info {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
|
|
.multiselect {
|
|
max-width: 10rem;
|
|
}
|
|
|
|
.iconified-button {
|
|
min-height: 2.5rem;
|
|
}
|
|
|
|
input {
|
|
flex-grow: 1;
|
|
max-width: fit-content;
|
|
}
|
|
|
|
input,
|
|
.multiselect,
|
|
.iconified-button {
|
|
margin: 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
.new-dependencies {
|
|
margin-top: var(--spacing-card-sm);
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
|
|
.dependency {
|
|
align-items: center;
|
|
display: flex;
|
|
flex-basis: 50%;
|
|
margin-bottom: 0.5rem;
|
|
|
|
.icon {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
margin-right: 0.5rem;
|
|
border-radius: var(--size-rounded-xs);
|
|
object-fit: contain;
|
|
}
|
|
|
|
.info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
padding: 0.25rem;
|
|
|
|
p {
|
|
margin: 0;
|
|
}
|
|
|
|
.title {
|
|
margin: 0 0.25rem 0 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.files {
|
|
grid-area: files;
|
|
|
|
.file-input {
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.uploaded-files {
|
|
.file {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 0.25rem;
|
|
flex-wrap: wrap;
|
|
row-gap: 0.25rem;
|
|
|
|
* {
|
|
margin-left: 0.25rem;
|
|
}
|
|
|
|
.filename {
|
|
margin: 0;
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.changelog {
|
|
grid-area: changelog;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
.separator {
|
|
margin: var(--spacing-card-sm) 0;
|
|
}
|
|
|
|
.edit-wrapper {
|
|
flex: 1;
|
|
|
|
.textarea-wrapper {
|
|
min-height: 10rem;
|
|
height: 100%;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
section.gallery {
|
|
grid-area: gallery;
|
|
|
|
.gallery-items {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--spacing-card-lg);
|
|
|
|
.gallery-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
|
|
h2 {
|
|
margin: 0;
|
|
}
|
|
h4 {
|
|
margin: 0;
|
|
}
|
|
width: calc((100% - 2 * var(--spacing-card-lg)) / 3);
|
|
|
|
img {
|
|
width: 100%;
|
|
border-radius: var(--size-rounded-card);
|
|
}
|
|
}
|
|
}
|
|
|
|
.new-gallery-item {
|
|
margin-top: 1rem;
|
|
display: grid;
|
|
grid-template:
|
|
'header' auto
|
|
'known-errors' auto
|
|
'info' auto
|
|
'image' auto;
|
|
|
|
@media screen and (min-width: 1024px) {
|
|
grid-template:
|
|
'header header' auto
|
|
'known-errors known-errors' auto
|
|
'info image' auto;
|
|
}
|
|
|
|
column-gap: var(--spacing-card-md);
|
|
row-gap: var(--spacing-card-sm);
|
|
|
|
.info {
|
|
grid-area: info;
|
|
|
|
label {
|
|
align-items: center;
|
|
margin-top: var(--spacing-card-sm);
|
|
|
|
span {
|
|
flex: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
.image {
|
|
grid-area: image;
|
|
|
|
img {
|
|
width: 100%;
|
|
margin-top: 0.5rem;
|
|
margin-bottom: 0.5rem;
|
|
|
|
height: 14rem;
|
|
object-fit: cover;
|
|
|
|
border-radius: var(--size-rounded-card);
|
|
}
|
|
|
|
.bottom {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
}
|
|
}
|
|
|
|
.buttons {
|
|
grid-area: buttons;
|
|
|
|
hr {
|
|
margin-top: 0.5rem;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
section.extra-links {
|
|
grid-area: extra-links;
|
|
|
|
.title {
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
label {
|
|
align-items: center;
|
|
margin-top: var(--spacing-card-sm);
|
|
|
|
span {
|
|
flex: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
section.license {
|
|
grid-area: license;
|
|
|
|
.title {
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
label {
|
|
margin-top: var(--spacing-card-sm);
|
|
}
|
|
|
|
.row {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
}
|
|
|
|
section.donations {
|
|
grid-area: donations;
|
|
}
|
|
|
|
.footer {
|
|
grid-area: footer;
|
|
}
|
|
|
|
.choose-image {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.card {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.required {
|
|
color: var(--color-badge-red-bg);
|
|
}
|
|
|
|
.essentials,
|
|
.new-version,
|
|
.donations {
|
|
label {
|
|
margin-bottom: 1rem;
|
|
}
|
|
span {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
@media screen and (min-width: 1024px) {
|
|
label {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
input {
|
|
margin-left: 1.5rem;
|
|
}
|
|
}
|
|
}
|
|
|
|
.slug-description {
|
|
overflow-wrap: break-word;
|
|
}
|
|
</style>
|