Improve editing project versions (#4933)

* add edit versions dropdown menu

* implement improved edit version with individual edit stages

* make changelog bigger

* update button styles

* remove hover button when hover on row

* bring editing versions back to project settings

* bring back gallery edit and upload in project page

* fix progress value

* fix admonition import

* fix v3 upload for modpacks

* fix modpack loader display for editing version and better open edit/create modal handling

* fix currentMember prop

* fix modpack loader displaying incorrectly

* fix max length

* fix version url after making an edit to version and fix delete

* small max height fix

* hide edit dependencies for modpack

* pnpm run fix

* fix import

* add tooltip

* update icons

* update copy and create version button style
This commit is contained in:
Truman Gao
2025-12-19 13:24:14 -08:00
committed by GitHub
parent 0663b8adb0
commit 3f93041ca2
25 changed files with 586 additions and 164 deletions

View File

@@ -47,7 +47,7 @@
>
</div>
<a
:href="version.primaryFile.url"
:href="version.primaryFile?.url"
class="iconified-button download"
:title="`Download ${version.name}`"
>

View File

@@ -196,7 +196,7 @@
</div>
</div>
<Admonition v-if="!hideGalleryAdmonition && currentMember" type="info" class="mb-4">
Managing gallery has moved! You can now add and edit gallery images in the
Creating and editing gallery images can now be done directly from the
<NuxtLink to="settings/gallery" class="font-medium text-blue hover:underline"
>project settings</NuxtLink
>.
@@ -224,6 +224,27 @@
</div>
</template>
</Admonition>
<div v-if="currentMember && project.gallery.length" class="card header-buttons">
<FileInput
:max-size="5242880"
:accept="acceptFileTypes"
prompt="Upload an image"
aria-label="Upload an image"
class="iconified-button brand-button"
:disabled="!isPermission(currentMember?.permissions, 1 << 2)"
@change="handleFiles"
>
<UploadIcon aria-hidden="true" />
</FileInput>
<span class="indicator">
<InfoIcon aria-hidden="true" /> Click to choose an image or drag one onto this page
</span>
<DropArea
:accept="acceptFileTypes"
:disabled="!isPermission(currentMember?.permissions, 1 << 2)"
@change="handleFiles"
/>
</div>
<div v-if="project.gallery.length" class="items">
<div v-for="(item, index) in project.gallery" :key="index" class="card gallery-item">
<a class="gallery-thumbnail" @click="expandImage(item, index)">
@@ -247,6 +268,37 @@
<CalendarIcon aria-hidden="true" aria-label="Date created" />
{{ $dayjs(item.created).format('MMMM D, YYYY') }}
</div>
<div v-if="currentMember" class="gallery-buttons input-group">
<button
class="iconified-button"
@click="
() => {
resetEdit()
editIndex = index
editTitle = item.title
editDescription = item.description
editFeatured = item.featured
editOrder = item.ordering
$refs.modal_edit_item.show()
}
"
>
<EditIcon aria-hidden="true" />
Edit
</button>
<button
class="iconified-button"
@click="
() => {
deleteIndex = index
$refs.modal_confirm.show()
}
"
>
<TrashIcon aria-hidden="true" />
Remove
</button>
</div>
</div>
</div>
</div>
@@ -266,9 +318,11 @@
import {
CalendarIcon,
ContractIcon,
EditIcon,
ExpandIcon,
ExternalIcon,
ImageIcon,
InfoIcon,
LeftArrowIcon,
PlusIcon,
RightArrowIcon,
@@ -276,18 +330,23 @@ import {
SettingsIcon,
StarIcon,
TransferIcon,
TrashIcon,
UploadIcon,
XIcon,
} from '@modrinth/assets'
import {
Admonition,
ButtonStyled,
ConfirmModal,
DropArea,
FileInput,
injectNotificationManager,
NewModal as Modal,
} from '@modrinth/ui'
import { useLocalStorage } from '@vueuse/core'
import { isPermission } from '~/utils/permissions.ts'
const props = defineProps({
project: {
type: Object,

View File

@@ -1,6 +1,9 @@
<template>
<div>
<CreateProjectVersionModal ref="modal"></CreateProjectVersionModal>
<CreateProjectVersionModal
v-if="currentMember"
ref="create-project-version-modal"
></CreateProjectVersionModal>
<ConfirmModal
v-if="currentMember"
@@ -27,32 +30,59 @@
project.slug ? project.slug : project.id
}/version/${encodeURI(version.displayUrlEnding)}`
"
:open-modal="currentMember ? openModal : undefined"
:open-modal="currentMember ? () => handleOpenCreateVersionModal() : undefined"
>
<template #actions="{ version }">
<ButtonStyled circular type="transparent">
<a
v-tooltip="`Download`"
:href="getPrimaryFile(version).url"
class="group-hover:!bg-brand group-hover:[&>svg]:!text-brand-inverted"
aria-label="Download"
@click="emit('onDownload')"
>
<DownloadIcon aria-hidden="true" />
</a>
</ButtonStyled>
<ButtonStyled circular type="transparent">
<a
v-tooltip="`Edit version`"
aria-label="Edit"
@click="() => handleOpenEditVersionModal(version)"
<OverflowMenu
v-tooltip="'Edit version'"
class="hover:!bg-button-bg [&>svg]:!text-green"
:dropdown-id="`${baseDropdownId}-edit-${version.id}`"
:options="[
{
id: 'edit-details',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-details'),
},
{
id: 'edit-changelog',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-changelog'),
},
{
id: 'edit-dependencies',
action: () =>
handleOpenEditVersionModal(version.id, project.id, 'add-dependencies'),
shown: project.project_type !== 'modpack',
},
{
id: 'edit-files',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-files'),
},
]"
aria-label="Edit version"
>
<EditIcon aria-hidden="true" />
</a>
<template #edit-files>
<FileIcon aria-hidden="true" />
Edit files
</template>
<template #edit-details>
<InfoIcon aria-hidden="true" />
Edit details
</template>
<template #edit-dependencies>
<BoxIcon aria-hidden="true" />
Edit dependencies
</template>
<template #edit-changelog>
<AlignLeftIcon aria-hidden="true" />
Edit changelog
</template>
</OverflowMenu>
</ButtonStyled>
<ButtonStyled circular type="transparent">
<OverflowMenu
class="group-hover:!bg-button-bg"
v-tooltip="'More options'"
class="hover:!bg-button-bg"
:dropdown-id="`${baseDropdownId}-${version.id}`"
:options="[
{
@@ -110,8 +140,24 @@
},
{ divider: true, shown: !!currentMember },
{
id: 'edit',
action: () => handleOpenEditVersionModal(version),
id: 'edit-details',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-details'),
shown: !!currentMember,
},
{
id: 'edit-changelog',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-changelog'),
shown: !!currentMember,
},
{
id: 'edit-dependencies',
action: () =>
handleOpenEditVersionModal(version.id, project.id, 'add-dependencies'),
shown: !!currentMember && project.project_type !== 'modpack',
},
{
id: 'edit-files',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-files'),
shown: !!currentMember,
},
{
@@ -148,9 +194,21 @@
<ReportIcon aria-hidden="true" />
Report
</template>
<template #edit>
<EditIcon aria-hidden="true" />
Edit
<template #edit-files>
<FileIcon aria-hidden="true" />
Edit files
</template>
<template #edit-details>
<InfoIcon aria-hidden="true" />
Edit details
</template>
<template #edit-dependencies>
<BoxIcon aria-hidden="true" />
Edit dependencies
</template>
<template #edit-changelog>
<AlignLeftIcon aria-hidden="true" />
Edit changelog
</template>
<template #delete>
<TrashIcon aria-hidden="true" />
@@ -230,7 +288,9 @@
<div>Create your first project version.</div>
<br />
<ButtonStyled color="green">
<button @click="openModal"><PlusIcon /> Create version</button>
<button @click="() => createProjectVersionModal?.openCreateVersionModal()">
<PlusIcon /> Create version
</button>
</ButtonStyled>
</div>
</div>
@@ -241,10 +301,14 @@
<script lang="ts" setup>
import type { Labrinth } from '@modrinth/api-client'
import {
AlignLeftIcon,
BoxIcon,
ClipboardCopyIcon,
DownloadIcon,
EditIcon,
ExternalIcon,
FileIcon,
InfoIcon,
LinkIcon,
MoreVerticalIcon,
PlusIcon,
@@ -261,6 +325,7 @@ import {
OverflowMenu,
ProjectPageVersions,
} from '@modrinth/ui'
import { useTemplateRef } from 'vue'
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
import { reportVersion } from '~/utils/report-helpers.ts'
@@ -278,23 +343,32 @@ const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const { refreshVersions } = injectProjectPageContext()
const modal = ref<InstanceType<typeof CreateProjectVersionModal>>()
function openModal() {
modal.value?.show?.()
}
const tags = useGeneratedState()
const flags = useFeatureFlags()
const auth = await useAuth()
const createProjectVersionModal = useTemplateRef('create-project-version-modal')
const deleteVersionModal = ref<InstanceType<typeof ConfirmModal>>()
const selectedVersion = ref<string | null>(null)
const handleOpenCreateVersionModal = () => {
if (!currentMember) return
createProjectVersionModal.value?.openCreateVersionModal()
}
const handleOpenEditVersionModal = (
versionId: string,
projectId: string,
stageId?: string | null,
) => {
if (!currentMember) return
createProjectVersionModal.value?.openEditVersionModal(versionId, projectId, stageId)
}
const versionsWithDisplayUrl = computed(() =>
versions.value.map((v) => ({
...v,
displayUrlEnding: v.version_number,
displayUrlEnding: v.id,
})),
)
@@ -337,30 +411,4 @@ async function deleteVersion() {
stopLoading()
}
async function handleOpenEditVersionModal(version: Labrinth.Versions.v3.Version) {
selectedVersion.value = version.id
try {
const versionData = await client.labrinth.versions_v3.getVersion(version.id)
modal.value?.show({
project_id: project.id,
version_id: version.id,
name: versionData.name ?? '',
version_number: versionData.version_number ?? '',
changelog: versionData.changelog ?? '',
game_versions: versionData.game_versions ?? [],
version_type: versionData.version_type ?? 'release',
loaders: versionData.loaders ?? [],
dependencies: versionData.dependencies ?? [],
existing_files: versionData.files ?? [],
environment: versionData.environment,
})
} catch (err: any) {
addNotification({
title: 'An error occurred',
text: err.data ? err.data.description : err,
type: 'error',
})
}
}
</script>

View File

@@ -1,7 +1,22 @@
<template>
<section class="experimental-styles-within overflow-visible">
<CreateProjectVersionModal
v-if="currentMember"
ref="create-project-version-modal"
></CreateProjectVersionModal>
<ConfirmModal
v-if="currentMember"
ref="deleteVersionModal"
title="Are you sure you want to delete this version?"
description="This will remove this version forever (like really forever)."
:has-to-type="false"
proceed-label="Delete"
@proceed="deleteVersion()"
/>
<Admonition v-if="!hideVersionsAdmonition && currentMember" type="info" class="mb-4">
Managing project versions has moved! You can now add and edit versions in the
Creating and editing project versions can now be done directly from the
<NuxtLink to="settings/versions" class="font-medium text-blue hover:underline"
>project settings</NuxtLink
>.
@@ -43,24 +58,72 @@
(version) =>
`/${project.project_type}/${
project.slug ? project.slug : project.id
}/version/${encodeURI(version.displayUrlEnding)}`
}/version/${encodeURI(version.displayUrlEnding ? version.displayUrlEnding : version.id)}`
"
:open-modal="currentMember ? () => handleOpenCreateVersionModal() : undefined"
>
<template #actions="{ version }">
<ButtonStyled circular type="transparent">
<a
v-tooltip="`Download`"
:href="getPrimaryFile(version).url"
class="group-hover:!bg-brand group-hover:[&>svg]:!text-brand-inverted"
class="hover:!bg-button-bg [&>svg]:!text-green"
aria-label="Download"
@click="emit('onDownload')"
>
<DownloadIcon aria-hidden="true" />
</a>
</ButtonStyled>
<ButtonStyled v-if="currentMember" circular type="transparent">
<OverflowMenu
v-tooltip="'Edit version'"
class="hover:!bg-button-bg"
:dropdown-id="`${baseDropdownId}-edit-${version.id}`"
:options="[
{
id: 'edit-details',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-details'),
},
{
id: 'edit-changelog',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-changelog'),
},
{
id: 'edit-dependencies',
action: () =>
handleOpenEditVersionModal(version.id, project.id, 'add-dependencies'),
shown: project.project_type !== 'modpack',
},
{
id: 'edit-files',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-files'),
},
]"
aria-label="Edit version"
>
<EditIcon aria-hidden="true" />
<template #edit-files>
<FileIcon aria-hidden="true" />
Edit files
</template>
<template #edit-details>
<InfoIcon aria-hidden="true" />
Edit details
</template>
<template #edit-dependencies>
<BoxIcon aria-hidden="true" />
Edit dependencies
</template>
<template #edit-changelog>
<AlignLeftIcon aria-hidden="true" />
Edit changelog
</template>
</OverflowMenu>
</ButtonStyled>
<ButtonStyled circular type="transparent">
<OverflowMenu
class="group-hover:!bg-button-bg"
v-tooltip="'More options'"
class="hover:!bg-button-bg"
:dropdown-id="`${baseDropdownId}-${version.id}`"
:options="[
{
@@ -116,6 +179,38 @@
},
shown: flags.developerMode,
},
{ divider: true, shown: !!currentMember },
{
id: 'edit-details',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-details'),
shown: !!currentMember,
},
{
id: 'edit-changelog',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-changelog'),
shown: !!currentMember,
},
{
id: 'edit-dependencies',
action: () =>
handleOpenEditVersionModal(version.id, project.id, 'add-dependencies'),
shown: !!currentMember && project.project_type !== 'modpack',
},
{
id: 'edit-files',
action: () => handleOpenEditVersionModal(version.id, project.id, 'add-files'),
shown: !!currentMember,
},
{
id: 'delete',
color: 'red',
hoverFilled: true,
action: () => {
selectedVersion = version.id
deleteVersionModal?.show()
},
shown: !!currentMember,
},
]"
aria-label="More options"
>
@@ -140,6 +235,26 @@
<ReportIcon aria-hidden="true" />
Report
</template>
<template #edit-files>
<FileIcon aria-hidden="true" />
Edit files
</template>
<template #edit-details>
<InfoIcon aria-hidden="true" />
Edit details
</template>
<template #edit-dependencies>
<BoxIcon aria-hidden="true" />
Edit dependencies
</template>
<template #edit-changelog>
<AlignLeftIcon aria-hidden="true" />
Edit changelog
</template>
<template #delete>
<TrashIcon aria-hidden="true" />
Delete
</template>
<template #copy-id>
<ClipboardCopyIcon aria-hidden="true" />
Copy ID
@@ -166,18 +281,35 @@
<script setup>
import {
AlignLeftIcon,
BoxIcon,
ClipboardCopyIcon,
DownloadIcon,
EditIcon,
ExternalIcon,
FileIcon,
InfoIcon,
LinkIcon,
MoreVerticalIcon,
ReportIcon,
SettingsIcon,
ShareIcon,
TrashIcon,
} from '@modrinth/assets'
import { Admonition, ButtonStyled, OverflowMenu, ProjectPageVersions } from '@modrinth/ui'
import {
Admonition,
ButtonStyled,
ConfirmModal,
injectModrinthClient,
injectNotificationManager,
injectProjectPageContext,
OverflowMenu,
ProjectPageVersions,
} from '@modrinth/ui'
import { useLocalStorage } from '@vueuse/core'
import { useTemplateRef } from 'vue'
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
import { reportVersion } from '~/utils/report-helpers.ts'
const props = defineProps({
@@ -205,6 +337,24 @@ const tags = useGeneratedState()
const flags = useFeatureFlags()
const auth = await useAuth()
const client = injectModrinthClient()
const { addNotification } = injectNotificationManager()
const { refreshVersions } = injectProjectPageContext()
const deleteVersionModal = ref()
const selectedVersion = ref(null)
const createProjectVersionModal = useTemplateRef('create-project-version-modal')
const handleOpenCreateVersionModal = () => {
if (!props.currentMember) return
createProjectVersionModal.value?.openCreateVersionModal()
}
const handleOpenEditVersionModal = (versionId, projectId, stageId) => {
if (!props.currentMember) return
createProjectVersionModal.value?.openEditVersionModal(versionId, projectId, stageId)
}
const hideVersionsAdmonition = useLocalStorage(
'hideVersionsHasMovedAdmonition',
!props.versions.length,
@@ -223,4 +373,32 @@ function getPrimaryFile(version) {
async function copyToClipboard(text) {
await navigator.clipboard.writeText(text)
}
async function deleteVersion() {
const id = selectedVersion.value
if (!id) return
startLoading()
try {
await client.labrinth.versions_v3.deleteVersion(id)
addNotification({
title: 'Version deleted',
text: 'The version has been successfully deleted.',
type: 'success',
})
} catch (err) {
addNotification({
title: 'An error occurred',
text: err.data ? err.data.description : err,
type: 'error',
})
}
refreshVersions()
selectedVersion.value = null
stopLoading()
}
</script>