mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
qa: content (#7184)
* qa: content card improvements for server panel * fix: server install icon bug * qa: 1 * fix: lint * qa: 2
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { CheckIcon, PlayIcon, PlusIcon, StopCircleIcon } from '@modrinth/assets'
|
||||
import { CheckIcon, PlayIcon, PlusIcon, SpinnerIcon, StopCircleIcon } from '@modrinth/assets'
|
||||
import type { CardAction } from '@modrinth/ui'
|
||||
import { commonMessages, defineMessages, useDebugLogger, useVIntl } from '@modrinth/ui'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
@@ -75,6 +75,7 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
const debugLog = useDebugLogger('BrowseServer')
|
||||
const serverPings = shallowRef<Record<string, number | undefined>>({})
|
||||
const runningServerProjects = ref<Record<string, string>>({})
|
||||
const preparingServerProjects = ref<string[]>([])
|
||||
const lastServerHits = shallowRef<Labrinth.Search.v3.ResultSearchProject[]>([])
|
||||
const contextMenuRef = ref<ContextMenuHandle | null>(null)
|
||||
let serverPingsActive = true
|
||||
@@ -109,9 +110,16 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
}
|
||||
|
||||
async function handlePlayServerProject(projectId: string) {
|
||||
if (preparingServerProjects.value.includes(projectId)) return
|
||||
|
||||
debugLog('handlePlayServerProject', projectId)
|
||||
preparingServerProjects.value.push(projectId)
|
||||
try {
|
||||
await options.playServerProject(projectId)
|
||||
checkServerRunningStates(lastServerHits.value)
|
||||
} finally {
|
||||
preparingServerProjects.value = preparingServerProjects.value.filter((id) => id !== projectId)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAddServerToInstance(project: Labrinth.Search.v3.ResultSearchProject) {
|
||||
@@ -247,13 +255,16 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
})
|
||||
} else {
|
||||
const isInstalling = options.installingServerProjects.value.includes(serverResult.project_id)
|
||||
const isPreparing = preparingServerProjects.value.includes(serverResult.project_id)
|
||||
const isBusy = isInstalling || isPreparing
|
||||
actions.push({
|
||||
key: 'play',
|
||||
label: formatMessage(
|
||||
isInstalling ? commonMessages.installingLabel : commonMessages.playButton,
|
||||
),
|
||||
icon: PlayIcon,
|
||||
disabled: isInstalling,
|
||||
icon: isBusy ? SpinnerIcon : PlayIcon,
|
||||
iconClass: isBusy ? 'animate-spin' : undefined,
|
||||
disabled: isBusy,
|
||||
color: 'brand',
|
||||
type: 'outlined',
|
||||
onClick: () => handlePlayServerProject(serverResult.project_id),
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
:ping="ping"
|
||||
:minecraft-server="minecraftServer"
|
||||
:show-instance-play-time="showInstancePlayTime"
|
||||
:playtime-label="playtimeLabel"
|
||||
:playtime-label="playtimeLabel ?? formatMessage(messages.neverPlayed)"
|
||||
/>
|
||||
<PageHeaderMetadata v-else>
|
||||
<PageHeaderMetadataItem
|
||||
|
||||
@@ -39,7 +39,7 @@ export interface ServerInstallContext {
|
||||
project: Labrinth.Projects.v3.Project,
|
||||
modpackVersionId: string | null,
|
||||
callback?: () => void,
|
||||
) => void
|
||||
) => Promise<void>
|
||||
>,
|
||||
) => void
|
||||
setUpdateToPlayModal: (
|
||||
@@ -84,7 +84,7 @@ export function createServerInstall(opts: {
|
||||
project: Labrinth.Projects.v3.Project,
|
||||
modpackVersionId: string | null,
|
||||
callback?: () => void,
|
||||
) => void
|
||||
) => Promise<void>
|
||||
> | null = null
|
||||
let updateToPlayModalRef: ModalRef<
|
||||
(instance: GameInstance, activeVersionId: string | null, callback?: () => void) => void
|
||||
@@ -246,10 +246,10 @@ export function createServerInstall(opts: {
|
||||
project_id: contentProjectId,
|
||||
version_id: contentVersionId,
|
||||
title: project.title,
|
||||
icon_url: project.icon_url,
|
||||
},
|
||||
{
|
||||
name: project.title,
|
||||
iconPath: project.icon_url ?? null,
|
||||
link: {
|
||||
type: 'server_project_modpack',
|
||||
server_project_id: serverProjectId,
|
||||
@@ -314,7 +314,7 @@ export function createServerInstall(opts: {
|
||||
return
|
||||
}
|
||||
if (isModpack && !instance) {
|
||||
installToPlayModalRef?.show(projectV3, modpackVersionId, async () => {
|
||||
await installToPlayModalRef?.show(projectV3, modpackVersionId, async () => {
|
||||
const newInstance = await findInstalledInstance(project.id)
|
||||
if (!newInstance) return
|
||||
showModpackInstallSuccess(newInstance, serverAddress)
|
||||
|
||||
@@ -309,7 +309,6 @@ pub(crate) async fn generate_pack_from_version_id_with_reporter(
|
||||
reporter: InstallProgressReporter,
|
||||
) -> crate::Result<CreatePack> {
|
||||
let state = State::get().await?;
|
||||
let has_icon_url = icon_url.is_some();
|
||||
|
||||
let version = CachedEntry::get_version(
|
||||
&version_id,
|
||||
@@ -443,25 +442,8 @@ pub(crate) async fn generate_pack_from_version_id_with_reporter(
|
||||
.update(InstallPhaseId::ResolvingPack, None, details.clone())
|
||||
.await?;
|
||||
|
||||
let project = CachedEntry::get_project(
|
||||
&version.project_id,
|
||||
None,
|
||||
&state.pool,
|
||||
&state.api_semaphore,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::InputError(
|
||||
"Invalid project ID specified!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Only fetch the pack icon when icon_url is provided (new profile).
|
||||
// When installing to an existing profile (e.g. server projects),
|
||||
// icon_url is None and we preserve the profile's existing icon.
|
||||
let icon = if has_icon_url {
|
||||
if let Some(icon_url) = project.icon_url {
|
||||
let state = State::get().await?;
|
||||
// When no icon URL is supplied, preserve the instance's existing icon.
|
||||
let icon = if let Some(icon_url) = icon_url {
|
||||
reporter
|
||||
.set_context(
|
||||
InstallErrorContext::new("download modpack icon")
|
||||
@@ -484,9 +466,6 @@ pub(crate) async fn generate_pack_from_version_id_with_reporter(
|
||||
Some(crate::api::instance::cache_icon(icon_bytes, &state).await?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Set the icon immediately so the UI shows it during download.
|
||||
@@ -587,7 +566,7 @@ pub async fn set_instance_information(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let link = match (&description.project_id, &description.version_id) {
|
||||
let pack_link = match (&description.project_id, &description.version_id) {
|
||||
(Some(project_id), Some(version_id)) => {
|
||||
Some(InstanceLink::ModrinthModpack {
|
||||
project_id: project_id.clone(),
|
||||
@@ -605,13 +584,35 @@ pub async fn set_instance_information(
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let existing_link = crate::api::instance::get(&instance_id)
|
||||
.await?
|
||||
.map(|metadata| metadata.link);
|
||||
let link = match existing_link {
|
||||
Some(
|
||||
link @ (InstanceLink::ServerProject { .. }
|
||||
| InstanceLink::ServerProjectModpack { .. }
|
||||
| InstanceLink::ModrinthHosting { .. }
|
||||
| InstanceLink::SharedInstance { .. }),
|
||||
) => Some(link),
|
||||
_ => pack_link,
|
||||
};
|
||||
let source_kind = match &link {
|
||||
Some(InstanceLink::ModrinthModpack { .. }) => {
|
||||
Some(ContentSourceKind::ModrinthModpack)
|
||||
}
|
||||
Some(
|
||||
InstanceLink::ServerProject { .. }
|
||||
| InstanceLink::ServerProjectModpack { .. },
|
||||
) => Some(ContentSourceKind::ServerProject),
|
||||
Some(InstanceLink::ModrinthHosting { .. }) => {
|
||||
Some(ContentSourceKind::ModrinthHosting)
|
||||
}
|
||||
Some(InstanceLink::ImportedModpack { .. }) => {
|
||||
Some(ContentSourceKind::ImportedModpack)
|
||||
}
|
||||
Some(InstanceLink::SharedInstance { .. }) => {
|
||||
Some(ContentSourceKind::SharedInstance)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
crate::api::instance::edit(
|
||||
|
||||
@@ -130,7 +130,7 @@ function getProjectCardTags(result: Labrinth.Search.v3.ResultSearchProject, disp
|
||||
<template v-if="ctx.installContext?.value && ctx.variant !== 'web'">
|
||||
<div
|
||||
ref="stickyInstallHeaderRef"
|
||||
class="sticky top-0 z-20 -mx-6 -mt-6 rounded-tl-[--radius-xl] border-0 border-b border-solid bg-surface-1 px-3 py-4 border-surface-5"
|
||||
class="sticky top-0 z-20 -mx-6 -mt-6 rounded-tl-[--radius-xl] border-0 border-b border-solid bg-surface-1 px-6 py-4 border-surface-5"
|
||||
:class="[isInstallHeaderStuck ? 'border-t' : '']"
|
||||
>
|
||||
<BrowseInstallHeader />
|
||||
|
||||
+14
-1
@@ -2,6 +2,8 @@
|
||||
import { EyeIcon, WrenchIcon } from '@modrinth/assets'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import AutoLink from '#ui/components/base/AutoLink.vue'
|
||||
import Avatar from '#ui/components/base/Avatar.vue'
|
||||
import { Button, IconButton } from '#ui/components/base/buttons'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
|
||||
@@ -67,10 +69,21 @@ const title = computed(() =>
|
||||
<div
|
||||
class="flex min-h-[94px] flex-col items-stretch justify-between gap-4 bg-surface-3 px-[18px] py-4 @[700px]:flex-row @[700px]:items-center"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-1.5 pl-0.5">
|
||||
<div class="flex min-w-0 flex-1 items-center gap-4 pl-0.5">
|
||||
<AutoLink v-if="data.kind === 'modpack'" :to="data.manager.link" class="shrink-0">
|
||||
<Avatar
|
||||
:src="data.manager.iconUrl"
|
||||
:alt="data.manager.name"
|
||||
size="4rem"
|
||||
:tint-by="data.manager.name"
|
||||
no-shadow
|
||||
/>
|
||||
</AutoLink>
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-1.5">
|
||||
<h2 class="m-0 text-2xl font-semibold leading-8 text-contrast">{{ title }}</h2>
|
||||
<ManagedContentCardSummary :summary="data.summary" :installing="data.installing" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showViewContent || showSettings"
|
||||
|
||||
+4
-4
@@ -44,9 +44,9 @@ const formatDateTime = useFormatDateTime({
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
managedBy: {
|
||||
id: 'content.managed-card.managed-by',
|
||||
defaultMessage: 'Managed by',
|
||||
providedBy: {
|
||||
id: 'content.managed-card.provided-by',
|
||||
defaultMessage: 'Provided by',
|
||||
},
|
||||
server: {
|
||||
id: 'content.managed-card.server-suffix',
|
||||
@@ -110,7 +110,7 @@ const showAction = computed(
|
||||
<LockIcon aria-hidden="true" class="size-5 shrink-0" />
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-[5px] @[700px]:flex-nowrap">
|
||||
<span class="whitespace-nowrap">
|
||||
{{ formatMessage(messages.managedBy) }}
|
||||
{{ formatMessage(messages.providedBy) }}
|
||||
</span>
|
||||
<AutoLink
|
||||
:to="data.manager.link"
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Loading managed content summary',
|
||||
},
|
||||
empty: {
|
||||
id: 'content.managed-card.summary.empty',
|
||||
defaultMessage: 'No managed content',
|
||||
id: 'content.managed-card.summary.no-provided-content',
|
||||
defaultMessage: 'No provided content',
|
||||
},
|
||||
installing: {
|
||||
id: 'content.managed-card.summary.installing',
|
||||
|
||||
+7
-17
@@ -115,10 +115,6 @@ const messages = defineMessages({
|
||||
id: 'content.metadata-filter.state',
|
||||
defaultMessage: 'State',
|
||||
},
|
||||
updates: {
|
||||
id: 'content.metadata-filter.updates',
|
||||
defaultMessage: 'Updates',
|
||||
},
|
||||
warnings: {
|
||||
id: 'content.metadata-filter.warnings',
|
||||
defaultMessage: 'Warnings',
|
||||
@@ -135,10 +131,6 @@ const messages = defineMessages({
|
||||
id: 'content.metadata-filter.update.available',
|
||||
defaultMessage: 'Update available',
|
||||
},
|
||||
upToDate: {
|
||||
id: 'content.metadata-filter.update.up-to-date',
|
||||
defaultMessage: 'Up to date',
|
||||
},
|
||||
clientRetained: {
|
||||
id: 'content.metadata-filter.warning.client-retained',
|
||||
defaultMessage: 'Client file retained',
|
||||
@@ -237,15 +229,6 @@ export function useContentMetadataFilters(
|
||||
: option('disabled', formatMessage(messages.disabled)),
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'updates',
|
||||
label: formatMessage(messages.updates),
|
||||
values: (item) => [
|
||||
item.has_update
|
||||
? option('available', formatMessage(messages.updateAvailable))
|
||||
: option('current', formatMessage(messages.upToDate)),
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'warnings',
|
||||
label: formatMessage(messages.warnings),
|
||||
@@ -263,6 +246,13 @@ export function useContentMetadataFilters(
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'updates',
|
||||
label: formatMessage(messages.updateAvailable),
|
||||
direct: true,
|
||||
values: (item) =>
|
||||
item.has_update ? [option('available', formatMessage(messages.updateAvailable))] : [],
|
||||
},
|
||||
{
|
||||
key: 'open_source',
|
||||
label: formatMessage(messages.openSource),
|
||||
|
||||
@@ -1024,11 +1024,8 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
class="flex flex-wrap items-center gap-1.5 [&>div:last-of-type]:!h-[34px] [&>div:last-of-type]:!gap-1.5 [&_[data-button]]:!h-[34px]"
|
||||
>
|
||||
<div
|
||||
class="h-6 w-px shrink-0 bg-surface-5"
|
||||
:class="{
|
||||
hidden: metadataFiltersWrapped,
|
||||
'mr-0.5': !metadataFiltersWrapped,
|
||||
}"
|
||||
class="mr-0.5 h-6 w-px shrink-0 bg-surface-5"
|
||||
:class="{ invisible: metadataFiltersWrapped }"
|
||||
/>
|
||||
<DropdownFilterBar
|
||||
v-model="selectedMetadataFilters"
|
||||
|
||||
@@ -587,12 +587,12 @@
|
||||
"content.inline-backup.world-label": {
|
||||
"defaultMessage": "world"
|
||||
},
|
||||
"content.managed-card.managed-by": {
|
||||
"defaultMessage": "Managed by"
|
||||
},
|
||||
"content.managed-card.modpack-content": {
|
||||
"defaultMessage": "Modpack content"
|
||||
},
|
||||
"content.managed-card.provided-by": {
|
||||
"defaultMessage": "Provided by"
|
||||
},
|
||||
"content.managed-card.server-suffix": {
|
||||
"defaultMessage": "server"
|
||||
},
|
||||
@@ -602,15 +602,15 @@
|
||||
"content.managed-card.shared-content": {
|
||||
"defaultMessage": "Shared content"
|
||||
},
|
||||
"content.managed-card.summary.empty": {
|
||||
"defaultMessage": "No managed content"
|
||||
},
|
||||
"content.managed-card.summary.installing": {
|
||||
"defaultMessage": "Installing content..."
|
||||
},
|
||||
"content.managed-card.summary.loading": {
|
||||
"defaultMessage": "Loading managed content summary"
|
||||
},
|
||||
"content.managed-card.summary.no-provided-content": {
|
||||
"defaultMessage": "No provided content"
|
||||
},
|
||||
"content.managed-card.switch-version": {
|
||||
"defaultMessage": "Switch version"
|
||||
},
|
||||
@@ -656,12 +656,6 @@
|
||||
"content.metadata-filter.update.available": {
|
||||
"defaultMessage": "Update available"
|
||||
},
|
||||
"content.metadata-filter.update.up-to-date": {
|
||||
"defaultMessage": "Up to date"
|
||||
},
|
||||
"content.metadata-filter.updates": {
|
||||
"defaultMessage": "Updates"
|
||||
},
|
||||
"content.metadata-filter.warning.client-depends": {
|
||||
"defaultMessage": "Client depends on file"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user