This commit is contained in:
Calum H. (IMB11)
2026-08-31 17:52:36 +01:00
parent 6380c12346
commit 097b8dba1e
5 changed files with 34 additions and 100 deletions
@@ -163,11 +163,12 @@ const installTooltip = computed(() => {
<template> <template>
<div <div
role="row" role="row"
class="flex items-center justify-between" class="flex items-center"
:class="{ :class="{
'h-[74px] gap-4 px-3': !inline && !enabledFor, 'h-[74px] gap-4 px-3': !inline && !enabledFor,
'h-[72px] gap-4 px-3': !inline && enabledFor, 'h-[72px] gap-2 px-3': !inline && enabledFor,
'gap-3': inline, 'gap-3': inline,
'justify-between': !enabledFor,
'opacity-50 grayscale': disabled && !installing, 'opacity-50 grayscale': disabled && !installing,
'opacity-50': installing || (enabledFor && isEnabledForDisabled && !disabled), 'opacity-50': installing || (enabledFor && isEnabledForDisabled && !disabled),
}" }"
@@ -328,7 +329,7 @@ const installTooltip = computed(() => {
hideActions hideActions
? 'flex-1' ? 'flex-1'
: enabledFor : enabledFor
? 'w-[250px] min-w-0 shrink-0' ? 'min-w-0 flex-1'
: 'flex-1 min-w-0', : 'flex-1 min-w-0',
!enabledFor && enabled === false && !disabled ? 'grayscale opacity-50' : '', !enabledFor && enabled === false && !disabled ? 'grayscale opacity-50' : '',
]" ]"
@@ -27,10 +27,6 @@ const messages = defineMessages({
defaultMessage: defaultMessage:
'Choose whether this content runs on the server, is sent to players, or is disabled in both places.', 'Choose whether this content runs on the server, is sent to players, or is disabled in both places.',
}, },
sortEnabledFor: {
id: 'content.enabled-for.sort',
defaultMessage: 'Sort by where content is enabled',
},
}) })
interface Props { interface Props {
@@ -45,7 +41,6 @@ interface Props {
flat?: boolean flat?: boolean
showItemActions?: boolean showItemActions?: boolean
showEnabledForColumn?: boolean showEnabledForColumn?: boolean
enabledForSortDirection?: ContentCardTableSortDirection
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -59,7 +54,6 @@ const props = withDefaults(defineProps<Props>(), {
flat: false, flat: false,
showItemActions: false, showItemActions: false,
showEnabledForColumn: false, showEnabledForColumn: false,
enabledForSortDirection: undefined,
}) })
const stickyHeaderRef = ref<HTMLElement | null>(null) const stickyHeaderRef = ref<HTMLElement | null>(null)
@@ -74,7 +68,6 @@ const emit = defineEmits<{
update: [id: string] update: [id: string]
switchVersion: [id: string] switchVersion: [id: string]
sort: [column: ContentCardTableSortColumn, direction: ContentCardTableSortDirection] sort: [column: ContentCardTableSortColumn, direction: ContentCardTableSortDirection]
'sort-enabled-for': [direction: ContentCardTableSortDirection]
}>() }>()
// Check if any actions are available // Check if any actions are available
@@ -194,11 +187,6 @@ function handleSort(column: ContentCardTableSortColumn) {
emit('sort', column, newDirection) emit('sort', column, newDirection)
} }
function handleEnabledForSort() {
const newDirection: ContentCardTableSortDirection =
props.enabledForSortDirection === 'desc' ? 'asc' : 'desc'
emit('sort-enabled-for', newDirection)
}
</script> </script>
<template> <template>
@@ -211,8 +199,9 @@ function handleEnabledForSort() {
v-if="!hideHeader" v-if="!hideHeader"
ref="stickyHeaderRef" ref="stickyHeaderRef"
role="rowgroup" role="rowgroup"
class="sticky top-0 z-10 flex h-12 items-center justify-between gap-4 bg-surface-3 px-3" class="sticky top-0 z-10 flex h-12 items-center bg-surface-3 px-3"
:class="[ :class="[
hasEnabledForColumn ? 'gap-2' : 'justify-between gap-4',
flat || isStuck ? 'rounded-none' : 'rounded-t-[20px]', flat || isStuck ? 'rounded-none' : 'rounded-t-[20px]',
isStuck isStuck
? 'transition-[border-radius] duration-100 border-0 border-y border-solid border-surface-4 shadow-md before:pointer-events-none before:absolute before:inset-x-0 before:-top-4 before:h-5 before:bg-surface-3' ? 'transition-[border-radius] duration-100 border-0 border-y border-solid border-surface-4 shadow-md before:pointer-events-none before:absolute before:inset-x-0 before:-top-4 before:h-5 before:bg-surface-3'
@@ -264,22 +253,9 @@ function handleEnabledForSort() {
<div <div
v-if="hasEnabledForColumn" v-if="hasEnabledForColumn"
role="columnheader" role="columnheader"
:aria-sort="
enabledForSortDirection
? enabledForSortDirection === 'asc'
? 'ascending'
: 'descending'
: 'none'
"
class="hidden w-[200px] shrink-0 items-center gap-1.5 font-semibold text-secondary @[800px]:flex" class="hidden w-[200px] shrink-0 items-center gap-1.5 font-semibold text-secondary @[800px]:flex"
> >
<button <span>{{ formatMessage(messages.enabledFor) }}</span>
type="button"
class="cursor-pointer border-0 bg-transparent p-0 font-semibold text-secondary"
@click="handleEnabledForSort"
>
{{ formatMessage(messages.enabledFor) }}
</button>
<span <span
v-tooltip="formatMessage(messages.enabledForDescription)" v-tooltip="formatMessage(messages.enabledForDescription)"
class="inline-flex size-4 cursor-help items-center justify-center" class="inline-flex size-4 cursor-help items-center justify-center"
@@ -287,15 +263,6 @@ function handleEnabledForSort() {
> >
<InfoIcon class="size-4" /> <InfoIcon class="size-4" />
</span> </span>
<button
type="button"
class="flex cursor-pointer items-center border-0 bg-transparent p-0 text-secondary"
:aria-label="formatMessage(messages.sortEnabledFor)"
@click="handleEnabledForSort"
>
<ChevronUpIcon v-if="enabledForSortDirection === 'asc'" class="size-4" />
<ChevronDownIcon v-else class="size-4" />
</button>
</div> </div>
<div <div
@@ -303,7 +270,7 @@ function handleEnabledForSort() {
:class=" :class="
hasAnyActions hasAnyActions
? hasEnabledForColumn ? hasEnabledForColumn
? 'w-[250px] min-w-0 shrink-0' ? 'min-w-0 flex-1'
: 'flex-1 min-w-0' : 'flex-1 min-w-0'
: 'flex-1' : 'flex-1'
" "
@@ -8,6 +8,7 @@ import {
PaintbrushIcon, PaintbrushIcon,
SearchIcon, SearchIcon,
SpinnerIcon, SpinnerIcon,
UnknownIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import Fuse from 'fuse.js' import Fuse from 'fuse.js'
import { computed, nextTick, ref, watchSyncEffect } from 'vue' import { computed, nextTick, ref, watchSyncEffect } from 'vue'
@@ -116,6 +117,11 @@ const messages = defineMessages({
id: 'content.enabled-for.label', id: 'content.enabled-for.label',
defaultMessage: 'Enabled for', defaultMessage: 'Enabled for',
}, },
enabledForDescription: {
id: 'content.enabled-for.description',
defaultMessage:
'Choose whether this content runs on the server, is sent to players, or is disabled in both places.',
},
pleaseWait: { pleaseWait: {
id: 'content.enabled-for.please-wait', id: 'content.enabled-for.please-wait',
defaultMessage: 'Please wait', defaultMessage: 'Please wait',
@@ -577,7 +583,8 @@ defineExpose({ show, showLoading, hide, getState, restore, updateItem, setItems
<div v-else class="@container flex-1 min-h-0 flex flex-col"> <div v-else class="@container flex-1 min-h-0 flex flex-col">
<div <div
class="flex h-12 shrink-0 items-center justify-between gap-4 border-0 border-b border-solid border-surface-4 bg-surface-3 px-3" class="flex h-12 shrink-0 items-center border-0 border-b border-solid border-surface-4 bg-surface-3 px-3"
:class="props.enableEnabledFor ? 'gap-2' : 'justify-between gap-4'"
> >
<div <div
class="flex min-w-0 items-center gap-4" class="flex min-w-0 items-center gap-4"
@@ -603,18 +610,23 @@ defineExpose({ show, showLoading, hide, getState, restore, updateItem, setItems
</div> </div>
<div <div
v-if="props.enableEnabledFor" v-if="props.enableEnabledFor"
class="hidden w-[200px] shrink-0 @[800px]:flex" class="hidden w-[200px] shrink-0 items-center gap-1.5 @[800px]:flex"
> >
<span class="font-semibold text-secondary">{{ <span class="font-semibold text-secondary">{{
formatMessage(messages.enabledFor) formatMessage(messages.enabledFor)
}}</span> }}</span>
<UnknownIcon
v-tooltip="formatMessage(messages.enabledForDescription)"
class="size-4 cursor-help text-secondary"
tabindex="0"
/>
</div> </div>
<div <div
class="hidden @[800px]:flex" class="hidden @[800px]:flex"
:class=" :class="
showTableActions showTableActions
? props.enableEnabledFor ? props.enableEnabledFor
? 'w-[250px] min-w-0 shrink-0' ? 'min-w-0 flex-1'
: 'flex-1 min-w-0' : 'flex-1 min-w-0'
: 'flex-1' : 'flex-1'
" "
@@ -14,7 +14,6 @@ import {
OrganizationIcon, OrganizationIcon,
RefreshCwIcon, RefreshCwIcon,
SearchIcon, SearchIcon,
ServerIcon,
ShareIcon, ShareIcon,
TextCursorInputIcon, TextCursorInputIcon,
TrashIcon, TrashIcon,
@@ -111,14 +110,6 @@ const messages = defineMessages({
id: 'content.page-layout.sort.date-added-oldest', id: 'content.page-layout.sort.date-added-oldest',
defaultMessage: 'Oldest first', defaultMessage: 'Oldest first',
}, },
sortEnabledForDescending: {
id: 'content.page-layout.sort.enabled-for-descending',
defaultMessage: 'Enabled for (most first)',
},
sortEnabledForAscending: {
id: 'content.page-layout.sort.enabled-for-ascending',
defaultMessage: 'Enabled for (least first)',
},
filter: { filter: {
id: 'content.page-layout.filter.add', id: 'content.page-layout.filter.add',
defaultMessage: 'Filter', defaultMessage: 'Filter',
@@ -193,20 +184,24 @@ type SortMode =
| 'alphabetical-desc' | 'alphabetical-desc'
| 'date-added-newest' | 'date-added-newest'
| 'date-added-oldest' | 'date-added-oldest'
| 'enabled-for-desc' const defaultSortMode: SortMode = 'alphabetical-asc'
| 'enabled-for-asc'
const defaultSortMode: SortMode = ctx.setEnabledFor ? 'enabled-for-desc' : 'alphabetical-asc'
const sortMode = ctx.filterPersistKey const sortMode = ctx.filterPersistKey
? useSessionStorage<SortMode>(`content-sort:${ctx.filterPersistKey}`, defaultSortMode) ? useSessionStorage<SortMode>(`content-sort:${ctx.filterPersistKey}`, defaultSortMode)
: ref<SortMode>(defaultSortMode) : ref<SortMode>(defaultSortMode)
const validSortModes: readonly string[] = [
'alphabetical-asc',
'alphabetical-desc',
'date-added-newest',
'date-added-oldest',
]
if (!validSortModes.includes(sortMode.value)) sortMode.value = defaultSortMode
const sortLabels: Record<SortMode, () => string> = { const sortLabels: Record<SortMode, () => string> = {
'alphabetical-asc': () => formatMessage(messages.sortAlphabeticalAscending), 'alphabetical-asc': () => formatMessage(messages.sortAlphabeticalAscending),
'alphabetical-desc': () => formatMessage(messages.sortAlphabeticalDescending), 'alphabetical-desc': () => formatMessage(messages.sortAlphabeticalDescending),
'date-added-newest': () => formatMessage(messages.sortDateAddedNewest), 'date-added-newest': () => formatMessage(messages.sortDateAddedNewest),
'date-added-oldest': () => formatMessage(messages.sortDateAddedOldest), 'date-added-oldest': () => formatMessage(messages.sortDateAddedOldest),
'enabled-for-desc': () => formatMessage(messages.sortEnabledForDescending),
'enabled-for-asc': () => formatMessage(messages.sortEnabledForAscending),
} }
const sortOptions = computed<ButtonMenuOption[]>(() => { const sortOptions = computed<ButtonMenuOption[]>(() => {
@@ -237,27 +232,9 @@ const sortOptions = computed<ButtonMenuOption[]>(() => {
}, },
] ]
if (ctx.setEnabledFor) {
options.push(
{
id: 'enabled-for-desc',
label: formatMessage(messages.sortEnabledForDescending),
action: () => (sortMode.value = 'enabled-for-desc'),
},
{
id: 'enabled-for-asc',
label: formatMessage(messages.sortEnabledForAscending),
action: () => (sortMode.value = 'enabled-for-asc'),
},
)
}
return options return options
}) })
function getEnabledForRank(item: ContentItem) {
return Number(item.enabledFor?.server) * 2 + Number(item.enabledFor?.player)
}
const sortedItems = computed(() => { const sortedItems = computed(() => {
const items = [...ctx.items.value] const items = [...ctx.items.value]
switch (sortMode.value) { switch (sortMode.value) {
@@ -282,16 +259,6 @@ const sortedItems = computed(() => {
const dateB = b.date_added ?? '' const dateB = b.date_added ?? ''
return dateA.localeCompare(dateB) || a.file_name.localeCompare(b.file_name) return dateA.localeCompare(dateB) || a.file_name.localeCompare(b.file_name)
}) })
case 'enabled-for-desc':
return items.sort(
(a, b) =>
getEnabledForRank(b) - getEnabledForRank(a) || a.file_name.localeCompare(b.file_name),
)
case 'enabled-for-asc':
return items.sort(
(a, b) =>
getEnabledForRank(a) - getEnabledForRank(b) || a.file_name.localeCompare(b.file_name),
)
default: default:
return items.sort((a, b) => { return items.sort((a, b) => {
const nameA = a.project?.title ?? a.file_name const nameA = a.project?.title ?? a.file_name
@@ -885,10 +852,6 @@ async function handleSetEnabledForById(
} }
} }
function handleEnabledForSort(direction: 'asc' | 'desc') {
sortMode.value = direction === 'asc' ? 'enabled-for-asc' : 'enabled-for-desc'
}
// Bulk updating // Bulk updating
const confirmBulkUpdateModal = ref<InstanceType<typeof ConfirmBulkUpdateModal>>() const confirmBulkUpdateModal = ref<InstanceType<typeof ConfirmBulkUpdateModal>>()
const pendingBulkUpdateItems = ref<ContentItem[]>([]) const pendingBulkUpdateItems = ref<ContentItem[]>([])
@@ -1090,7 +1053,6 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
<ArrowUpZAIcon v-if="sortMode === 'alphabetical-desc'" /> <ArrowUpZAIcon v-if="sortMode === 'alphabetical-desc'" />
<ClockArrowDownIcon v-else-if="sortMode === 'date-added-newest'" /> <ClockArrowDownIcon v-else-if="sortMode === 'date-added-newest'" />
<ClockArrowUpIcon v-else-if="sortMode === 'date-added-oldest'" /> <ClockArrowUpIcon v-else-if="sortMode === 'date-added-oldest'" />
<ServerIcon v-else-if="sortMode.startsWith('enabled-for')" />
<ArrowDownAZIcon v-else /> <ArrowDownAZIcon v-else />
{{ sortLabels[sortMode]() }} {{ sortLabels[sortMode]() }}
<DropdownIcon /> <DropdownIcon />
@@ -1306,16 +1268,8 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
:items="tableItems" :items="tableItems"
:show-selection="true" :show-selection="true"
:show-enabled-for-column="!!ctx.setEnabledFor" :show-enabled-for-column="!!ctx.setEnabledFor"
:enabled-for-sort-direction="
sortMode === 'enabled-for-asc'
? 'asc'
: sortMode === 'enabled-for-desc'
? 'desc'
: undefined
"
@update:enabled="handleToggleEnabledById" @update:enabled="handleToggleEnabledById"
@update:enabled-for="handleSetEnabledForById" @update:enabled-for="handleSetEnabledForById"
@sort-enabled-for="handleEnabledForSort"
@delete="handleDeleteById" @delete="handleDeleteById"
@update="handleUpdateById" @update="handleUpdateById"
@switch-version="handleSwitchVersionById" @switch-version="handleSwitchVersionById"
@@ -116,11 +116,11 @@ const messages = defineMessages({
}, },
lockEnvironment: { lockEnvironment: {
id: 'hosting.content.enabled-for.lock-environment', id: 'hosting.content.enabled-for.lock-environment',
defaultMessage: 'Lock detected environment', defaultMessage: 'Lock',
}, },
unlockEnvironment: { unlockEnvironment: {
id: 'hosting.content.enabled-for.unlock-environment', id: 'hosting.content.enabled-for.unlock-environment',
defaultMessage: 'Unlock environment controls', defaultMessage: 'Unlock',
}, },
}) })