fix: final content tab qa (#5611)
* fix: queued admonition always showing * fix: dont apply grayscale to checkbox in content card item * fix: actual stable id for disable/enable/bulk state * fix: vue-router resolve workaround * fix: show disable/enable btns same time * fix: remove mr-2 on toggle * fix: type errors + add ModpackAlreadyInstalledModal * fix: bulk actions + overflow menu hitting ad container * fix: responsiveness of ContentSelectionBar * feat: better backup naming for inline backups + sorting fixes * fix: lint * fix: typo
This commit is contained in:
@@ -1,11 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { onUnmounted, watch } from 'vue'
|
||||
import { onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
shown: boolean
|
||||
ariaLabel?: string
|
||||
}>()
|
||||
|
||||
const toolbarEl = ref<HTMLElement | null>(null)
|
||||
const compact = ref(false)
|
||||
|
||||
function checkCompact() {
|
||||
const el = toolbarEl.value
|
||||
if (!el) return
|
||||
|
||||
const clone = el.cloneNode(true) as HTMLElement
|
||||
clone.classList.remove('bar-compact')
|
||||
clone.style.position = 'absolute'
|
||||
clone.style.visibility = 'hidden'
|
||||
clone.style.pointerEvents = 'none'
|
||||
clone.style.width = `${el.offsetWidth}px`
|
||||
|
||||
el.parentElement!.appendChild(clone)
|
||||
const needsCompact = clone.offsetHeight > 70
|
||||
clone.remove()
|
||||
|
||||
compact.value = needsCompact
|
||||
}
|
||||
|
||||
let observer: ResizeObserver | null = null
|
||||
|
||||
watch(
|
||||
toolbarEl,
|
||||
(el) => {
|
||||
observer?.disconnect()
|
||||
if (!el) return
|
||||
observer = new ResizeObserver(() => {
|
||||
checkCompact()
|
||||
})
|
||||
observer.observe(el.parentElement!)
|
||||
checkCompact()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.shown,
|
||||
(shown) => {
|
||||
@@ -15,6 +52,7 @@ watch(
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
observer?.disconnect()
|
||||
document?.body.classList.remove('floating-action-bar-shown')
|
||||
})
|
||||
</script>
|
||||
@@ -27,9 +65,11 @@ onUnmounted(() => {
|
||||
aria-live="polite"
|
||||
>
|
||||
<div
|
||||
ref="toolbarEl"
|
||||
role="toolbar"
|
||||
:aria-label="ariaLabel"
|
||||
class="relative overflow-clip flex items-center gap-2 rounded-[20px] bg-surface-3 border border-surface-5 border-solid mx-auto max-w-[60vw] px-4 py-3 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 }"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -81,4 +121,12 @@ onUnmounted(() => {
|
||||
.intercom-lightweight-app-launcher {
|
||||
z-index: 9 !important;
|
||||
}
|
||||
|
||||
.bar-compact .bar-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bar-compact .cq-show-icon {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -100,15 +100,17 @@
|
||||
|
||||
<InlineBackupCreator
|
||||
v-if="ctx.flowType === 'reset-server'"
|
||||
backup-name="Before reinstall"
|
||||
ref="backupCreator"
|
||||
backup-name="Before reset server"
|
||||
hide-shift-click-hint
|
||||
@update:buttons-disabled="ctx.isBackingUp.value = $event"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { EyeIcon, EyeOffIcon, SettingsIcon } from '@modrinth/assets'
|
||||
import { computed, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { useDebugLogger } from '#ui/composables/debug-logger'
|
||||
|
||||
@@ -125,6 +127,11 @@ import { capitalize } from '../shared'
|
||||
|
||||
const debug = useDebugLogger('FinalConfigStage')
|
||||
const ctx = injectCreationFlowContext()
|
||||
|
||||
const backupCreator = ref<InstanceType<typeof InlineBackupCreator> | null>(null)
|
||||
watch(backupCreator, (creator) => {
|
||||
ctx.cancelBackup.value = creator?.cancelBackup ?? null
|
||||
})
|
||||
const {
|
||||
worldName,
|
||||
gamemode,
|
||||
|
||||
@@ -115,6 +115,10 @@ export interface CreationFlowContextValue {
|
||||
// Loading state (set when finish() is called, cleared on reset)
|
||||
loading: Ref<boolean>
|
||||
|
||||
// Backup state (set by InlineBackupCreator in reset-server flow)
|
||||
isBackingUp: Ref<boolean>
|
||||
cancelBackup: Ref<(() => void) | null>
|
||||
|
||||
// Modal
|
||||
modal: ShallowRef<ComponentExposed<typeof MultiStageModal> | null>
|
||||
stageConfigs: StageConfigInput<CreationFlowContextValue>[]
|
||||
@@ -240,6 +244,8 @@ export function createCreationFlowContext(
|
||||
|
||||
const hardReset = ref(isInitialSetup)
|
||||
const loading = ref(false)
|
||||
const isBackingUp = ref(false)
|
||||
const cancelBackup = ref<(() => void) | null>(null)
|
||||
|
||||
// hideLoaderChips: hides the entire loader chips section (only for vanilla world type in world/server flows)
|
||||
const hideLoaderChips = computed(() => setupType.value === 'vanilla')
|
||||
@@ -292,6 +298,8 @@ export function createCreationFlowContext(
|
||||
|
||||
hardReset.value = isInitialSetup
|
||||
loading.value = false
|
||||
isBackingUp.value = false
|
||||
cancelBackup.value = null
|
||||
}
|
||||
|
||||
function setSetupType(type: SetupType) {
|
||||
@@ -401,6 +409,8 @@ export function createCreationFlowContext(
|
||||
importSearchQuery,
|
||||
hardReset,
|
||||
loading,
|
||||
isBackingUp,
|
||||
cancelBackup,
|
||||
modal,
|
||||
stageConfigs: resolvedStageConfigs,
|
||||
onBack,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:context="ctx"
|
||||
:fade="fade"
|
||||
disable-progress
|
||||
@hide="$emit('hide')"
|
||||
@hide="handleHide"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -89,5 +89,10 @@ function hide() {
|
||||
modal.value?.hide()
|
||||
}
|
||||
|
||||
function handleHide() {
|
||||
ctx.cancelBackup.value?.()
|
||||
emit('hide')
|
||||
}
|
||||
|
||||
defineExpose({ show, hide, ctx })
|
||||
</script>
|
||||
|
||||
@@ -44,7 +44,7 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
|
||||
icon: isFinish ? PlusIcon : RightArrowIcon,
|
||||
iconPosition: isFinish ? ('before' as const) : ('after' as const),
|
||||
color: isReset ? ('red' as const) : isFinish ? ('brand' as const) : undefined,
|
||||
disabled: isForwardBlocked(ctx),
|
||||
disabled: isForwardBlocked(ctx) || ctx.isBackingUp.value,
|
||||
loading: isFinish && ctx.loading.value,
|
||||
onClick: () => {
|
||||
if (isFinish) {
|
||||
|
||||
@@ -109,7 +109,7 @@ const description = computed(() => {
|
||||
const messages = defineMessages({
|
||||
fallbackName: {
|
||||
id: 'servers.backups.admonition.fallback-name',
|
||||
defaultMessage: 'your backup',
|
||||
defaultMessage: 'Your backup',
|
||||
},
|
||||
backupQueuedTitle: {
|
||||
id: 'servers.backups.admonition.backup-queued.title',
|
||||
|
||||
@@ -84,10 +84,10 @@ const admonitions = computed<AdmonitionEntry[]>(() => {
|
||||
// 1. Active WS entries (real-time progress from backupsState)
|
||||
for (const [id, entry] of backupsState.entries()) {
|
||||
const backup = findBackup(id)
|
||||
seenIds.add(id)
|
||||
if (entry.create && entry.create.state === 'ongoing') {
|
||||
const key = `${id}:create`
|
||||
if (!dismissedIds.has(key)) {
|
||||
seenIds.add(id)
|
||||
result.push({
|
||||
key,
|
||||
backupId: id,
|
||||
@@ -102,7 +102,6 @@ const admonitions = computed<AdmonitionEntry[]>(() => {
|
||||
if (entry.restore && entry.restore.state === 'ongoing') {
|
||||
const key = `${id}:restore`
|
||||
if (!dismissedIds.has(key)) {
|
||||
seenIds.add(id)
|
||||
result.push({
|
||||
key,
|
||||
backupId: id,
|
||||
|
||||
@@ -178,10 +178,10 @@ const calculateMenuPosition = () => {
|
||||
top = Math.max(margin, window.innerHeight - menuHeight - margin)
|
||||
}
|
||||
|
||||
if (triggerRect.left + menuWidth + margin <= window.innerWidth) {
|
||||
left = triggerRect.left
|
||||
} else if (triggerRect.right - menuWidth - margin >= 0) {
|
||||
if (triggerRect.right - menuWidth >= margin) {
|
||||
left = triggerRect.right - menuWidth
|
||||
} else if (triggerRect.left + menuWidth + margin <= window.innerWidth) {
|
||||
left = triggerRect.left
|
||||
} else {
|
||||
left = Math.max(margin, window.innerWidth - menuWidth - margin)
|
||||
}
|
||||
|
||||
@@ -87,22 +87,23 @@ const deleteHovered = ref(false)
|
||||
:class="{ 'opacity-50': disabled }"
|
||||
>
|
||||
<div
|
||||
class="flex min-w-0 items-center gap-4 transition-[filter,opacity] duration-200"
|
||||
:class="[
|
||||
hideActions ? 'flex-1' : 'flex-1 @[800px]:w-[350px] @[800px]:shrink-0 @[800px]:flex-none',
|
||||
enabled === false && !disabled ? 'grayscale opacity-50' : '',
|
||||
]"
|
||||
class="flex min-w-0 items-center gap-4"
|
||||
:class="
|
||||
hideActions ? 'flex-1' : 'flex-1 @[800px]:w-[350px] @[800px]:shrink-0 @[800px]:flex-none'
|
||||
"
|
||||
>
|
||||
<Checkbox
|
||||
v-if="showCheckbox"
|
||||
:model-value="selected ?? false"
|
||||
:disabled="disabled"
|
||||
:aria-label="`Select ${project.title}`"
|
||||
class="shrink-0"
|
||||
@update:model-value="selected = $event"
|
||||
/>
|
||||
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<div
|
||||
class="flex min-w-0 items-center gap-3 transition-[filter,opacity] duration-200"
|
||||
:class="enabled === false && !disabled ? 'grayscale opacity-50' : ''"
|
||||
>
|
||||
<div
|
||||
v-tooltip="installing ? formatMessage(commonMessages.installingLabel) : undefined"
|
||||
class="relative shrink-0"
|
||||
@@ -256,7 +257,7 @@ const deleteHovered = ref(false)
|
||||
:model-value="enabled"
|
||||
:disabled="disabled"
|
||||
:aria-label="project.title"
|
||||
class="mr-2 my-auto"
|
||||
class="my-auto"
|
||||
@update:model-value="(val) => emit('update:enabled', val as boolean)"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { PowerIcon, PowerOffIcon } from '@modrinth/assets'
|
||||
import { PowerIcon, PowerOffIcon, XIcon } from '@modrinth/assets'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
@@ -61,6 +61,14 @@ const messages = defineMessages({
|
||||
id: 'content.selection-bar.bulk.deleting-waiting',
|
||||
defaultMessage: 'Deleting {contentType}...',
|
||||
},
|
||||
allAlreadyEnabled: {
|
||||
id: 'content.selection-bar.all-already-enabled',
|
||||
defaultMessage: 'All selected content is already enabled',
|
||||
},
|
||||
allAlreadyDisabled: {
|
||||
id: 'content.selection-bar.all-already-disabled',
|
||||
defaultMessage: 'All selected content is already disabled',
|
||||
},
|
||||
})
|
||||
|
||||
interface Props {
|
||||
@@ -95,6 +103,7 @@ const emit = defineEmits<{
|
||||
const shown = computed(() => props.selectedItems.length > 0 || props.isBulkOperating)
|
||||
|
||||
const allDisabled = computed(() => props.selectedItems.every((m) => !m.enabled))
|
||||
const allEnabled = computed(() => props.selectedItems.every((m) => m.enabled))
|
||||
|
||||
const selectedCountText = computed(() => {
|
||||
const count = props.selectedItems.length || props.bulkTotal
|
||||
@@ -135,12 +144,14 @@ const bulkProgressMessage = computed(() => {
|
||||
<div class="mx-1 h-6 w-px bg-surface-5" />
|
||||
<ButtonStyled type="transparent">
|
||||
<button
|
||||
v-tooltip="formatMessage(commonMessages.clearButton)"
|
||||
class="!text-primary"
|
||||
:disabled="isBulkOperating"
|
||||
:class="{ 'opacity-60 pointer-events-none': isBulkOperating }"
|
||||
@click="emit('clear')"
|
||||
>
|
||||
{{ formatMessage(commonMessages.clearButton) }}
|
||||
<XIcon class="hidden cq-show-icon" />
|
||||
<span class="bar-label">{{ formatMessage(commonMessages.clearButton) }}</span>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -148,16 +159,30 @@ const bulkProgressMessage = computed(() => {
|
||||
<div v-if="!isBulkOperating" class="ml-auto flex items-center gap-0.5">
|
||||
<slot name="actions" />
|
||||
|
||||
<ButtonStyled v-if="allDisabled" type="transparent">
|
||||
<button :disabled="isBusy" @click="emit('enable')">
|
||||
<ButtonStyled type="transparent">
|
||||
<button
|
||||
v-tooltip="
|
||||
allEnabled ? formatMessage(messages.allAlreadyEnabled) : formatMessage(messages.enable)
|
||||
"
|
||||
:disabled="isBusy || allEnabled"
|
||||
@click="emit('enable')"
|
||||
>
|
||||
<PowerIcon />
|
||||
{{ formatMessage(messages.enable) }}
|
||||
<span class="bar-label">{{ formatMessage(messages.enable) }}</span>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-else type="transparent">
|
||||
<button :disabled="isBusy" @click="emit('disable')">
|
||||
<ButtonStyled type="transparent">
|
||||
<button
|
||||
v-tooltip="
|
||||
allDisabled
|
||||
? formatMessage(messages.allAlreadyDisabled)
|
||||
: formatMessage(messages.disable)
|
||||
"
|
||||
:disabled="isBusy || allDisabled"
|
||||
@click="emit('disable')"
|
||||
>
|
||||
<PowerOffIcon />
|
||||
{{ formatMessage(messages.disable) }}
|
||||
<span class="bar-label">{{ formatMessage(messages.disable) }}</span>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</Admonition>
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
backup-name="Before bulk update"
|
||||
:backup-name="backupTip ? `Before bulk update (${backupTip})` : 'Before bulk update'"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
</div>
|
||||
@@ -73,6 +73,7 @@ const messages = defineMessages({
|
||||
defineProps<{
|
||||
count: number
|
||||
server?: boolean
|
||||
backupTip?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</Admonition>
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
backup-name="Before deletion"
|
||||
:backup-name="backupTip ? `Before deletion (${backupTip})` : 'Before deletion'"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
</div>
|
||||
@@ -78,9 +78,11 @@ withDefaults(
|
||||
count: number
|
||||
itemType: string
|
||||
variant?: 'instance' | 'server'
|
||||
backupTip?: string
|
||||
}>(),
|
||||
{
|
||||
variant: 'instance',
|
||||
backupTip: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</Admonition>
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
:backup-name="downgrade ? 'Before modpack downgrade' : 'Before modpack update'"
|
||||
:backup-name="backupName"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
</div>
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import Admonition from '#ui/components/base/Admonition.vue'
|
||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||
@@ -55,13 +55,21 @@ import { commonMessages } from '#ui/utils/common-messages'
|
||||
|
||||
import InlineBackupCreator from './InlineBackupCreator.vue'
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
downgrade?: boolean
|
||||
server?: boolean
|
||||
backupTip?: string
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const backupName = computed(() => {
|
||||
const action = props.downgrade ? 'downgrade' : 'update'
|
||||
return props.backupTip
|
||||
? `Before modpack ${action} (${props.backupTip})`
|
||||
: `Before modpack ${action}`
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
header: {
|
||||
id: 'content.confirm-modpack-update.header',
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</Admonition>
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
backup-name="Before reinstall"
|
||||
:backup-name="backupTip ? `Before reinstall (${backupTip})` : 'Before reinstall'"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
</div>
|
||||
@@ -72,6 +72,7 @@ const messages = defineMessages({
|
||||
|
||||
defineProps<{
|
||||
server?: boolean
|
||||
backupTip?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</Admonition>
|
||||
<InlineBackupCreator
|
||||
ref="backupCreator"
|
||||
backup-name="Before unlink"
|
||||
:backup-name="backupTip ? `Before unlink (${backupTip})` : 'Before unlink'"
|
||||
@update:buttons-disabled="buttonsDisabled = $event"
|
||||
/>
|
||||
</div>
|
||||
@@ -50,6 +50,7 @@ import InlineBackupCreator from './InlineBackupCreator.vue'
|
||||
|
||||
defineProps<{
|
||||
server?: boolean
|
||||
backupTip?: string
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -3,19 +3,16 @@ import { computed, ref, watch } from 'vue'
|
||||
|
||||
import type { ContentItem } from '../types'
|
||||
|
||||
export function useContentSelection(
|
||||
items: Ref<ContentItem[]>,
|
||||
getItemId: (item: ContentItem) => string,
|
||||
) {
|
||||
export function useContentSelection(items: Ref<ContentItem[]>) {
|
||||
const selectedIds = ref<string[]>([])
|
||||
|
||||
const selectedItems = computed(() =>
|
||||
items.value.filter((item) => selectedIds.value.includes(getItemId(item))),
|
||||
items.value.filter((item) => selectedIds.value.includes(item.id)),
|
||||
)
|
||||
|
||||
watch(items, (newItems) => {
|
||||
if (selectedIds.value.length === 0) return
|
||||
const validIds = new Set(newItems.map(getItemId))
|
||||
const validIds = new Set(newItems.map((item) => item.id))
|
||||
const pruned = selectedIds.value.filter((id) => validIds.has(id))
|
||||
if (pruned.length !== selectedIds.value.length) {
|
||||
selectedIds.value = pruned
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ArrowDownAZIcon,
|
||||
ArrowDownZAIcon,
|
||||
ArrowUpZAIcon,
|
||||
ClockArrowDownIcon,
|
||||
ClockArrowUpIcon,
|
||||
CodeIcon,
|
||||
@@ -171,8 +171,8 @@ const sortLabels: Record<SortMode, () => string> = {
|
||||
function cycleSortMode() {
|
||||
const modes: SortMode[] = [
|
||||
'alphabetical-asc',
|
||||
'date-added-newest',
|
||||
'alphabetical-desc',
|
||||
'date-added-newest',
|
||||
'date-added-oldest',
|
||||
]
|
||||
const idx = modes.indexOf(sortMode.value)
|
||||
@@ -235,7 +235,6 @@ const { selectedFilters, filterOptions, toggleFilter, applyFilters } = useConten
|
||||
|
||||
const { selectedIds, selectedItems, clearSelection, removeFromSelection } = useContentSelection(
|
||||
ctx.items,
|
||||
ctx.getItemId,
|
||||
)
|
||||
|
||||
const { isBulkOperating, bulkProgress, bulkTotal, bulkOperation, runBulk } = useBulkOperation()
|
||||
@@ -292,7 +291,7 @@ const pendingDeletionItems = ref<ContentItem[]>([])
|
||||
const confirmDeletionModal = ref<InstanceType<typeof ConfirmDeletionModal>>()
|
||||
|
||||
function handleDeleteById(id: string, event?: MouseEvent) {
|
||||
const item = ctx.items.value.find((i) => ctx.getItemId(i) === id)
|
||||
const item = ctx.items.value.find((i) => i.id === id)
|
||||
if (item) {
|
||||
pendingDeletionItems.value = [item]
|
||||
if (event?.shiftKey) {
|
||||
@@ -334,7 +333,7 @@ async function confirmDelete() {
|
||||
|
||||
if (itemsToDelete.length === 1) {
|
||||
const item = itemsToDelete[0]
|
||||
const id = ctx.getItemId(item)
|
||||
const id = item.id
|
||||
markChanging(id)
|
||||
await ctx.deleteItem(item)
|
||||
removeFromSelection(id)
|
||||
@@ -347,14 +346,14 @@ async function confirmDelete() {
|
||||
itemsToDelete,
|
||||
async (item) => {
|
||||
await ctx.deleteItem(item)
|
||||
removeFromSelection(ctx.getItemId(item))
|
||||
removeFromSelection(item.id)
|
||||
},
|
||||
{ onComplete: clearSelection },
|
||||
)
|
||||
}
|
||||
|
||||
async function handleToggleEnabledById(id: string, _value: boolean) {
|
||||
const item = ctx.items.value.find((i) => ctx.getItemId(i) === id)
|
||||
const item = ctx.items.value.find((i) => i.id === id)
|
||||
if (!item) return
|
||||
markChanging(id)
|
||||
try {
|
||||
@@ -647,7 +646,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
"
|
||||
@click="cycleSortMode"
|
||||
>
|
||||
<ArrowDownZAIcon v-if="sortMode === 'alphabetical-desc'" /><ClockArrowDownIcon
|
||||
<ArrowUpZAIcon v-if="sortMode === 'alphabetical-desc'" /><ClockArrowDownIcon
|
||||
v-else-if="sortMode === 'date-added-newest'"
|
||||
/><ClockArrowUpIcon
|
||||
v-else-if="sortMode === 'date-added-oldest'"
|
||||
@@ -667,7 +666,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
"
|
||||
@click="cycleSortMode"
|
||||
>
|
||||
<ArrowDownZAIcon v-if="sortMode === 'alphabetical-desc'" /><ClockArrowDownIcon
|
||||
<ArrowUpZAIcon v-if="sortMode === 'alphabetical-desc'" /><ClockArrowDownIcon
|
||||
v-else-if="sortMode === 'date-added-newest'"
|
||||
/><ClockArrowUpIcon
|
||||
v-else-if="sortMode === 'date-added-oldest'"
|
||||
@@ -790,9 +789,13 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
color-fill="text"
|
||||
hover-color-fill="background"
|
||||
>
|
||||
<button :disabled="ctx.isBusy.value" @click="promptUpdateSelected">
|
||||
<button
|
||||
v-tooltip="formatMessage(commonMessages.updateButton)"
|
||||
:disabled="ctx.isBusy.value"
|
||||
@click="promptUpdateSelected"
|
||||
>
|
||||
<DownloadIcon />
|
||||
{{ formatMessage(commonMessages.updateButton) }}
|
||||
<span class="bar-label">{{ formatMessage(commonMessages.updateButton) }}</span>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
|
||||
@@ -818,7 +821,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
]"
|
||||
>
|
||||
<ShareIcon />
|
||||
{{ formatMessage(messages.share) }}
|
||||
<span class="bar-label">{{ formatMessage(messages.share) }}</span>
|
||||
<DropdownIcon />
|
||||
<template #share-names>
|
||||
<TextCursorInputIcon />
|
||||
@@ -849,9 +852,13 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
color-fill="text"
|
||||
hover-color-fill="background"
|
||||
>
|
||||
<button :disabled="ctx.isBusy.value" @click="showBulkDeleteModal">
|
||||
<button
|
||||
v-tooltip="formatMessage(commonMessages.deleteLabel)"
|
||||
:disabled="ctx.isBusy.value"
|
||||
@click="showBulkDeleteModal"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(commonMessages.deleteLabel) }}
|
||||
<span class="bar-label">{{ formatMessage(commonMessages.deleteLabel) }}</span>
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
@@ -862,6 +869,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
:count="pendingDeletionItems.length"
|
||||
:item-type="ctx.contentTypeLabel.value"
|
||||
:variant="ctx.deletionContext ?? 'instance'"
|
||||
:backup-tip="pendingDeletionItems.map((i) => i.project.title).join(', ')"
|
||||
@delete="confirmDelete"
|
||||
/>
|
||||
<ConfirmBulkUpdateModal
|
||||
@@ -875,6 +883,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
v-if="ctx.unlinkModpack"
|
||||
ref="confirmUnlinkModal"
|
||||
:server="ctx.deletionContext === 'server'"
|
||||
:backup-tip="ctx.modpack.value?.project.title"
|
||||
@unlink="ctx.unlinkModpack!()"
|
||||
/>
|
||||
|
||||
|
||||
@@ -51,8 +51,7 @@ export interface ContentManagerContext {
|
||||
disableAddContent?: Ref<boolean> | ComputedRef<boolean>
|
||||
disableAddContentTooltip?: string
|
||||
|
||||
// Identity & labelling
|
||||
getItemId: (item: ContentItem) => string
|
||||
// Labelling
|
||||
contentTypeLabel: Ref<string> | ComputedRef<string>
|
||||
|
||||
// Core actions
|
||||
|
||||
@@ -44,9 +44,9 @@ export interface ContentItem extends Omit<
|
||||
ContentCardTableItem,
|
||||
'id' | 'projectLink' | 'disabled' | 'overflowOptions'
|
||||
> {
|
||||
id: string
|
||||
file_name: string
|
||||
file_path?: string
|
||||
hash?: string
|
||||
size?: number
|
||||
project_type: string
|
||||
has_update: boolean
|
||||
|
||||
@@ -679,6 +679,9 @@ const messages = defineMessages({
|
||||
ref="modpackUpdateModal"
|
||||
:downgrade="isUpdateDowngrade"
|
||||
:server="ctx.isServer"
|
||||
:backup-tip="
|
||||
[ctx.modpack.value?.title, pendingUpdateVersion?.version_number].filter(Boolean).join(' ')
|
||||
"
|
||||
@confirm="handleModpackUpdateConfirm"
|
||||
@cancel="handleModpackUpdateCancel"
|
||||
/>
|
||||
@@ -686,9 +689,15 @@ const messages = defineMessages({
|
||||
<ConfirmReinstallModal
|
||||
ref="reinstallModal"
|
||||
:server="ctx.isServer"
|
||||
:backup-tip="ctx.modpack.value?.title"
|
||||
@reinstall="handleReinstall"
|
||||
/>
|
||||
<ConfirmUnlinkModal ref="unlinkModal" :server="ctx.isServer" @unlink="handleUnlink" />
|
||||
<ConfirmUnlinkModal
|
||||
ref="unlinkModal"
|
||||
:server="ctx.isServer"
|
||||
:backup-tip="ctx.modpack.value?.title"
|
||||
@unlink="handleUnlink"
|
||||
/>
|
||||
|
||||
<ContentDiffModal
|
||||
v-if="form.pendingPreview.value"
|
||||
|
||||
@@ -487,6 +487,7 @@ function addonToContentItem(addon: Archon.Content.v1.Addon): ContentItem {
|
||||
link: `/${addon.owner.type}/${addon.owner.id}`,
|
||||
}
|
||||
: undefined,
|
||||
id: addon.id,
|
||||
enabled: !addon.disabled,
|
||||
file_name: addon.filename,
|
||||
project_type: addon.kind,
|
||||
@@ -604,8 +605,8 @@ async function handleBulkUpdate(items: ContentItem[]) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUpdateItem(fileNameKey: string) {
|
||||
const item = contentItems.value.find((i) => i.file_name === fileNameKey)
|
||||
async function handleUpdateItem(id: string) {
|
||||
const item = contentItems.value.find((i) => i.id === id)
|
||||
if (!item?.has_update || !item.project?.id || !item.version?.id) return
|
||||
|
||||
updatingModpack.value = false
|
||||
@@ -872,7 +873,6 @@ provideContentManager({
|
||||
})
|
||||
return filteredReasons.length > 0 ? formatMessage(filteredReasons[0].reason) : null
|
||||
}),
|
||||
getItemId: (item) => item.file_path ?? item.file_name,
|
||||
contentTypeLabel: type,
|
||||
toggleEnabled: handleToggleEnabled,
|
||||
deleteItem: handleDeleteItem,
|
||||
@@ -898,7 +898,7 @@ provideContentManager({
|
||||
mapToTableItem: (item) => {
|
||||
const projectType = item.project_type ?? type.value
|
||||
return {
|
||||
id: item.file_path ?? item.file_name,
|
||||
id: item.id,
|
||||
project: item.project,
|
||||
projectLink: item.project?.id ? `/${projectType}/${item.project.id}` : undefined,
|
||||
version: item.version,
|
||||
@@ -962,6 +962,11 @@ provideContentManager({
|
||||
<ConfirmModpackUpdateModal
|
||||
ref="modpackUpdateModal"
|
||||
:downgrade="isModpackUpdateDowngrade"
|
||||
:backup-tip="
|
||||
[modpack?.project.title, pendingModpackUpdateVersion?.version_number]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
"
|
||||
server
|
||||
@confirm="handleModpackUpdateConfirm"
|
||||
@cancel="handleModpackUpdateCancel"
|
||||
|
||||
@@ -374,6 +374,12 @@
|
||||
"content.page-layout.uploading-files": {
|
||||
"defaultMessage": "Uploading files ({completed}/{total})"
|
||||
},
|
||||
"content.selection-bar.all-already-disabled": {
|
||||
"defaultMessage": "All selected content is already disabled"
|
||||
},
|
||||
"content.selection-bar.all-already-enabled": {
|
||||
"defaultMessage": "All selected content is already enabled"
|
||||
},
|
||||
"content.selection-bar.bulk.deleting": {
|
||||
"defaultMessage": "Deleting {progress}/{total} {contentType}..."
|
||||
},
|
||||
@@ -2151,7 +2157,7 @@
|
||||
"defaultMessage": "Creating backup"
|
||||
},
|
||||
"servers.backups.admonition.fallback-name": {
|
||||
"defaultMessage": "your backup"
|
||||
"defaultMessage": "Your backup"
|
||||
},
|
||||
"servers.backups.admonition.restore-failed.description": {
|
||||
"defaultMessage": "Something went wrong while restoring from {backupName}. Please try again or contact support if the issue continues."
|
||||
|
||||
Reference in New Issue
Block a user