mirror of
https://github.com/modrinth/code.git
synced 2026-09-04 22:10:15 +00:00
fix: qa
This commit is contained in:
@@ -17,6 +17,7 @@ const props = defineProps<{
|
||||
ariaLabel?: string
|
||||
belowModal?: boolean
|
||||
hideWhenModalOpen?: boolean
|
||||
toolbarMaxWidth?: string
|
||||
}>()
|
||||
|
||||
const INTERCOM_BUBBLE_GAP = 8
|
||||
@@ -46,6 +47,11 @@ const barStyle = computed(() => ({
|
||||
'--floating-action-bar-left-offset': leftOffset.value,
|
||||
'--floating-action-bar-right-offset': rightOffset.value,
|
||||
}))
|
||||
const toolbarStyle = computed(() =>
|
||||
props.toolbarMaxWidth
|
||||
? { '--floating-action-bar-toolbar-max-width': props.toolbarMaxWidth }
|
||||
: undefined,
|
||||
)
|
||||
|
||||
function checkCompact() {
|
||||
const el = toolbarEl.value
|
||||
@@ -203,8 +209,9 @@ onUnmounted(() => {
|
||||
ref="toolbarEl"
|
||||
role="toolbar"
|
||||
:aria-label="ariaLabel"
|
||||
class="relative overflow-clip flex items-center gap-1.5 rounded-[20px] bg-surface-3 border border-surface-5 border-solid mx-auto md:max-w-[60vw] px-3 py-2.5 shadow-[0px_1px_3px_0px_rgba(0,0,0,0.3),0px_6px_10px_0px_rgba(0,0,0,0.15)]"
|
||||
class="floating-action-toolbar relative overflow-clip flex items-center gap-1.5 rounded-[20px] bg-surface-3 border border-surface-5 border-solid mx-auto px-3 py-2.5 shadow-[0px_1px_3px_0px_rgba(0,0,0,0.3),0px_6px_10px_0px_rgba(0,0,0,0.15)]"
|
||||
:class="{ 'bar-compact': compact }"
|
||||
:style="toolbarStyle"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -220,6 +227,12 @@ onUnmounted(() => {
|
||||
transition: bottom 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.floating-action-toolbar {
|
||||
max-width: var(--floating-action-bar-toolbar-max-width, 60vw);
|
||||
}
|
||||
}
|
||||
|
||||
.floating-action-bar-enter-active {
|
||||
transition:
|
||||
transform 0.25s cubic-bezier(0.15, 1.4, 0.64, 0.96),
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<span class="font-semibold text-contrast">
|
||||
{{ formatMessage(messages.worldNameLabel) }} <span class="text-red">*</span>
|
||||
{{ formatMessage(messages.worldNameLabel) }}
|
||||
</span>
|
||||
<StyledInput
|
||||
v-model="worldName"
|
||||
|
||||
@@ -31,42 +31,6 @@
|
||||
row-key="id"
|
||||
:row-transition-name="rowTransitionName"
|
||||
>
|
||||
<template #header-world="{ column }">
|
||||
<span class="inline-flex min-w-0 max-w-full items-center gap-1 font-semibold">
|
||||
<span class="min-w-0 truncate">{{ column.label }}</span>
|
||||
<Tooltip
|
||||
theme="dismissable-prompt"
|
||||
class="inline-flex shrink-0"
|
||||
:triggers="['hover', 'focus']"
|
||||
:popper-triggers="['hover', 'focus']"
|
||||
popper-class="v-popper--interactive"
|
||||
placement="top"
|
||||
:delay="{ show: 200, hide: 100 }"
|
||||
no-auto-focus
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="formatMessage(messages.instanceTooltipTitle)"
|
||||
class="inline-flex cursor-help items-center justify-center border-0 bg-transparent p-0 text-secondary transition-colors hover:text-contrast"
|
||||
>
|
||||
<UnknownIcon class="size-4" aria-hidden="true" />
|
||||
</button>
|
||||
<template #popper>
|
||||
<div class="grid !w-64 gap-1">
|
||||
<h3 class="m-0 whitespace-nowrap text-base w-full font-bold text-contrast">
|
||||
{{ formatMessage(messages.instanceTooltipTitle) }}
|
||||
</h3>
|
||||
<p
|
||||
class="m-0 text-wrap text-sm w-full font-medium leading-tight text-secondary"
|
||||
>
|
||||
{{ formatMessage(messages.instanceTooltipDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-user="{ row: entry }">
|
||||
<AutoLink
|
||||
v-tooltip="actorName(entry)"
|
||||
@@ -171,7 +135,7 @@
|
||||
class="hidden min-h-14 bg-surface-3 @[800px]:grid @[800px]:h-14"
|
||||
:class="
|
||||
showWorldColumn
|
||||
? '@[800px]:grid-cols-[18%_52%_20%_10%]'
|
||||
? '@[800px]:grid-cols-[18%_46%_22%_14%]'
|
||||
: '@[800px]:grid-cols-[26%_58%_16%]'
|
||||
"
|
||||
>
|
||||
@@ -185,37 +149,7 @@
|
||||
v-if="showWorldColumn"
|
||||
class="hidden items-center px-2 font-semibold text-secondary @[800px]:flex"
|
||||
>
|
||||
<span class="inline-flex min-w-0 max-w-full items-center gap-1 font-semibold">
|
||||
<span class="min-w-0 truncate">{{ formatMessage(messages.worldColumn) }}</span>
|
||||
<Tooltip
|
||||
theme="dismissable-prompt"
|
||||
class="inline-flex shrink-0"
|
||||
:triggers="['hover', 'focus']"
|
||||
:popper-triggers="['hover', 'focus']"
|
||||
popper-class="v-popper--interactive"
|
||||
placement="top"
|
||||
:delay="{ show: 200, hide: 100 }"
|
||||
no-auto-focus
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="formatMessage(messages.instanceTooltipTitle)"
|
||||
class="inline-flex cursor-help items-center justify-center border-0 bg-transparent p-0 text-secondary transition-colors hover:text-contrast"
|
||||
>
|
||||
<UnknownIcon class="size-4" aria-hidden="true" />
|
||||
</button>
|
||||
<template #popper>
|
||||
<div class="grid !w-64 gap-1">
|
||||
<h3 class="m-0 whitespace-nowrap text-base font-bold text-contrast">
|
||||
{{ formatMessage(messages.instanceTooltipTitle) }}
|
||||
</h3>
|
||||
<p class="m-0 text-wrap text-sm font-medium leading-tight text-secondary">
|
||||
{{ formatMessage(messages.instanceTooltipDescription) }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span class="min-w-0 truncate">{{ formatMessage(messages.worldColumn) }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="hidden items-center justify-end pl-2 pr-4 font-semibold text-secondary @[800px]:flex"
|
||||
@@ -250,8 +184,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { IntercomBubbleIcon, UnknownIcon } from '@modrinth/assets'
|
||||
import { Tooltip } from 'floating-vue'
|
||||
import { IntercomBubbleIcon } from '@modrinth/assets'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, useSlots, watch } from 'vue'
|
||||
|
||||
import { useFormatDateTime, useRelativeTime } from '../../../composables'
|
||||
@@ -324,15 +257,6 @@ const messages = defineMessages({
|
||||
id: 'servers.audit-log.column.world',
|
||||
defaultMessage: 'Instance',
|
||||
},
|
||||
instanceTooltipTitle: {
|
||||
id: 'servers.audit-log.column.world.tooltip-title',
|
||||
defaultMessage: 'Coming soon!',
|
||||
},
|
||||
instanceTooltipDescription: {
|
||||
id: 'servers.audit-log.column.world.tooltip-description',
|
||||
defaultMessage:
|
||||
'Server instances are contained environments with their own installed content and world files.',
|
||||
},
|
||||
eventColumn: {
|
||||
id: 'servers.audit-log.column.event',
|
||||
defaultMessage: 'Actions',
|
||||
@@ -417,7 +341,7 @@ const columns = computed<TableColumn<AuditLogTableColumn>[]>(() => {
|
||||
{
|
||||
key: 'event',
|
||||
label: formatMessage(messages.eventColumn),
|
||||
width: showWorldColumn.value ? '52%' : '58%',
|
||||
width: showWorldColumn.value ? '46%' : '58%',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -425,7 +349,7 @@ const columns = computed<TableColumn<AuditLogTableColumn>[]>(() => {
|
||||
tableColumns.push({
|
||||
key: 'world',
|
||||
label: formatMessage(messages.worldColumn),
|
||||
width: '20%',
|
||||
width: '22%',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -434,7 +358,7 @@ const columns = computed<TableColumn<AuditLogTableColumn>[]>(() => {
|
||||
label: formatMessage(messages.timeColumn),
|
||||
align: 'right',
|
||||
enableSorting: true,
|
||||
width: showWorldColumn.value ? '10%' : '16%',
|
||||
width: showWorldColumn.value ? '14%' : '16%',
|
||||
})
|
||||
|
||||
return tableColumns
|
||||
|
||||
@@ -112,10 +112,6 @@ const props = withDefaults(
|
||||
worlds?: HeaderWorld[]
|
||||
powerDisabled?: boolean
|
||||
settingsLabel?: string
|
||||
showSettingsHint?: boolean
|
||||
settingsHintTitle?: string
|
||||
settingsHintDescription?: string
|
||||
settingsHintDismissLabel?: string
|
||||
actions?: HeaderAction[]
|
||||
}>(),
|
||||
{
|
||||
@@ -131,17 +127,12 @@ const props = withDefaults(
|
||||
worlds: () => [],
|
||||
powerDisabled: false,
|
||||
settingsLabel: 'Server settings',
|
||||
showSettingsHint: false,
|
||||
settingsHintTitle: '',
|
||||
settingsHintDescription: '',
|
||||
settingsHintDismissLabel: "Don't show again",
|
||||
actions: () => [],
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
openSettings: []
|
||||
dismissSettingsHint: []
|
||||
}>()
|
||||
|
||||
const client = injectModrinthClient()
|
||||
@@ -408,16 +399,8 @@ const settingsAction = computed<HeaderAction>(() => ({
|
||||
label: props.settingsLabel,
|
||||
icon: SettingsIcon,
|
||||
labelHidden: true,
|
||||
tooltip: props.showSettingsHint ? undefined : props.settingsLabel,
|
||||
tooltip: props.settingsLabel,
|
||||
onClick: () => emit('openSettings'),
|
||||
prompt: {
|
||||
title: props.settingsHintTitle,
|
||||
description: props.settingsHintDescription,
|
||||
dismissLabel: props.settingsHintDismissLabel,
|
||||
shown: props.showSettingsHint,
|
||||
placement: 'bottom-end',
|
||||
onDismiss: () => emit('dismissSettingsHint'),
|
||||
},
|
||||
}))
|
||||
|
||||
const headerActions = computed<HeaderAction[]>(() => [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<FloatingActionBar
|
||||
:shown="shown"
|
||||
:aria-label="formatMessage(messages.ariaLabel)"
|
||||
toolbar-max-width="min(1152px, calc(100vw - 3rem))"
|
||||
hide-when-modal-open
|
||||
>
|
||||
<div class="flex min-w-0 items-center gap-0.5">
|
||||
|
||||
@@ -170,16 +170,16 @@ const messages = defineMessages({
|
||||
},
|
||||
resetWorldFilesButton: {
|
||||
id: 'server.instance-settings.general.reset-world-files.button',
|
||||
defaultMessage: 'Reset world data',
|
||||
defaultMessage: 'Reset world',
|
||||
},
|
||||
resetWorldFilesDescription: {
|
||||
id: 'server.instance-settings.general.reset-world-files.description',
|
||||
defaultMessage:
|
||||
'Delete the current world data and generate a new one. Your content and files will stay the same.',
|
||||
'Create a fresh world for this instance. Content, files, settings, and backups stay the same.',
|
||||
},
|
||||
resetWorldFilesModalTitle: {
|
||||
id: 'server.instance-settings.general.reset-world-files.modal.title',
|
||||
defaultMessage: 'Reset world data',
|
||||
defaultMessage: 'Reset world',
|
||||
},
|
||||
resetWorldFilesModalDescription: {
|
||||
id: 'server.instance-settings.general.reset-world-files.modal.description',
|
||||
@@ -200,12 +200,12 @@ const messages = defineMessages({
|
||||
},
|
||||
resetEverythingButton: {
|
||||
id: 'server.instance-settings.general.reset-everything.button',
|
||||
defaultMessage: 'Reset everything',
|
||||
defaultMessage: 'Reset instance',
|
||||
},
|
||||
resetEverythingDescription: {
|
||||
id: 'server.instance-settings.general.reset-everything.description',
|
||||
defaultMessage:
|
||||
'Reset your instance completely. This removes world data, content, and any configuration. A backup of the previous instance will remain available.',
|
||||
'Recreate this instance from scratch. World data, content, files, and settings are removed. Previous backups will remain available.',
|
||||
},
|
||||
deleteInstanceButton: {
|
||||
id: 'server.instance-settings.general.delete-instance.button',
|
||||
@@ -214,7 +214,7 @@ const messages = defineMessages({
|
||||
deleteInstanceDescription: {
|
||||
id: 'server.instance-settings.general.delete-instance.description',
|
||||
defaultMessage:
|
||||
'Permanently delete this instance, including its content, files, settings, and backups.',
|
||||
'Permanently delete this instance. World data, content, files, settings, and backups will be removed.',
|
||||
},
|
||||
deleteInstanceModalTitle: {
|
||||
id: 'server.instance-settings.general.delete-instance.modal.title',
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
:has-more="hasMoreActionLogEntries"
|
||||
:loading="isActionLogFiltering"
|
||||
:loading-more="isLoadingMoreActionLogEntries"
|
||||
:show-world-column="showAuditLogInstances"
|
||||
show-world-column
|
||||
:suppress-row-transitions="isActionLogSortTransitioning"
|
||||
@load-more="loadMoreActionLogEntries"
|
||||
>
|
||||
@@ -145,14 +145,10 @@ type RoleFilter = ServerAccessRole | 'all'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
showAuditLogInstances?: boolean
|
||||
userProfileLink?: (username: string) => ServerAccessUserProfileLink
|
||||
}>(),
|
||||
{
|
||||
showAuditLogInstances: false,
|
||||
},
|
||||
{},
|
||||
)
|
||||
const showAuditLogInstances = computed(() => props.showAuditLogInstances)
|
||||
|
||||
const INVITE_RESEND_COOLDOWN_SECONDS = 2 * 60
|
||||
|
||||
@@ -294,7 +290,6 @@ const {
|
||||
client,
|
||||
serverId,
|
||||
serverFull,
|
||||
showAuditLogInstances,
|
||||
addNotification,
|
||||
})
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ type UseAccessAuditLogOptions = {
|
||||
client: AbstractModrinthClient
|
||||
serverId: string
|
||||
serverFull: ComputedRef<Archon.Servers.v1.ServerFull | null>
|
||||
showAuditLogInstances: ComputedRef<boolean>
|
||||
addNotification: AbstractWebNotificationManager['addNotification']
|
||||
}
|
||||
|
||||
@@ -47,7 +46,6 @@ export function useAccessAuditLog({
|
||||
client,
|
||||
serverId,
|
||||
serverFull,
|
||||
showAuditLogInstances,
|
||||
addNotification,
|
||||
}: UseAccessAuditLogOptions) {
|
||||
const { formatMessage } = useVIntl()
|
||||
@@ -68,9 +66,7 @@ export function useAccessAuditLog({
|
||||
const worldOptions = computed(
|
||||
() => serverFull.value?.worlds.map((world) => ({ id: world.id, name: world.name })) ?? [],
|
||||
)
|
||||
const isAuditLogWorldFilterVisible = computed(
|
||||
() => showAuditLogInstances.value && worldOptions.value.length > 0,
|
||||
)
|
||||
const isAuditLogWorldFilterVisible = computed(() => worldOptions.value.length > 0)
|
||||
|
||||
const worldById = computed(
|
||||
() => new Map(worldOptions.value.map((world) => [world.id, world] as const)),
|
||||
|
||||
@@ -130,12 +130,7 @@
|
||||
:worlds="serverFull?.worlds ?? []"
|
||||
:power-disabled="!!installError"
|
||||
:settings-label="formatMessage(messages.serverSettings)"
|
||||
:show-settings-hint="showSettingsHint"
|
||||
:settings-hint-title="formatMessage(settingsHintMessages.title)"
|
||||
:settings-hint-description="formatMessage(settingsHintMessages.description)"
|
||||
:settings-hint-dismiss-label="formatMessage(settingsHintMessages.dismiss)"
|
||||
@open-settings="openServerSettingsModal()"
|
||||
@dismiss-settings-hint="dismissSettingsHint"
|
||||
/>
|
||||
|
||||
<ServerOnboardingPanelPage v-if="isOnboarding" :browse-modpacks="handleBrowseModpacks" />
|
||||
@@ -443,21 +438,6 @@ const leaveMessages = defineMessages({
|
||||
},
|
||||
})
|
||||
|
||||
const settingsHintMessages = defineMessages({
|
||||
title: {
|
||||
id: 'servers.manage.settings-hint.title',
|
||||
defaultMessage: 'Your server settings have moved',
|
||||
},
|
||||
description: {
|
||||
id: 'servers.manage.settings-hint.description',
|
||||
defaultMessage: 'They can now be found here!',
|
||||
},
|
||||
dismiss: {
|
||||
id: 'servers.manage.settings-hint.dismiss',
|
||||
defaultMessage: "Don't show again",
|
||||
},
|
||||
})
|
||||
|
||||
const instancesHintMessages = defineMessages({
|
||||
title: {
|
||||
id: 'servers.manage.instances-hint.title',
|
||||
@@ -712,14 +692,6 @@ const errorLog = ref('')
|
||||
const errorLogFile = ref('')
|
||||
const isOnboarding = computed(() => serverData.value?.flows?.intro)
|
||||
|
||||
const SETTINGS_HINT_KEY = 'server-panel-settings-hint-dismissed'
|
||||
const settingsHintDismissed = useStorage(SETTINGS_HINT_KEY, false)
|
||||
const showSettingsHint = ref(!settingsHintDismissed.value)
|
||||
function dismissSettingsHint() {
|
||||
showSettingsHint.value = false
|
||||
settingsHintDismissed.value = true
|
||||
}
|
||||
|
||||
const INSTANCES_HINT_KEY = 'server-panel-instances-hint-dismissed'
|
||||
const instancesHintDismissed = useStorage(INSTANCES_HINT_KEY, false)
|
||||
const showInstancesHint = ref(!instancesHintDismissed.value)
|
||||
|
||||
@@ -201,9 +201,13 @@ async function loadContentSummary(
|
||||
index: number,
|
||||
): Promise<ContentSummary> {
|
||||
try {
|
||||
const content = await client.archon.content_v1.getAddons(serverId, world.id, {
|
||||
addons: true,
|
||||
updates: false,
|
||||
const content = await queryClient.fetchQuery({
|
||||
queryKey: ['content', 'list', 'v1', serverId, world.id],
|
||||
queryFn: () =>
|
||||
client.archon.content_v1.getAddons(serverId, world.id, {
|
||||
from_modpack: false,
|
||||
}),
|
||||
staleTime: 0,
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -211,13 +215,35 @@ async function loadContentSummary(
|
||||
loader: content.modloader ?? world.content?.modloader ?? null,
|
||||
loaderVersion: content.modloader_version ?? world.content?.modloader_version ?? null,
|
||||
linkedModpack: getLinkedModpack(content.modpack),
|
||||
installedContentCount: content.addons?.length ?? 0,
|
||||
installedContentCount: await getInstalledContentCount(world.id, content),
|
||||
}
|
||||
} catch {
|
||||
return createDummyContentSummary(world, index)
|
||||
}
|
||||
}
|
||||
|
||||
async function getInstalledContentCount(
|
||||
worldId: string,
|
||||
content: Archon.Content.v1.Addons,
|
||||
): Promise<number> {
|
||||
const addonCount = content.addons?.length ?? 0
|
||||
if (!content.modpack) return addonCount
|
||||
|
||||
try {
|
||||
const modpackContent = await queryClient.fetchQuery({
|
||||
queryKey: ['content', 'list', 'v1', serverId, worldId, 'modpack'],
|
||||
queryFn: () =>
|
||||
client.archon.content_v1.getAddons(serverId, worldId, {
|
||||
from_modpack: true,
|
||||
}),
|
||||
staleTime: 0,
|
||||
})
|
||||
return addonCount + (modpackContent.addons?.length ?? 0)
|
||||
} catch {
|
||||
return addonCount
|
||||
}
|
||||
}
|
||||
|
||||
function toWorldSlot(world: Archon.Servers.v1.WorldFull, content: ContentSummary): WorldSlot {
|
||||
return {
|
||||
type: 'world',
|
||||
|
||||
Reference in New Issue
Block a user