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:
@@ -68,11 +68,11 @@ import ErrorModal from '@/components/ui/ErrorModal.vue'
|
|||||||
import FriendsList from '@/components/ui/friends/FriendsList.vue'
|
import FriendsList from '@/components/ui/friends/FriendsList.vue'
|
||||||
import AddServerToInstanceModal from '@/components/ui/install_flow/AddServerToInstanceModal.vue'
|
import AddServerToInstanceModal from '@/components/ui/install_flow/AddServerToInstanceModal.vue'
|
||||||
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
|
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
|
||||||
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
|
|
||||||
import MinecraftAuthErrorModal from '@/components/ui/minecraft-auth-error-modal/MinecraftAuthErrorModal.vue'
|
import MinecraftAuthErrorModal from '@/components/ui/minecraft-auth-error-modal/MinecraftAuthErrorModal.vue'
|
||||||
import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue'
|
import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue'
|
||||||
import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue'
|
import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue'
|
||||||
import InstallToPlayModal from '@/components/ui/modal/InstallToPlayModal.vue'
|
import InstallToPlayModal from '@/components/ui/modal/InstallToPlayModal.vue'
|
||||||
|
import ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlreadyInstalledModal.vue'
|
||||||
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
|
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
|
||||||
import NavButton from '@/components/ui/NavButton.vue'
|
import NavButton from '@/components/ui/NavButton.vue'
|
||||||
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
||||||
@@ -151,6 +151,9 @@ const {
|
|||||||
handleBrowseModpacks,
|
handleBrowseModpacks,
|
||||||
searchModpacks,
|
searchModpacks,
|
||||||
getProjectVersions,
|
getProjectVersions,
|
||||||
|
setModpackAlreadyInstalledModal,
|
||||||
|
handleModpackDuplicateCreateAnyway,
|
||||||
|
handleModpackDuplicateGoToInstance,
|
||||||
} = setupProviders(notificationManager)
|
} = setupProviders(notificationManager)
|
||||||
|
|
||||||
const news = ref([])
|
const news = ref([])
|
||||||
@@ -424,7 +427,9 @@ const {
|
|||||||
handleNavigate: handleContentInstallNavigate,
|
handleNavigate: handleContentInstallNavigate,
|
||||||
handleCancel: handleContentInstallCancel,
|
handleCancel: handleContentInstallCancel,
|
||||||
setContentInstallModal,
|
setContentInstallModal,
|
||||||
setInstallConfirmModal: setContentInstallConfirmModal,
|
setModpackAlreadyInstalledModal: setContentInstallModpackAlreadyInstalledModal,
|
||||||
|
handleModpackDuplicateCreateAnyway: handleContentInstallModpackDuplicateCreateAnyway,
|
||||||
|
handleModpackDuplicateGoToInstance: handleContentInstallModpackDuplicateGoToInstance,
|
||||||
setIncompatibilityWarningModal: setContentIncompatibilityWarningModal,
|
setIncompatibilityWarningModal: setContentIncompatibilityWarningModal,
|
||||||
} = contentInstall
|
} = contentInstall
|
||||||
|
|
||||||
@@ -438,8 +443,9 @@ const {
|
|||||||
} = serverInstall
|
} = serverInstall
|
||||||
|
|
||||||
const modInstallModal = ref()
|
const modInstallModal = ref()
|
||||||
|
const modpackAlreadyInstalledModal = ref()
|
||||||
|
const contentInstallModpackAlreadyInstalledModal = ref()
|
||||||
const addServerToInstanceModal = ref()
|
const addServerToInstanceModal = ref()
|
||||||
const installConfirmModal = ref()
|
|
||||||
const incompatibilityWarningModal = ref()
|
const incompatibilityWarningModal = ref()
|
||||||
const installToPlayModal = ref()
|
const installToPlayModal = ref()
|
||||||
const updateToPlayModal = ref()
|
const updateToPlayModal = ref()
|
||||||
@@ -519,8 +525,9 @@ onMounted(() => {
|
|||||||
error.setMinecraftAuthErrorModal(minecraftAuthErrorModal.value)
|
error.setMinecraftAuthErrorModal(minecraftAuthErrorModal.value)
|
||||||
|
|
||||||
setContentIncompatibilityWarningModal(incompatibilityWarningModal.value)
|
setContentIncompatibilityWarningModal(incompatibilityWarningModal.value)
|
||||||
setContentInstallConfirmModal(installConfirmModal.value)
|
|
||||||
setContentInstallModal(modInstallModal.value)
|
setContentInstallModal(modInstallModal.value)
|
||||||
|
setContentInstallModpackAlreadyInstalledModal(contentInstallModpackAlreadyInstalledModal.value)
|
||||||
|
setModpackAlreadyInstalledModal(modpackAlreadyInstalledModal.value)
|
||||||
setServerAddServerToInstanceModal(addServerToInstanceModal.value)
|
setServerAddServerToInstanceModal(addServerToInstanceModal.value)
|
||||||
setServerInstallToPlayModal(installToPlayModal.value)
|
setServerInstallToPlayModal(installToPlayModal.value)
|
||||||
setServerUpdateToPlayModal(updateToPlayModal.value)
|
setServerUpdateToPlayModal(updateToPlayModal.value)
|
||||||
@@ -1295,9 +1302,18 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
|||||||
@navigate="handleContentInstallNavigate"
|
@navigate="handleContentInstallNavigate"
|
||||||
@cancel="handleContentInstallCancel"
|
@cancel="handleContentInstallCancel"
|
||||||
/>
|
/>
|
||||||
|
<ModpackAlreadyInstalledModal
|
||||||
|
ref="modpackAlreadyInstalledModal"
|
||||||
|
@create-anyway="handleModpackDuplicateCreateAnyway"
|
||||||
|
@go-to-instance="handleModpackDuplicateGoToInstance"
|
||||||
|
/>
|
||||||
<AddServerToInstanceModal ref="addServerToInstanceModal" />
|
<AddServerToInstanceModal ref="addServerToInstanceModal" />
|
||||||
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
||||||
<InstallConfirmModal ref="installConfirmModal" />
|
<ModpackAlreadyInstalledModal
|
||||||
|
ref="contentInstallModpackAlreadyInstalledModal"
|
||||||
|
@create-anyway="handleContentInstallModpackDuplicateCreateAnyway"
|
||||||
|
@go-to-instance="handleContentInstallModpackDuplicateGoToInstance"
|
||||||
|
/>
|
||||||
<InstallToPlayModal ref="installToPlayModal" />
|
<InstallToPlayModal ref="installToPlayModal" />
|
||||||
<UpdateToPlayModal ref="updateToPlayModal" />
|
<UpdateToPlayModal ref="updateToPlayModal" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
|
||||||
import { Button, injectNotificationManager } from '@modrinth/ui'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
|
||||||
import { create_profile_and_install as pack_install } from '@/helpers/pack'
|
|
||||||
|
|
||||||
const { handleError } = injectNotificationManager()
|
|
||||||
|
|
||||||
const versionId = ref()
|
|
||||||
const project = ref()
|
|
||||||
const confirmModal = ref(null)
|
|
||||||
const installing = ref(false)
|
|
||||||
|
|
||||||
const onInstall = ref(() => {})
|
|
||||||
const onCreateInstance = ref(() => {})
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
show: (projectVal, versionIdVal, callback, createInstanceCallback) => {
|
|
||||||
project.value = projectVal
|
|
||||||
versionId.value = versionIdVal
|
|
||||||
installing.value = false
|
|
||||||
confirmModal.value.show()
|
|
||||||
|
|
||||||
onInstall.value = callback
|
|
||||||
onCreateInstance.value = createInstanceCallback
|
|
||||||
|
|
||||||
trackEvent('PackInstallStart')
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
async function install() {
|
|
||||||
installing.value = true
|
|
||||||
confirmModal.value.hide()
|
|
||||||
|
|
||||||
await pack_install(
|
|
||||||
project.value.id,
|
|
||||||
versionId.value,
|
|
||||||
project.value.title,
|
|
||||||
project.value.icon_url,
|
|
||||||
onCreateInstance.value,
|
|
||||||
).catch(handleError)
|
|
||||||
trackEvent('PackInstall', {
|
|
||||||
id: project.value.id,
|
|
||||||
version_id: versionId.value,
|
|
||||||
title: project.value.title,
|
|
||||||
source: 'ConfirmModal',
|
|
||||||
})
|
|
||||||
|
|
||||||
onInstall.value(versionId.value)
|
|
||||||
installing.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ModalWrapper ref="confirmModal" header="Are you sure?" :on-hide="onInstall">
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>You already have this modpack installed. Are you sure you want to install it again?</p>
|
|
||||||
<div class="input-group push-right">
|
|
||||||
<Button @click="() => $refs.confirmModal.hide()"><XIcon />Cancel</Button>
|
|
||||||
<Button color="primary" :disabled="installing" @click="install()"
|
|
||||||
><DownloadIcon /> {{ installing ? 'Installing' : 'Install' }}</Button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalWrapper>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.modal-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<NewModal ref="modal" :header="formatMessage(messages.header)" fade="warning" max-width="500px">
|
||||||
|
<Admonition type="warning" :header="formatMessage(messages.admonitionHeader)">
|
||||||
|
{{ formatMessage(messages.admonitionBody, { instanceName }) }}
|
||||||
|
</Admonition>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<div class="flex gap-2 justify-end">
|
||||||
|
<ButtonStyled type="outlined">
|
||||||
|
<button class="!border !border-surface-4" @click="handleGoToInstance">
|
||||||
|
<ExternalIcon />
|
||||||
|
{{ formatMessage(messages.goToInstance) }}
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled color="orange">
|
||||||
|
<button @click="handleCreateAnyway">
|
||||||
|
<PlusIcon />
|
||||||
|
{{ formatMessage(messages.createAnyway) }}
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NewModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ExternalIcon, PlusIcon } from '@modrinth/assets'
|
||||||
|
import { Admonition, ButtonStyled, defineMessages, NewModal, useVIntl } from '@modrinth/ui'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
header: {
|
||||||
|
id: 'app.instance.modpack-already-installed.header',
|
||||||
|
defaultMessage: 'Modpack already installed',
|
||||||
|
},
|
||||||
|
admonitionHeader: {
|
||||||
|
id: 'app.instance.modpack-already-installed.admonition-header',
|
||||||
|
defaultMessage: 'Duplicate modpack',
|
||||||
|
},
|
||||||
|
admonitionBody: {
|
||||||
|
id: 'app.instance.modpack-already-installed.admonition-body',
|
||||||
|
defaultMessage: 'This modpack is already installed in the "{instanceName}" instance.',
|
||||||
|
},
|
||||||
|
goToInstance: {
|
||||||
|
id: 'app.instance.modpack-already-installed.go-to-instance',
|
||||||
|
defaultMessage: 'Go to instance',
|
||||||
|
},
|
||||||
|
createAnyway: {
|
||||||
|
id: 'app.instance.modpack-already-installed.create-anyway',
|
||||||
|
defaultMessage: 'Create anyway',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'go-to-instance', instancePath: string): void
|
||||||
|
(e: 'create-anyway'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const modal = ref<InstanceType<typeof NewModal>>()
|
||||||
|
const instanceName = ref('')
|
||||||
|
const instancePath = ref('')
|
||||||
|
|
||||||
|
function show(name: string, path: string) {
|
||||||
|
instanceName.value = name
|
||||||
|
instancePath.value = path
|
||||||
|
modal.value?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGoToInstance() {
|
||||||
|
modal.value?.hide()
|
||||||
|
emit('go-to-instance', instancePath.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreateAnyway() {
|
||||||
|
modal.value?.hide()
|
||||||
|
emit('create-anyway')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
4
apps/app-frontend/src/helpers/types.d.ts
vendored
4
apps/app-frontend/src/helpers/types.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
import type { ModrinthId } from '@modrinth/utils'
|
import type { ModrinthId } from '@modrinth/utils'
|
||||||
|
|
||||||
type GameInstance = {
|
export type GameInstance = {
|
||||||
path: string
|
path: string
|
||||||
install_stage: InstallStage
|
install_stage: InstallStage
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ type LinkedData = {
|
|||||||
locked: boolean
|
locked: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstanceLoader = 'vanilla' | 'forge' | 'fabric' | 'quilt' | 'neoforge'
|
export type InstanceLoader = 'vanilla' | 'forge' | 'fabric' | 'quilt' | 'neoforge'
|
||||||
|
|
||||||
type ContentFile = {
|
type ContentFile = {
|
||||||
metadata?: {
|
metadata?: {
|
||||||
|
|||||||
@@ -41,6 +41,21 @@
|
|||||||
"app.instance.confirm-delete.header": {
|
"app.instance.confirm-delete.header": {
|
||||||
"message": "Delete instance"
|
"message": "Delete instance"
|
||||||
},
|
},
|
||||||
|
"app.instance.modpack-already-installed.admonition-body": {
|
||||||
|
"message": "This modpack is already installed in the \"{instanceName}\" instance."
|
||||||
|
},
|
||||||
|
"app.instance.modpack-already-installed.admonition-header": {
|
||||||
|
"message": "Duplicate modpack"
|
||||||
|
},
|
||||||
|
"app.instance.modpack-already-installed.create-anyway": {
|
||||||
|
"message": "Create anyway"
|
||||||
|
},
|
||||||
|
"app.instance.modpack-already-installed.go-to-instance": {
|
||||||
|
"message": "Go to instance"
|
||||||
|
},
|
||||||
|
"app.instance.modpack-already-installed.header": {
|
||||||
|
"message": "Modpack already installed"
|
||||||
|
},
|
||||||
"app.instance.mods.content-type-project": {
|
"app.instance.mods.content-type-project": {
|
||||||
"message": "project"
|
"message": "project"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
<ConfirmModpackUpdateModal
|
<ConfirmModpackUpdateModal
|
||||||
ref="modpackUpdateConfirmModal"
|
ref="modpackUpdateConfirmModal"
|
||||||
:downgrade="isModpackUpdateDowngrade"
|
:downgrade="isModpackUpdateDowngrade"
|
||||||
|
:backup-tip="
|
||||||
|
[linkedModpackProject?.title, pendingModpackUpdateVersion?.version_number]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
"
|
||||||
@confirm="handleModpackUpdateConfirm"
|
@confirm="handleModpackUpdateConfirm"
|
||||||
@cancel="handleModpackUpdateCancel"
|
@cancel="handleModpackUpdateCancel"
|
||||||
/>
|
/>
|
||||||
@@ -471,7 +476,7 @@ async function handleModpackContentToggle(item: ContentItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleModpackContentBulkToggle(items: ContentItem[]) {
|
async function handleModpackContentBulkToggle(items: ContentItem[]) {
|
||||||
await Promise.all(items.map((item) => toggleDisableMod(item)))
|
await Promise.all(items.map((item) => _toggleDisableMod(item)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleModpackContent() {
|
async function handleModpackContent() {
|
||||||
@@ -814,13 +819,12 @@ provideContentManager({
|
|||||||
isPackLocked,
|
isPackLocked,
|
||||||
isBusy: isInstanceBusy,
|
isBusy: isInstanceBusy,
|
||||||
isBulkOperating,
|
isBulkOperating,
|
||||||
getItemId: (item) => item.file_path ?? item.file_name,
|
|
||||||
contentTypeLabel: ref(formatMessage(messages.contentTypeProject)),
|
contentTypeLabel: ref(formatMessage(messages.contentTypeProject)),
|
||||||
toggleEnabled: toggleDisableMod,
|
toggleEnabled: toggleDisableMod,
|
||||||
bulkEnableItems: (items) =>
|
bulkEnableItems: (items) =>
|
||||||
Promise.all(items.map((item) => toggleDisableMod(item))).then(() => {}),
|
Promise.all(items.map((item) => _toggleDisableMod(item))).then(() => {}),
|
||||||
bulkDisableItems: (items) =>
|
bulkDisableItems: (items) =>
|
||||||
Promise.all(items.map((item) => toggleDisableMod(item))).then(() => {}),
|
Promise.all(items.map((item) => _toggleDisableMod(item))).then(() => {}),
|
||||||
deleteItem: removeMod,
|
deleteItem: removeMod,
|
||||||
bulkDeleteItems: (items) => Promise.all(items.map((item) => removeMod(item))).then(() => {}),
|
bulkDeleteItems: (items) => Promise.all(items.map((item) => removeMod(item))).then(() => {}),
|
||||||
refresh: () => initProjects('must_revalidate'),
|
refresh: () => initProjects('must_revalidate'),
|
||||||
@@ -838,7 +842,7 @@ provideContentManager({
|
|||||||
dismissContentHint,
|
dismissContentHint,
|
||||||
shareItems: handleShareItems,
|
shareItems: handleShareItems,
|
||||||
mapToTableItem: (item) => ({
|
mapToTableItem: (item) => ({
|
||||||
id: item.file_path ?? item.file_name,
|
id: item.id,
|
||||||
project: item.project ?? {
|
project: item.project ?? {
|
||||||
id: item.file_name,
|
id: item.file_name,
|
||||||
slug: null,
|
slug: null,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
remove_project,
|
remove_project,
|
||||||
} from '@/helpers/profile.js'
|
} from '@/helpers/profile.js'
|
||||||
import { get_game_versions } from '@/helpers/tags'
|
import { get_game_versions } from '@/helpers/tags'
|
||||||
|
import type { GameInstance, InstanceLoader } from '@/helpers/types'
|
||||||
import {
|
import {
|
||||||
findPreferredVersion,
|
findPreferredVersion,
|
||||||
installVersionDependencies,
|
installVersionDependencies,
|
||||||
@@ -37,13 +38,8 @@ interface ModalRef {
|
|||||||
hide: () => void
|
hide: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InstallConfirmModalRef {
|
interface ModpackAlreadyInstalledModalRef {
|
||||||
show: (
|
show: (instanceName: string, instancePath: string) => void
|
||||||
project: Labrinth.Projects.v2.Project,
|
|
||||||
version: string,
|
|
||||||
callback: (versionId?: string) => void,
|
|
||||||
createInstanceCallback: (profile: string) => void,
|
|
||||||
) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IncompatibilityWarningModalRef {
|
interface IncompatibilityWarningModalRef {
|
||||||
@@ -92,7 +88,9 @@ export interface ContentInstallContext {
|
|||||||
handleNavigate: (instance: ContentInstallInstance) => void
|
handleNavigate: (instance: ContentInstallInstance) => void
|
||||||
handleCancel: () => void
|
handleCancel: () => void
|
||||||
setContentInstallModal: (ref: ModalRef) => void
|
setContentInstallModal: (ref: ModalRef) => void
|
||||||
setInstallConfirmModal: (ref: InstallConfirmModalRef) => void
|
setModpackAlreadyInstalledModal: (ref: ModpackAlreadyInstalledModalRef) => void
|
||||||
|
handleModpackDuplicateCreateAnyway: () => Promise<void>
|
||||||
|
handleModpackDuplicateGoToInstance: (instancePath: string) => void
|
||||||
setIncompatibilityWarningModal: (ref: IncompatibilityWarningModalRef) => void
|
setIncompatibilityWarningModal: (ref: IncompatibilityWarningModalRef) => void
|
||||||
install: (
|
install: (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@@ -140,12 +138,13 @@ export function createContentInstall(opts: {
|
|||||||
) {
|
) {
|
||||||
const primaryFile = version?.files?.find((f) => f.primary) ?? version?.files?.[0]
|
const primaryFile = version?.files?.find((f) => f.primary) ?? version?.files?.[0]
|
||||||
const placeholder: ContentItem = {
|
const placeholder: ContentItem = {
|
||||||
|
id: `__installing_${project.id}`,
|
||||||
file_name: `__installing_${project.id}`,
|
file_name: `__installing_${project.id}`,
|
||||||
project: {
|
project: {
|
||||||
id: project.id,
|
id: project.id,
|
||||||
slug: project.slug ?? null,
|
slug: project.slug ?? '',
|
||||||
title: project.title,
|
title: project.title,
|
||||||
icon_url: project.icon_url ?? null,
|
icon_url: project.icon_url ?? undefined,
|
||||||
},
|
},
|
||||||
version: version
|
version: version
|
||||||
? {
|
? {
|
||||||
@@ -183,18 +182,26 @@ export function createContentInstall(opts: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let modalRef: ModalRef | null = null
|
let modalRef: ModalRef | null = null
|
||||||
let installConfirmModalRef: InstallConfirmModalRef | null = null
|
let modpackAlreadyInstalledModalRef: ModpackAlreadyInstalledModalRef | null = null
|
||||||
let incompatibilityWarningModalRef: IncompatibilityWarningModalRef | null = null
|
let incompatibilityWarningModalRef: IncompatibilityWarningModalRef | null = null
|
||||||
let currentProject: Labrinth.Projects.v2.Project | null = null
|
let currentProject: Labrinth.Projects.v2.Project | null = null
|
||||||
let currentVersions: Labrinth.Versions.v2.Version[] = []
|
let currentVersions: Labrinth.Versions.v2.Version[] = []
|
||||||
let currentCallback: (versionId?: string) => void = () => {}
|
let currentCallback: (versionId?: string) => void = () => {}
|
||||||
let profileMap: Record<string, GameInstance> = {}
|
let profileMap: Record<string, GameInstance> = {}
|
||||||
|
|
||||||
|
let pendingModpackInstall: {
|
||||||
|
project: Labrinth.Projects.v2.Project
|
||||||
|
version: string
|
||||||
|
source: string
|
||||||
|
callback: (versionId?: string) => void
|
||||||
|
createInstanceCallback: (profile: string) => void
|
||||||
|
} | null = null
|
||||||
|
|
||||||
async function showModInstallModal(
|
async function showModInstallModal(
|
||||||
project: Labrinth.Projects.v2.Project,
|
project: Labrinth.Projects.v2.Project,
|
||||||
versions: Labrinth.Versions.v2.Version[],
|
versions: Labrinth.Versions.v2.Version[],
|
||||||
onInstall: (versionId?: string) => void,
|
onInstall: (versionId?: string) => void,
|
||||||
hints?: { preferredLoader?: string; preferredGameVersion?: string },
|
hints?: { preferredLoader?: string; preferredGameVersion?: string; showProjectInfo?: boolean },
|
||||||
) {
|
) {
|
||||||
currentProject = project
|
currentProject = project
|
||||||
currentVersions = versions
|
currentVersions = versions
|
||||||
@@ -379,10 +386,10 @@ export function createContentInstall(opts: {
|
|||||||
trackEvent('ProjectInstall', {
|
trackEvent('ProjectInstall', {
|
||||||
loader: profile.loader,
|
loader: profile.loader,
|
||||||
game_version: profile.game_version,
|
game_version: profile.game_version,
|
||||||
id: currentProject.id,
|
id: currentProject!.id,
|
||||||
version_id: version.id,
|
version_id: version.id,
|
||||||
project_type: currentProject.project_type,
|
project_type: currentProject!.project_type,
|
||||||
title: currentProject.title,
|
title: currentProject!.title,
|
||||||
source: 'ProjectInstallModal',
|
source: 'ProjectInstallModal',
|
||||||
})
|
})
|
||||||
currentCallback(version.id)
|
currentCallback(version.id)
|
||||||
@@ -433,10 +440,10 @@ export function createContentInstall(opts: {
|
|||||||
trackEvent('ProjectInstall', {
|
trackEvent('ProjectInstall', {
|
||||||
loader: data.loader,
|
loader: data.loader,
|
||||||
game_version: data.gameVersion,
|
game_version: data.gameVersion,
|
||||||
id: currentProject.id,
|
id: currentProject!.id,
|
||||||
version_id: version.id,
|
version_id: version.id,
|
||||||
project_type: currentProject.project_type,
|
project_type: currentProject!.project_type,
|
||||||
title: currentProject.title,
|
title: currentProject!.title,
|
||||||
source: 'ProjectInstallModal',
|
source: 'ProjectInstallModal',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -470,28 +477,28 @@ export function createContentInstall(opts: {
|
|||||||
if (project.project_type === 'modpack') {
|
if (project.project_type === 'modpack') {
|
||||||
const version = versionId ?? project.versions[project.versions.length - 1]
|
const version = versionId ?? project.versions[project.versions.length - 1]
|
||||||
const packs = await list()
|
const packs = await list()
|
||||||
|
const existingPack = packs.find((pack) => pack.linked_data?.project_id === project.id)
|
||||||
|
|
||||||
if (
|
if (existingPack) {
|
||||||
packs.length === 0 ||
|
pendingModpackInstall = { project, version, source, callback, createInstanceCallback }
|
||||||
!packs.find((pack) => pack.linked_data?.project_id === project.id)
|
modpackAlreadyInstalledModalRef?.show(existingPack.name, existingPack.path)
|
||||||
) {
|
return
|
||||||
await packInstall(
|
|
||||||
project.id,
|
|
||||||
version,
|
|
||||||
project.title,
|
|
||||||
project.icon_url,
|
|
||||||
createInstanceCallback,
|
|
||||||
)
|
|
||||||
trackEvent('PackInstall', {
|
|
||||||
id: project.id,
|
|
||||||
version_id: version,
|
|
||||||
title: project.title,
|
|
||||||
source,
|
|
||||||
})
|
|
||||||
callback(version)
|
|
||||||
} else {
|
|
||||||
installConfirmModalRef?.show(project, version, callback, createInstanceCallback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await packInstall(
|
||||||
|
project.id,
|
||||||
|
version,
|
||||||
|
project.title,
|
||||||
|
project.icon_url,
|
||||||
|
createInstanceCallback,
|
||||||
|
)
|
||||||
|
trackEvent('PackInstall', {
|
||||||
|
id: project.id,
|
||||||
|
version_id: version,
|
||||||
|
title: project.title,
|
||||||
|
source,
|
||||||
|
})
|
||||||
|
callback(version)
|
||||||
} else if (instancePath) {
|
} else if (instancePath) {
|
||||||
const [instanceOrNull, instanceProjects, versions] = await Promise.all([
|
const [instanceOrNull, instanceProjects, versions] = await Promise.all([
|
||||||
get(instancePath),
|
get(instancePath),
|
||||||
@@ -577,8 +584,31 @@ export function createContentInstall(opts: {
|
|||||||
setContentInstallModal(ref: ModalRef) {
|
setContentInstallModal(ref: ModalRef) {
|
||||||
modalRef = ref
|
modalRef = ref
|
||||||
},
|
},
|
||||||
setInstallConfirmModal(ref: InstallConfirmModalRef) {
|
setModpackAlreadyInstalledModal(ref: ModpackAlreadyInstalledModalRef) {
|
||||||
installConfirmModalRef = ref
|
modpackAlreadyInstalledModalRef = ref
|
||||||
|
},
|
||||||
|
async handleModpackDuplicateCreateAnyway() {
|
||||||
|
if (!pendingModpackInstall) return
|
||||||
|
const { project, version, source, callback, createInstanceCallback } = pendingModpackInstall
|
||||||
|
pendingModpackInstall = null
|
||||||
|
await packInstall(
|
||||||
|
project.id,
|
||||||
|
version,
|
||||||
|
project.title,
|
||||||
|
project.icon_url,
|
||||||
|
createInstanceCallback,
|
||||||
|
)
|
||||||
|
trackEvent('PackInstall', {
|
||||||
|
id: project.id,
|
||||||
|
version_id: version,
|
||||||
|
title: project.title,
|
||||||
|
source,
|
||||||
|
})
|
||||||
|
callback(version)
|
||||||
|
},
|
||||||
|
handleModpackDuplicateGoToInstance(instancePath: string) {
|
||||||
|
pendingModpackInstall = null
|
||||||
|
opts.router.push(`/instance/${encodeURIComponent(instancePath)}/`)
|
||||||
},
|
},
|
||||||
setIncompatibilityWarningModal(ref: IncompatibilityWarningModalRef) {
|
setIncompatibilityWarningModal(ref: IncompatibilityWarningModalRef) {
|
||||||
incompatibilityWarningModalRef = ref
|
incompatibilityWarningModalRef = ref
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
import type { AbstractWebNotificationManager, CreationFlowContextValue } from '@modrinth/ui'
|
import type {
|
||||||
import { provide, useTemplateRef } from 'vue'
|
AbstractWebNotificationManager,
|
||||||
|
CreationFlowContextValue,
|
||||||
|
CreationFlowModal,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import { provide, ref, useTemplateRef } from 'vue'
|
||||||
|
import type { ComponentExposed } from 'vue-component-type-helpers'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import type ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlreadyInstalledModal.vue'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import { get_project_versions, get_search_results } from '@/helpers/cache.js'
|
import { get_project_versions, get_search_results } from '@/helpers/cache.js'
|
||||||
import { import_instance } from '@/helpers/import.js'
|
import { import_instance } from '@/helpers/import.js'
|
||||||
import { create_profile_and_install, create_profile_and_install_from_file } from '@/helpers/pack'
|
import { create_profile_and_install, create_profile_and_install_from_file } from '@/helpers/pack'
|
||||||
import { create, list } from '@/helpers/profile.js'
|
import { create, list } from '@/helpers/profile.js'
|
||||||
|
import type { InstanceLoader } from '@/helpers/types'
|
||||||
|
|
||||||
export function setupCreationModal(notificationManager: AbstractWebNotificationManager) {
|
export function setupCreationModal(notificationManager: AbstractWebNotificationManager) {
|
||||||
const { handleError } = notificationManager
|
const { handleError } = notificationManager
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const installationModal = useTemplateRef('installationModal')
|
const installationModal =
|
||||||
|
useTemplateRef<ComponentExposed<typeof CreationFlowModal>>('installationModal')
|
||||||
|
const modpackAlreadyInstalledModal = ref<InstanceType<typeof ModpackAlreadyInstalledModal>>()
|
||||||
|
|
||||||
|
function setModpackAlreadyInstalledModal(
|
||||||
|
modal: InstanceType<typeof ModpackAlreadyInstalledModal>,
|
||||||
|
) {
|
||||||
|
modpackAlreadyInstalledModal.value = modal
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchExistingInstanceNames(): Promise<string[]> {
|
async function fetchExistingInstanceNames(): Promise<string[]> {
|
||||||
const instances = await list().catch(handleError)
|
const instances = await list().catch(handleError)
|
||||||
@@ -23,10 +38,34 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
|||||||
installationModal.value?.show()
|
installationModal.value?.show()
|
||||||
})
|
})
|
||||||
|
|
||||||
async function handleCreate(config: CreationFlowContextValue) {
|
async function proceedWithModpackCreation(
|
||||||
installationModal.value?.hide()
|
projectId: string,
|
||||||
|
versionId: string,
|
||||||
|
name: string,
|
||||||
|
iconUrl?: string,
|
||||||
|
) {
|
||||||
|
await create_profile_and_install(projectId, versionId, name, iconUrl).catch(handleError)
|
||||||
|
trackEvent('InstanceCreate', { source: 'CreationModalModpack' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCreate(config: CreationFlowContextValue) {
|
||||||
try {
|
try {
|
||||||
|
if (config.modpackSelection.value) {
|
||||||
|
const { projectId, versionId, name, iconUrl } = config.modpackSelection.value
|
||||||
|
|
||||||
|
const instances = await list().catch(handleError)
|
||||||
|
const existingInstance = instances?.find((i) => i.linked_data?.project_id === projectId)
|
||||||
|
|
||||||
|
if (existingInstance) {
|
||||||
|
pendingModpackCreation.value = { projectId, versionId, name, iconUrl }
|
||||||
|
installationModal.value?.hide()
|
||||||
|
modpackAlreadyInstalledModal.value?.show(existingInstance.name, existingInstance.path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
installationModal.value?.hide()
|
||||||
|
|
||||||
if (config.isImportMode.value) {
|
if (config.isImportMode.value) {
|
||||||
for (const [launcherName, instanceSet] of Object.entries(
|
for (const [launcherName, instanceSet] of Object.entries(
|
||||||
config.importSelectedInstances.value,
|
config.importSelectedInstances.value,
|
||||||
@@ -43,8 +82,7 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
|||||||
|
|
||||||
if (config.modpackSelection.value) {
|
if (config.modpackSelection.value) {
|
||||||
const { projectId, versionId, name, iconUrl } = config.modpackSelection.value
|
const { projectId, versionId, name, iconUrl } = config.modpackSelection.value
|
||||||
await create_profile_and_install(projectId, versionId, name, iconUrl).catch(handleError)
|
await proceedWithModpackCreation(projectId, versionId, name, iconUrl)
|
||||||
trackEvent('InstanceCreate', { source: 'CreationModalModpack' })
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,26 +104,40 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
|||||||
|
|
||||||
await create(
|
await create(
|
||||||
name,
|
name,
|
||||||
config.selectedGameVersion.value,
|
config.selectedGameVersion.value!,
|
||||||
loader,
|
loader as InstanceLoader,
|
||||||
loaderVersion,
|
loaderVersion,
|
||||||
iconPath,
|
iconPath,
|
||||||
false,
|
false,
|
||||||
).catch(handleError)
|
).catch(handleError)
|
||||||
|
|
||||||
trackEvent('InstanceCreate', {
|
trackEvent('InstanceCreate', {
|
||||||
profile_name: name,
|
|
||||||
game_version: config.selectedGameVersion.value,
|
|
||||||
loader,
|
|
||||||
loader_version: loaderVersion,
|
|
||||||
has_icon: !!iconPath,
|
|
||||||
source: 'CreationModal',
|
source: 'CreationModal',
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err)
|
handleError(err as Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pendingModpackCreation = ref<{
|
||||||
|
projectId: string
|
||||||
|
versionId: string
|
||||||
|
name: string
|
||||||
|
iconUrl?: string
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
|
async function handleModpackDuplicateCreateAnyway() {
|
||||||
|
if (!pendingModpackCreation.value) return
|
||||||
|
const { projectId, versionId, name, iconUrl } = pendingModpackCreation.value
|
||||||
|
pendingModpackCreation.value = null
|
||||||
|
await proceedWithModpackCreation(projectId, versionId, name, iconUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleModpackDuplicateGoToInstance(instancePath: string) {
|
||||||
|
pendingModpackCreation.value = null
|
||||||
|
router.push(`/instance/${encodeURIComponent(instancePath)}/`)
|
||||||
|
}
|
||||||
|
|
||||||
function handleBrowseModpacks() {
|
function handleBrowseModpacks() {
|
||||||
installationModal.value?.hide()
|
installationModal.value?.hide()
|
||||||
router.push('/browse/modpack')
|
router.push('/browse/modpack')
|
||||||
@@ -113,5 +165,8 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
|||||||
handleBrowseModpacks,
|
handleBrowseModpacks,
|
||||||
searchModpacks,
|
searchModpacks,
|
||||||
getProjectVersions,
|
getProjectVersions,
|
||||||
|
setModpackAlreadyInstalledModal,
|
||||||
|
handleModpackDuplicateCreateAnyway,
|
||||||
|
handleModpackDuplicateGoToInstance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"semver": "^7.5.4",
|
"semver": "^7.5.4",
|
||||||
"three": "^0.172.0",
|
"three": "^0.172.0",
|
||||||
"vue-confetti-explosion": "^1.0.2",
|
"vue-confetti-explosion": "^1.0.2",
|
||||||
|
"vue-router": "*",
|
||||||
"vue-typed-virtual-list": "^1.0.10",
|
"vue-typed-virtual-list": "^1.0.10",
|
||||||
"vue3-ace-editor": "^2.2.4",
|
"vue3-ace-editor": "^2.2.4",
|
||||||
"vue3-apexcharts": "^1.5.2",
|
"vue3-apexcharts": "^1.5.2",
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ pub struct ContentItem {
|
|||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
/// Relative path to the file within the profile
|
/// Relative path to the file within the profile
|
||||||
pub file_path: String,
|
pub file_path: String,
|
||||||
/// SHA1 hash of the file
|
/// Stable frontend identifier (SHA1 hash of file content, survives renames).
|
||||||
pub hash: String,
|
/// Not a project or version ID.
|
||||||
|
pub id: String,
|
||||||
/// File size in bytes
|
/// File size in bytes
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
/// Whether the file is enabled (not .disabled)
|
/// Whether the file is enabled (not .disabled)
|
||||||
@@ -542,7 +543,7 @@ async fn profile_files_to_content_items(
|
|||||||
ContentItem {
|
ContentItem {
|
||||||
file_name: file.file_name.clone(),
|
file_name: file.file_name.clone(),
|
||||||
file_path: path.clone(),
|
file_path: path.clone(),
|
||||||
hash: file.hash.clone(),
|
id: file.hash.clone(),
|
||||||
size: file.size,
|
size: file.size,
|
||||||
enabled: !file.file_name.ends_with(".disabled"),
|
enabled: !file.file_name.ends_with(".disabled"),
|
||||||
project_type: file.project_type,
|
project_type: file.project_type,
|
||||||
@@ -726,7 +727,7 @@ pub async fn dependencies_to_content_items(
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
file_path: String::new(),
|
file_path: String::new(),
|
||||||
hash: String::new(),
|
id: String::new(),
|
||||||
size: version
|
size: version
|
||||||
.and_then(|v| v.files.first())
|
.and_then(|v| v.files.first())
|
||||||
.map(|f| f.size as u64)
|
.map(|f| f.size as u64)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import _ArrowLeftRightIcon from './icons/arrow-left-right.svg?component'
|
|||||||
import _ArrowUpIcon from './icons/arrow-up.svg?component'
|
import _ArrowUpIcon from './icons/arrow-up.svg?component'
|
||||||
import _ArrowUpDownIcon from './icons/arrow-up-down.svg?component'
|
import _ArrowUpDownIcon from './icons/arrow-up-down.svg?component'
|
||||||
import _ArrowUpRightIcon from './icons/arrow-up-right.svg?component'
|
import _ArrowUpRightIcon from './icons/arrow-up-right.svg?component'
|
||||||
|
import _ArrowUpZAIcon from './icons/arrow-up-z-a.svg?component'
|
||||||
import _AsteriskIcon from './icons/asterisk.svg?component'
|
import _AsteriskIcon from './icons/asterisk.svg?component'
|
||||||
import _BadgeCheckIcon from './icons/badge-check.svg?component'
|
import _BadgeCheckIcon from './icons/badge-check.svg?component'
|
||||||
import _BadgeDollarSignIcon from './icons/badge-dollar-sign.svg?component'
|
import _BadgeDollarSignIcon from './icons/badge-dollar-sign.svg?component'
|
||||||
@@ -404,6 +405,7 @@ export const ArrowLeftRightIcon = _ArrowLeftRightIcon
|
|||||||
export const ArrowUpIcon = _ArrowUpIcon
|
export const ArrowUpIcon = _ArrowUpIcon
|
||||||
export const ArrowUpDownIcon = _ArrowUpDownIcon
|
export const ArrowUpDownIcon = _ArrowUpDownIcon
|
||||||
export const ArrowUpRightIcon = _ArrowUpRightIcon
|
export const ArrowUpRightIcon = _ArrowUpRightIcon
|
||||||
|
export const ArrowUpZAIcon = _ArrowUpZAIcon
|
||||||
export const AsteriskIcon = _AsteriskIcon
|
export const AsteriskIcon = _AsteriskIcon
|
||||||
export const BadgeCheckIcon = _BadgeCheckIcon
|
export const BadgeCheckIcon = _BadgeCheckIcon
|
||||||
export const BadgeDollarSignIcon = _BadgeDollarSignIcon
|
export const BadgeDollarSignIcon = _BadgeDollarSignIcon
|
||||||
|
|||||||
19
packages/assets/icons/arrow-up-z-a.svg
Normal file
19
packages/assets/icons/arrow-up-z-a.svg
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!-- @license lucide-static v0.562.0 - ISC -->
|
||||||
|
<svg
|
||||||
|
class="lucide lucide-arrow-up-z-a"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="m3 8 4-4 4 4" />
|
||||||
|
<path d="M7 4v16" />
|
||||||
|
<path d="M15 4h5l-5 6h5" />
|
||||||
|
<path d="M15 20v-3.5a2.5 2.5 0 0 1 5 0V20" />
|
||||||
|
<path d="M20 18h-5" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 446 B |
@@ -1,11 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onUnmounted, watch } from 'vue'
|
import { onUnmounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
shown: boolean
|
shown: boolean
|
||||||
ariaLabel?: string
|
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(
|
watch(
|
||||||
() => props.shown,
|
() => props.shown,
|
||||||
(shown) => {
|
(shown) => {
|
||||||
@@ -15,6 +52,7 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
observer?.disconnect()
|
||||||
document?.body.classList.remove('floating-action-bar-shown')
|
document?.body.classList.remove('floating-action-bar-shown')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -27,9 +65,11 @@ onUnmounted(() => {
|
|||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
ref="toolbarEl"
|
||||||
role="toolbar"
|
role="toolbar"
|
||||||
:aria-label="ariaLabel"
|
: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="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 />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
@@ -81,4 +121,12 @@ onUnmounted(() => {
|
|||||||
.intercom-lightweight-app-launcher {
|
.intercom-lightweight-app-launcher {
|
||||||
z-index: 9 !important;
|
z-index: 9 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bar-compact .bar-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-compact .cq-show-icon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -100,15 +100,17 @@
|
|||||||
|
|
||||||
<InlineBackupCreator
|
<InlineBackupCreator
|
||||||
v-if="ctx.flowType === 'reset-server'"
|
v-if="ctx.flowType === 'reset-server'"
|
||||||
backup-name="Before reinstall"
|
ref="backupCreator"
|
||||||
|
backup-name="Before reset server"
|
||||||
hide-shift-click-hint
|
hide-shift-click-hint
|
||||||
|
@update:buttons-disabled="ctx.isBackingUp.value = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { EyeIcon, EyeOffIcon, SettingsIcon } from '@modrinth/assets'
|
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'
|
import { useDebugLogger } from '#ui/composables/debug-logger'
|
||||||
|
|
||||||
@@ -125,6 +127,11 @@ import { capitalize } from '../shared'
|
|||||||
|
|
||||||
const debug = useDebugLogger('FinalConfigStage')
|
const debug = useDebugLogger('FinalConfigStage')
|
||||||
const ctx = injectCreationFlowContext()
|
const ctx = injectCreationFlowContext()
|
||||||
|
|
||||||
|
const backupCreator = ref<InstanceType<typeof InlineBackupCreator> | null>(null)
|
||||||
|
watch(backupCreator, (creator) => {
|
||||||
|
ctx.cancelBackup.value = creator?.cancelBackup ?? null
|
||||||
|
})
|
||||||
const {
|
const {
|
||||||
worldName,
|
worldName,
|
||||||
gamemode,
|
gamemode,
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ export interface CreationFlowContextValue {
|
|||||||
// Loading state (set when finish() is called, cleared on reset)
|
// Loading state (set when finish() is called, cleared on reset)
|
||||||
loading: Ref<boolean>
|
loading: Ref<boolean>
|
||||||
|
|
||||||
|
// Backup state (set by InlineBackupCreator in reset-server flow)
|
||||||
|
isBackingUp: Ref<boolean>
|
||||||
|
cancelBackup: Ref<(() => void) | null>
|
||||||
|
|
||||||
// Modal
|
// Modal
|
||||||
modal: ShallowRef<ComponentExposed<typeof MultiStageModal> | null>
|
modal: ShallowRef<ComponentExposed<typeof MultiStageModal> | null>
|
||||||
stageConfigs: StageConfigInput<CreationFlowContextValue>[]
|
stageConfigs: StageConfigInput<CreationFlowContextValue>[]
|
||||||
@@ -240,6 +244,8 @@ export function createCreationFlowContext(
|
|||||||
|
|
||||||
const hardReset = ref(isInitialSetup)
|
const hardReset = ref(isInitialSetup)
|
||||||
const loading = ref(false)
|
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)
|
// hideLoaderChips: hides the entire loader chips section (only for vanilla world type in world/server flows)
|
||||||
const hideLoaderChips = computed(() => setupType.value === 'vanilla')
|
const hideLoaderChips = computed(() => setupType.value === 'vanilla')
|
||||||
@@ -292,6 +298,8 @@ export function createCreationFlowContext(
|
|||||||
|
|
||||||
hardReset.value = isInitialSetup
|
hardReset.value = isInitialSetup
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
isBackingUp.value = false
|
||||||
|
cancelBackup.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSetupType(type: SetupType) {
|
function setSetupType(type: SetupType) {
|
||||||
@@ -401,6 +409,8 @@ export function createCreationFlowContext(
|
|||||||
importSearchQuery,
|
importSearchQuery,
|
||||||
hardReset,
|
hardReset,
|
||||||
loading,
|
loading,
|
||||||
|
isBackingUp,
|
||||||
|
cancelBackup,
|
||||||
modal,
|
modal,
|
||||||
stageConfigs: resolvedStageConfigs,
|
stageConfigs: resolvedStageConfigs,
|
||||||
onBack,
|
onBack,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:context="ctx"
|
:context="ctx"
|
||||||
:fade="fade"
|
:fade="fade"
|
||||||
disable-progress
|
disable-progress
|
||||||
@hide="$emit('hide')"
|
@hide="handleHide"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -89,5 +89,10 @@ function hide() {
|
|||||||
modal.value?.hide()
|
modal.value?.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleHide() {
|
||||||
|
ctx.cancelBackup.value?.()
|
||||||
|
emit('hide')
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ show, hide, ctx })
|
defineExpose({ show, hide, ctx })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
|
|||||||
icon: isFinish ? PlusIcon : RightArrowIcon,
|
icon: isFinish ? PlusIcon : RightArrowIcon,
|
||||||
iconPosition: isFinish ? ('before' as const) : ('after' as const),
|
iconPosition: isFinish ? ('before' as const) : ('after' as const),
|
||||||
color: isReset ? ('red' as const) : isFinish ? ('brand' as const) : undefined,
|
color: isReset ? ('red' as const) : isFinish ? ('brand' as const) : undefined,
|
||||||
disabled: isForwardBlocked(ctx),
|
disabled: isForwardBlocked(ctx) || ctx.isBackingUp.value,
|
||||||
loading: isFinish && ctx.loading.value,
|
loading: isFinish && ctx.loading.value,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (isFinish) {
|
if (isFinish) {
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const description = computed(() => {
|
|||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
fallbackName: {
|
fallbackName: {
|
||||||
id: 'servers.backups.admonition.fallback-name',
|
id: 'servers.backups.admonition.fallback-name',
|
||||||
defaultMessage: 'your backup',
|
defaultMessage: 'Your backup',
|
||||||
},
|
},
|
||||||
backupQueuedTitle: {
|
backupQueuedTitle: {
|
||||||
id: 'servers.backups.admonition.backup-queued.title',
|
id: 'servers.backups.admonition.backup-queued.title',
|
||||||
|
|||||||
@@ -84,10 +84,10 @@ const admonitions = computed<AdmonitionEntry[]>(() => {
|
|||||||
// 1. Active WS entries (real-time progress from backupsState)
|
// 1. Active WS entries (real-time progress from backupsState)
|
||||||
for (const [id, entry] of backupsState.entries()) {
|
for (const [id, entry] of backupsState.entries()) {
|
||||||
const backup = findBackup(id)
|
const backup = findBackup(id)
|
||||||
|
seenIds.add(id)
|
||||||
if (entry.create && entry.create.state === 'ongoing') {
|
if (entry.create && entry.create.state === 'ongoing') {
|
||||||
const key = `${id}:create`
|
const key = `${id}:create`
|
||||||
if (!dismissedIds.has(key)) {
|
if (!dismissedIds.has(key)) {
|
||||||
seenIds.add(id)
|
|
||||||
result.push({
|
result.push({
|
||||||
key,
|
key,
|
||||||
backupId: id,
|
backupId: id,
|
||||||
@@ -102,7 +102,6 @@ const admonitions = computed<AdmonitionEntry[]>(() => {
|
|||||||
if (entry.restore && entry.restore.state === 'ongoing') {
|
if (entry.restore && entry.restore.state === 'ongoing') {
|
||||||
const key = `${id}:restore`
|
const key = `${id}:restore`
|
||||||
if (!dismissedIds.has(key)) {
|
if (!dismissedIds.has(key)) {
|
||||||
seenIds.add(id)
|
|
||||||
result.push({
|
result.push({
|
||||||
key,
|
key,
|
||||||
backupId: id,
|
backupId: id,
|
||||||
|
|||||||
@@ -178,10 +178,10 @@ const calculateMenuPosition = () => {
|
|||||||
top = Math.max(margin, window.innerHeight - menuHeight - margin)
|
top = Math.max(margin, window.innerHeight - menuHeight - margin)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (triggerRect.left + menuWidth + margin <= window.innerWidth) {
|
if (triggerRect.right - menuWidth >= margin) {
|
||||||
left = triggerRect.left
|
|
||||||
} else if (triggerRect.right - menuWidth - margin >= 0) {
|
|
||||||
left = triggerRect.right - menuWidth
|
left = triggerRect.right - menuWidth
|
||||||
|
} else if (triggerRect.left + menuWidth + margin <= window.innerWidth) {
|
||||||
|
left = triggerRect.left
|
||||||
} else {
|
} else {
|
||||||
left = Math.max(margin, window.innerWidth - menuWidth - margin)
|
left = Math.max(margin, window.innerWidth - menuWidth - margin)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,22 +87,23 @@ const deleteHovered = ref(false)
|
|||||||
:class="{ 'opacity-50': disabled }"
|
:class="{ 'opacity-50': disabled }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex min-w-0 items-center gap-4 transition-[filter,opacity] duration-200"
|
class="flex min-w-0 items-center gap-4"
|
||||||
:class="[
|
:class="
|
||||||
hideActions ? 'flex-1' : 'flex-1 @[800px]:w-[350px] @[800px]:shrink-0 @[800px]:flex-none',
|
hideActions ? 'flex-1' : 'flex-1 @[800px]:w-[350px] @[800px]:shrink-0 @[800px]:flex-none'
|
||||||
enabled === false && !disabled ? 'grayscale opacity-50' : '',
|
"
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-if="showCheckbox"
|
v-if="showCheckbox"
|
||||||
:model-value="selected ?? false"
|
:model-value="selected ?? false"
|
||||||
:disabled="disabled"
|
|
||||||
:aria-label="`Select ${project.title}`"
|
:aria-label="`Select ${project.title}`"
|
||||||
class="shrink-0"
|
class="shrink-0"
|
||||||
@update:model-value="selected = $event"
|
@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
|
<div
|
||||||
v-tooltip="installing ? formatMessage(commonMessages.installingLabel) : undefined"
|
v-tooltip="installing ? formatMessage(commonMessages.installingLabel) : undefined"
|
||||||
class="relative shrink-0"
|
class="relative shrink-0"
|
||||||
@@ -256,7 +257,7 @@ const deleteHovered = ref(false)
|
|||||||
:model-value="enabled"
|
:model-value="enabled"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:aria-label="project.title"
|
:aria-label="project.title"
|
||||||
class="mr-2 my-auto"
|
class="my-auto"
|
||||||
@update:model-value="(val) => emit('update:enabled', val as boolean)"
|
@update:model-value="(val) => emit('update:enabled', val as boolean)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PowerIcon, PowerOffIcon } from '@modrinth/assets'
|
import { PowerIcon, PowerOffIcon, XIcon } from '@modrinth/assets'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
|
||||||
@@ -61,6 +61,14 @@ const messages = defineMessages({
|
|||||||
id: 'content.selection-bar.bulk.deleting-waiting',
|
id: 'content.selection-bar.bulk.deleting-waiting',
|
||||||
defaultMessage: 'Deleting {contentType}...',
|
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 {
|
interface Props {
|
||||||
@@ -95,6 +103,7 @@ const emit = defineEmits<{
|
|||||||
const shown = computed(() => props.selectedItems.length > 0 || props.isBulkOperating)
|
const shown = computed(() => props.selectedItems.length > 0 || props.isBulkOperating)
|
||||||
|
|
||||||
const allDisabled = computed(() => props.selectedItems.every((m) => !m.enabled))
|
const allDisabled = computed(() => props.selectedItems.every((m) => !m.enabled))
|
||||||
|
const allEnabled = computed(() => props.selectedItems.every((m) => m.enabled))
|
||||||
|
|
||||||
const selectedCountText = computed(() => {
|
const selectedCountText = computed(() => {
|
||||||
const count = props.selectedItems.length || props.bulkTotal
|
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" />
|
<div class="mx-1 h-6 w-px bg-surface-5" />
|
||||||
<ButtonStyled type="transparent">
|
<ButtonStyled type="transparent">
|
||||||
<button
|
<button
|
||||||
|
v-tooltip="formatMessage(commonMessages.clearButton)"
|
||||||
class="!text-primary"
|
class="!text-primary"
|
||||||
:disabled="isBulkOperating"
|
:disabled="isBulkOperating"
|
||||||
:class="{ 'opacity-60 pointer-events-none': isBulkOperating }"
|
:class="{ 'opacity-60 pointer-events-none': isBulkOperating }"
|
||||||
@click="emit('clear')"
|
@click="emit('clear')"
|
||||||
>
|
>
|
||||||
{{ formatMessage(commonMessages.clearButton) }}
|
<XIcon class="hidden cq-show-icon" />
|
||||||
|
<span class="bar-label">{{ formatMessage(commonMessages.clearButton) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
@@ -148,16 +159,30 @@ const bulkProgressMessage = computed(() => {
|
|||||||
<div v-if="!isBulkOperating" class="ml-auto flex items-center gap-0.5">
|
<div v-if="!isBulkOperating" class="ml-auto flex items-center gap-0.5">
|
||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
|
|
||||||
<ButtonStyled v-if="allDisabled" type="transparent">
|
<ButtonStyled type="transparent">
|
||||||
<button :disabled="isBusy" @click="emit('enable')">
|
<button
|
||||||
|
v-tooltip="
|
||||||
|
allEnabled ? formatMessage(messages.allAlreadyEnabled) : formatMessage(messages.enable)
|
||||||
|
"
|
||||||
|
:disabled="isBusy || allEnabled"
|
||||||
|
@click="emit('enable')"
|
||||||
|
>
|
||||||
<PowerIcon />
|
<PowerIcon />
|
||||||
{{ formatMessage(messages.enable) }}
|
<span class="bar-label">{{ formatMessage(messages.enable) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled v-else type="transparent">
|
<ButtonStyled type="transparent">
|
||||||
<button :disabled="isBusy" @click="emit('disable')">
|
<button
|
||||||
|
v-tooltip="
|
||||||
|
allDisabled
|
||||||
|
? formatMessage(messages.allAlreadyDisabled)
|
||||||
|
: formatMessage(messages.disable)
|
||||||
|
"
|
||||||
|
:disabled="isBusy || allDisabled"
|
||||||
|
@click="emit('disable')"
|
||||||
|
>
|
||||||
<PowerOffIcon />
|
<PowerOffIcon />
|
||||||
{{ formatMessage(messages.disable) }}
|
<span class="bar-label">{{ formatMessage(messages.disable) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</Admonition>
|
</Admonition>
|
||||||
<InlineBackupCreator
|
<InlineBackupCreator
|
||||||
ref="backupCreator"
|
ref="backupCreator"
|
||||||
backup-name="Before bulk update"
|
:backup-name="backupTip ? `Before bulk update (${backupTip})` : 'Before bulk update'"
|
||||||
@update:buttons-disabled="buttonsDisabled = $event"
|
@update:buttons-disabled="buttonsDisabled = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,6 +73,7 @@ const messages = defineMessages({
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
count: number
|
count: number
|
||||||
server?: boolean
|
server?: boolean
|
||||||
|
backupTip?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</Admonition>
|
</Admonition>
|
||||||
<InlineBackupCreator
|
<InlineBackupCreator
|
||||||
ref="backupCreator"
|
ref="backupCreator"
|
||||||
backup-name="Before deletion"
|
:backup-name="backupTip ? `Before deletion (${backupTip})` : 'Before deletion'"
|
||||||
@update:buttons-disabled="buttonsDisabled = $event"
|
@update:buttons-disabled="buttonsDisabled = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,9 +78,11 @@ withDefaults(
|
|||||||
count: number
|
count: number
|
||||||
itemType: string
|
itemType: string
|
||||||
variant?: 'instance' | 'server'
|
variant?: 'instance' | 'server'
|
||||||
|
backupTip?: string
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
variant: 'instance',
|
variant: 'instance',
|
||||||
|
backupTip: undefined,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</Admonition>
|
</Admonition>
|
||||||
<InlineBackupCreator
|
<InlineBackupCreator
|
||||||
ref="backupCreator"
|
ref="backupCreator"
|
||||||
:backup-name="downgrade ? 'Before modpack downgrade' : 'Before modpack update'"
|
:backup-name="backupName"
|
||||||
@update:buttons-disabled="buttonsDisabled = $event"
|
@update:buttons-disabled="buttonsDisabled = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import Admonition from '#ui/components/base/Admonition.vue'
|
import Admonition from '#ui/components/base/Admonition.vue'
|
||||||
import ButtonStyled from '#ui/components/base/ButtonStyled.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'
|
import InlineBackupCreator from './InlineBackupCreator.vue'
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
downgrade?: boolean
|
downgrade?: boolean
|
||||||
server?: boolean
|
server?: boolean
|
||||||
|
backupTip?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
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({
|
const messages = defineMessages({
|
||||||
header: {
|
header: {
|
||||||
id: 'content.confirm-modpack-update.header',
|
id: 'content.confirm-modpack-update.header',
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</Admonition>
|
</Admonition>
|
||||||
<InlineBackupCreator
|
<InlineBackupCreator
|
||||||
ref="backupCreator"
|
ref="backupCreator"
|
||||||
backup-name="Before reinstall"
|
:backup-name="backupTip ? `Before reinstall (${backupTip})` : 'Before reinstall'"
|
||||||
@update:buttons-disabled="buttonsDisabled = $event"
|
@update:buttons-disabled="buttonsDisabled = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,6 +72,7 @@ const messages = defineMessages({
|
|||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
server?: boolean
|
server?: boolean
|
||||||
|
backupTip?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</Admonition>
|
</Admonition>
|
||||||
<InlineBackupCreator
|
<InlineBackupCreator
|
||||||
ref="backupCreator"
|
ref="backupCreator"
|
||||||
backup-name="Before unlink"
|
:backup-name="backupTip ? `Before unlink (${backupTip})` : 'Before unlink'"
|
||||||
@update:buttons-disabled="buttonsDisabled = $event"
|
@update:buttons-disabled="buttonsDisabled = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,6 +50,7 @@ import InlineBackupCreator from './InlineBackupCreator.vue'
|
|||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
server?: boolean
|
server?: boolean
|
||||||
|
backupTip?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|||||||
@@ -3,19 +3,16 @@ import { computed, ref, watch } from 'vue'
|
|||||||
|
|
||||||
import type { ContentItem } from '../types'
|
import type { ContentItem } from '../types'
|
||||||
|
|
||||||
export function useContentSelection(
|
export function useContentSelection(items: Ref<ContentItem[]>) {
|
||||||
items: Ref<ContentItem[]>,
|
|
||||||
getItemId: (item: ContentItem) => string,
|
|
||||||
) {
|
|
||||||
const selectedIds = ref<string[]>([])
|
const selectedIds = ref<string[]>([])
|
||||||
|
|
||||||
const selectedItems = computed(() =>
|
const selectedItems = computed(() =>
|
||||||
items.value.filter((item) => selectedIds.value.includes(getItemId(item))),
|
items.value.filter((item) => selectedIds.value.includes(item.id)),
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(items, (newItems) => {
|
watch(items, (newItems) => {
|
||||||
if (selectedIds.value.length === 0) return
|
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))
|
const pruned = selectedIds.value.filter((id) => validIds.has(id))
|
||||||
if (pruned.length !== selectedIds.value.length) {
|
if (pruned.length !== selectedIds.value.length) {
|
||||||
selectedIds.value = pruned
|
selectedIds.value = pruned
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
ArrowDownAZIcon,
|
ArrowDownAZIcon,
|
||||||
ArrowDownZAIcon,
|
ArrowUpZAIcon,
|
||||||
ClockArrowDownIcon,
|
ClockArrowDownIcon,
|
||||||
ClockArrowUpIcon,
|
ClockArrowUpIcon,
|
||||||
CodeIcon,
|
CodeIcon,
|
||||||
@@ -171,8 +171,8 @@ const sortLabels: Record<SortMode, () => string> = {
|
|||||||
function cycleSortMode() {
|
function cycleSortMode() {
|
||||||
const modes: SortMode[] = [
|
const modes: SortMode[] = [
|
||||||
'alphabetical-asc',
|
'alphabetical-asc',
|
||||||
'date-added-newest',
|
|
||||||
'alphabetical-desc',
|
'alphabetical-desc',
|
||||||
|
'date-added-newest',
|
||||||
'date-added-oldest',
|
'date-added-oldest',
|
||||||
]
|
]
|
||||||
const idx = modes.indexOf(sortMode.value)
|
const idx = modes.indexOf(sortMode.value)
|
||||||
@@ -235,7 +235,6 @@ const { selectedFilters, filterOptions, toggleFilter, applyFilters } = useConten
|
|||||||
|
|
||||||
const { selectedIds, selectedItems, clearSelection, removeFromSelection } = useContentSelection(
|
const { selectedIds, selectedItems, clearSelection, removeFromSelection } = useContentSelection(
|
||||||
ctx.items,
|
ctx.items,
|
||||||
ctx.getItemId,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const { isBulkOperating, bulkProgress, bulkTotal, bulkOperation, runBulk } = useBulkOperation()
|
const { isBulkOperating, bulkProgress, bulkTotal, bulkOperation, runBulk } = useBulkOperation()
|
||||||
@@ -292,7 +291,7 @@ const pendingDeletionItems = ref<ContentItem[]>([])
|
|||||||
const confirmDeletionModal = ref<InstanceType<typeof ConfirmDeletionModal>>()
|
const confirmDeletionModal = ref<InstanceType<typeof ConfirmDeletionModal>>()
|
||||||
|
|
||||||
function handleDeleteById(id: string, event?: MouseEvent) {
|
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) {
|
if (item) {
|
||||||
pendingDeletionItems.value = [item]
|
pendingDeletionItems.value = [item]
|
||||||
if (event?.shiftKey) {
|
if (event?.shiftKey) {
|
||||||
@@ -334,7 +333,7 @@ async function confirmDelete() {
|
|||||||
|
|
||||||
if (itemsToDelete.length === 1) {
|
if (itemsToDelete.length === 1) {
|
||||||
const item = itemsToDelete[0]
|
const item = itemsToDelete[0]
|
||||||
const id = ctx.getItemId(item)
|
const id = item.id
|
||||||
markChanging(id)
|
markChanging(id)
|
||||||
await ctx.deleteItem(item)
|
await ctx.deleteItem(item)
|
||||||
removeFromSelection(id)
|
removeFromSelection(id)
|
||||||
@@ -347,14 +346,14 @@ async function confirmDelete() {
|
|||||||
itemsToDelete,
|
itemsToDelete,
|
||||||
async (item) => {
|
async (item) => {
|
||||||
await ctx.deleteItem(item)
|
await ctx.deleteItem(item)
|
||||||
removeFromSelection(ctx.getItemId(item))
|
removeFromSelection(item.id)
|
||||||
},
|
},
|
||||||
{ onComplete: clearSelection },
|
{ onComplete: clearSelection },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleToggleEnabledById(id: string, _value: boolean) {
|
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
|
if (!item) return
|
||||||
markChanging(id)
|
markChanging(id)
|
||||||
try {
|
try {
|
||||||
@@ -647,7 +646,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
|||||||
"
|
"
|
||||||
@click="cycleSortMode"
|
@click="cycleSortMode"
|
||||||
>
|
>
|
||||||
<ArrowDownZAIcon v-if="sortMode === 'alphabetical-desc'" /><ClockArrowDownIcon
|
<ArrowUpZAIcon v-if="sortMode === 'alphabetical-desc'" /><ClockArrowDownIcon
|
||||||
v-else-if="sortMode === 'date-added-newest'"
|
v-else-if="sortMode === 'date-added-newest'"
|
||||||
/><ClockArrowUpIcon
|
/><ClockArrowUpIcon
|
||||||
v-else-if="sortMode === 'date-added-oldest'"
|
v-else-if="sortMode === 'date-added-oldest'"
|
||||||
@@ -667,7 +666,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
|||||||
"
|
"
|
||||||
@click="cycleSortMode"
|
@click="cycleSortMode"
|
||||||
>
|
>
|
||||||
<ArrowDownZAIcon v-if="sortMode === 'alphabetical-desc'" /><ClockArrowDownIcon
|
<ArrowUpZAIcon v-if="sortMode === 'alphabetical-desc'" /><ClockArrowDownIcon
|
||||||
v-else-if="sortMode === 'date-added-newest'"
|
v-else-if="sortMode === 'date-added-newest'"
|
||||||
/><ClockArrowUpIcon
|
/><ClockArrowUpIcon
|
||||||
v-else-if="sortMode === 'date-added-oldest'"
|
v-else-if="sortMode === 'date-added-oldest'"
|
||||||
@@ -790,9 +789,13 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
|||||||
color-fill="text"
|
color-fill="text"
|
||||||
hover-color-fill="background"
|
hover-color-fill="background"
|
||||||
>
|
>
|
||||||
<button :disabled="ctx.isBusy.value" @click="promptUpdateSelected">
|
<button
|
||||||
|
v-tooltip="formatMessage(commonMessages.updateButton)"
|
||||||
|
:disabled="ctx.isBusy.value"
|
||||||
|
@click="promptUpdateSelected"
|
||||||
|
>
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
{{ formatMessage(commonMessages.updateButton) }}
|
<span class="bar-label">{{ formatMessage(commonMessages.updateButton) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
|
|
||||||
@@ -818,7 +821,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
|||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<ShareIcon />
|
<ShareIcon />
|
||||||
{{ formatMessage(messages.share) }}
|
<span class="bar-label">{{ formatMessage(messages.share) }}</span>
|
||||||
<DropdownIcon />
|
<DropdownIcon />
|
||||||
<template #share-names>
|
<template #share-names>
|
||||||
<TextCursorInputIcon />
|
<TextCursorInputIcon />
|
||||||
@@ -849,9 +852,13 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
|||||||
color-fill="text"
|
color-fill="text"
|
||||||
hover-color-fill="background"
|
hover-color-fill="background"
|
||||||
>
|
>
|
||||||
<button :disabled="ctx.isBusy.value" @click="showBulkDeleteModal">
|
<button
|
||||||
|
v-tooltip="formatMessage(commonMessages.deleteLabel)"
|
||||||
|
:disabled="ctx.isBusy.value"
|
||||||
|
@click="showBulkDeleteModal"
|
||||||
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
{{ formatMessage(commonMessages.deleteLabel) }}
|
<span class="bar-label">{{ formatMessage(commonMessages.deleteLabel) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</template>
|
</template>
|
||||||
@@ -862,6 +869,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
|||||||
:count="pendingDeletionItems.length"
|
:count="pendingDeletionItems.length"
|
||||||
:item-type="ctx.contentTypeLabel.value"
|
:item-type="ctx.contentTypeLabel.value"
|
||||||
:variant="ctx.deletionContext ?? 'instance'"
|
:variant="ctx.deletionContext ?? 'instance'"
|
||||||
|
:backup-tip="pendingDeletionItems.map((i) => i.project.title).join(', ')"
|
||||||
@delete="confirmDelete"
|
@delete="confirmDelete"
|
||||||
/>
|
/>
|
||||||
<ConfirmBulkUpdateModal
|
<ConfirmBulkUpdateModal
|
||||||
@@ -875,6 +883,7 @@ const confirmUnlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
|||||||
v-if="ctx.unlinkModpack"
|
v-if="ctx.unlinkModpack"
|
||||||
ref="confirmUnlinkModal"
|
ref="confirmUnlinkModal"
|
||||||
:server="ctx.deletionContext === 'server'"
|
:server="ctx.deletionContext === 'server'"
|
||||||
|
:backup-tip="ctx.modpack.value?.project.title"
|
||||||
@unlink="ctx.unlinkModpack!()"
|
@unlink="ctx.unlinkModpack!()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ export interface ContentManagerContext {
|
|||||||
disableAddContent?: Ref<boolean> | ComputedRef<boolean>
|
disableAddContent?: Ref<boolean> | ComputedRef<boolean>
|
||||||
disableAddContentTooltip?: string
|
disableAddContentTooltip?: string
|
||||||
|
|
||||||
// Identity & labelling
|
// Labelling
|
||||||
getItemId: (item: ContentItem) => string
|
|
||||||
contentTypeLabel: Ref<string> | ComputedRef<string>
|
contentTypeLabel: Ref<string> | ComputedRef<string>
|
||||||
|
|
||||||
// Core actions
|
// Core actions
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ export interface ContentItem extends Omit<
|
|||||||
ContentCardTableItem,
|
ContentCardTableItem,
|
||||||
'id' | 'projectLink' | 'disabled' | 'overflowOptions'
|
'id' | 'projectLink' | 'disabled' | 'overflowOptions'
|
||||||
> {
|
> {
|
||||||
|
id: string
|
||||||
file_name: string
|
file_name: string
|
||||||
file_path?: string
|
file_path?: string
|
||||||
hash?: string
|
|
||||||
size?: number
|
size?: number
|
||||||
project_type: string
|
project_type: string
|
||||||
has_update: boolean
|
has_update: boolean
|
||||||
|
|||||||
@@ -679,6 +679,9 @@ const messages = defineMessages({
|
|||||||
ref="modpackUpdateModal"
|
ref="modpackUpdateModal"
|
||||||
:downgrade="isUpdateDowngrade"
|
:downgrade="isUpdateDowngrade"
|
||||||
:server="ctx.isServer"
|
:server="ctx.isServer"
|
||||||
|
:backup-tip="
|
||||||
|
[ctx.modpack.value?.title, pendingUpdateVersion?.version_number].filter(Boolean).join(' ')
|
||||||
|
"
|
||||||
@confirm="handleModpackUpdateConfirm"
|
@confirm="handleModpackUpdateConfirm"
|
||||||
@cancel="handleModpackUpdateCancel"
|
@cancel="handleModpackUpdateCancel"
|
||||||
/>
|
/>
|
||||||
@@ -686,9 +689,15 @@ const messages = defineMessages({
|
|||||||
<ConfirmReinstallModal
|
<ConfirmReinstallModal
|
||||||
ref="reinstallModal"
|
ref="reinstallModal"
|
||||||
:server="ctx.isServer"
|
:server="ctx.isServer"
|
||||||
|
:backup-tip="ctx.modpack.value?.title"
|
||||||
@reinstall="handleReinstall"
|
@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
|
<ContentDiffModal
|
||||||
v-if="form.pendingPreview.value"
|
v-if="form.pendingPreview.value"
|
||||||
|
|||||||
@@ -487,6 +487,7 @@ function addonToContentItem(addon: Archon.Content.v1.Addon): ContentItem {
|
|||||||
link: `/${addon.owner.type}/${addon.owner.id}`,
|
link: `/${addon.owner.type}/${addon.owner.id}`,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
id: addon.id,
|
||||||
enabled: !addon.disabled,
|
enabled: !addon.disabled,
|
||||||
file_name: addon.filename,
|
file_name: addon.filename,
|
||||||
project_type: addon.kind,
|
project_type: addon.kind,
|
||||||
@@ -604,8 +605,8 @@ async function handleBulkUpdate(items: ContentItem[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUpdateItem(fileNameKey: string) {
|
async function handleUpdateItem(id: string) {
|
||||||
const item = contentItems.value.find((i) => i.file_name === fileNameKey)
|
const item = contentItems.value.find((i) => i.id === id)
|
||||||
if (!item?.has_update || !item.project?.id || !item.version?.id) return
|
if (!item?.has_update || !item.project?.id || !item.version?.id) return
|
||||||
|
|
||||||
updatingModpack.value = false
|
updatingModpack.value = false
|
||||||
@@ -872,7 +873,6 @@ provideContentManager({
|
|||||||
})
|
})
|
||||||
return filteredReasons.length > 0 ? formatMessage(filteredReasons[0].reason) : null
|
return filteredReasons.length > 0 ? formatMessage(filteredReasons[0].reason) : null
|
||||||
}),
|
}),
|
||||||
getItemId: (item) => item.file_path ?? item.file_name,
|
|
||||||
contentTypeLabel: type,
|
contentTypeLabel: type,
|
||||||
toggleEnabled: handleToggleEnabled,
|
toggleEnabled: handleToggleEnabled,
|
||||||
deleteItem: handleDeleteItem,
|
deleteItem: handleDeleteItem,
|
||||||
@@ -898,7 +898,7 @@ provideContentManager({
|
|||||||
mapToTableItem: (item) => {
|
mapToTableItem: (item) => {
|
||||||
const projectType = item.project_type ?? type.value
|
const projectType = item.project_type ?? type.value
|
||||||
return {
|
return {
|
||||||
id: item.file_path ?? item.file_name,
|
id: item.id,
|
||||||
project: item.project,
|
project: item.project,
|
||||||
projectLink: item.project?.id ? `/${projectType}/${item.project.id}` : undefined,
|
projectLink: item.project?.id ? `/${projectType}/${item.project.id}` : undefined,
|
||||||
version: item.version,
|
version: item.version,
|
||||||
@@ -962,6 +962,11 @@ provideContentManager({
|
|||||||
<ConfirmModpackUpdateModal
|
<ConfirmModpackUpdateModal
|
||||||
ref="modpackUpdateModal"
|
ref="modpackUpdateModal"
|
||||||
:downgrade="isModpackUpdateDowngrade"
|
:downgrade="isModpackUpdateDowngrade"
|
||||||
|
:backup-tip="
|
||||||
|
[modpack?.project.title, pendingModpackUpdateVersion?.version_number]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
"
|
||||||
server
|
server
|
||||||
@confirm="handleModpackUpdateConfirm"
|
@confirm="handleModpackUpdateConfirm"
|
||||||
@cancel="handleModpackUpdateCancel"
|
@cancel="handleModpackUpdateCancel"
|
||||||
|
|||||||
@@ -374,6 +374,12 @@
|
|||||||
"content.page-layout.uploading-files": {
|
"content.page-layout.uploading-files": {
|
||||||
"defaultMessage": "Uploading files ({completed}/{total})"
|
"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": {
|
"content.selection-bar.bulk.deleting": {
|
||||||
"defaultMessage": "Deleting {progress}/{total} {contentType}..."
|
"defaultMessage": "Deleting {progress}/{total} {contentType}..."
|
||||||
},
|
},
|
||||||
@@ -2151,7 +2157,7 @@
|
|||||||
"defaultMessage": "Creating backup"
|
"defaultMessage": "Creating backup"
|
||||||
},
|
},
|
||||||
"servers.backups.admonition.fallback-name": {
|
"servers.backups.admonition.fallback-name": {
|
||||||
"defaultMessage": "your backup"
|
"defaultMessage": "Your backup"
|
||||||
},
|
},
|
||||||
"servers.backups.admonition.restore-failed.description": {
|
"servers.backups.admonition.restore-failed.description": {
|
||||||
"defaultMessage": "Something went wrong while restoring from {backupName}. Please try again or contact support if the issue continues."
|
"defaultMessage": "Something went wrong while restoring from {backupName}. Please try again or contact support if the issue continues."
|
||||||
|
|||||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -350,6 +350,9 @@ importers:
|
|||||||
vue-confetti-explosion:
|
vue-confetti-explosion:
|
||||||
specifier: ^1.0.2
|
specifier: ^1.0.2
|
||||||
version: 1.0.2(vue@3.5.27(typescript@5.9.3))
|
version: 1.0.2(vue@3.5.27(typescript@5.9.3))
|
||||||
|
vue-router:
|
||||||
|
specifier: '*'
|
||||||
|
version: 4.6.4(vue@3.5.27(typescript@5.9.3))
|
||||||
vue-typed-virtual-list:
|
vue-typed-virtual-list:
|
||||||
specifier: ^1.0.10
|
specifier: ^1.0.10
|
||||||
version: 1.0.10(vue@3.5.27(typescript@5.9.3))
|
version: 1.0.10(vue@3.5.27(typescript@5.9.3))
|
||||||
@@ -8689,6 +8692,7 @@ packages:
|
|||||||
tar@7.5.7:
|
tar@7.5.7:
|
||||||
resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==}
|
resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
|
|
||||||
terser@5.46.0:
|
terser@5.46.0:
|
||||||
resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==}
|
resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==}
|
||||||
@@ -8975,6 +8979,7 @@ packages:
|
|||||||
|
|
||||||
unplugin-vue-router@0.19.2:
|
unplugin-vue-router@0.19.2:
|
||||||
resolution: {integrity: sha512-u5dgLBarxE5cyDK/hzJGfpCTLIAyiTXGlo85COuD4Nssj6G7NxS+i9mhCWz/1p/ud1eMwdcUbTXehQe41jYZUA==}
|
resolution: {integrity: sha512-u5dgLBarxE5cyDK/hzJGfpCTLIAyiTXGlo85COuD4Nssj6G7NxS+i9mhCWz/1p/ud1eMwdcUbTXehQe41jYZUA==}
|
||||||
|
deprecated: 'Merged into vuejs/router. Migrate: https://router.vuejs.org/guide/migration/v4-to-v5.html'
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@vue/compiler-sfc': ^3.5.17
|
'@vue/compiler-sfc': ^3.5.17
|
||||||
vue-router: ^4.6.0
|
vue-router: ^4.6.0
|
||||||
@@ -9406,8 +9411,8 @@ packages:
|
|||||||
vue-component-type-helpers@3.2.4:
|
vue-component-type-helpers@3.2.4:
|
||||||
resolution: {integrity: sha512-05lR16HeZDcDpB23ku5b5f1fBOoHqFnMiKRr2CiEvbG5Ux4Yi0McmQBOET0dR0nxDXosxyVqv67q6CzS3AK8rw==}
|
resolution: {integrity: sha512-05lR16HeZDcDpB23ku5b5f1fBOoHqFnMiKRr2CiEvbG5Ux4Yi0McmQBOET0dR0nxDXosxyVqv67q6CzS3AK8rw==}
|
||||||
|
|
||||||
vue-component-type-helpers@3.2.5:
|
vue-component-type-helpers@3.2.6:
|
||||||
resolution: {integrity: sha512-tkvNr+bU8+xD/onAThIe7CHFvOJ/BO6XCOrxMzeytJq40nTfpGDJuVjyCM8ccGZKfAbGk2YfuZyDMXM56qheZQ==}
|
resolution: {integrity: sha512-O02tnvIfOQVmnvoWwuSydwRoHjZVt8UEBR+2p4rT35p8GAy5VTlWP8o5qXfJR/GWCN0nVZoYWsVUvx2jwgdBmQ==}
|
||||||
|
|
||||||
vue-confetti-explosion@1.0.2:
|
vue-confetti-explosion@1.0.2:
|
||||||
resolution: {integrity: sha512-80OboM3/6BItIoZ6DpNcZFqGpF607kjIVc5af56oKgtFmt5yWehvJeoYhkzYlqxrqdBe0Ko4Ie3bWrmLau+dJw==}
|
resolution: {integrity: sha512-80OboM3/6BItIoZ6DpNcZFqGpF607kjIVc5af56oKgtFmt5yWehvJeoYhkzYlqxrqdBe0Ko4Ie3bWrmLau+dJw==}
|
||||||
@@ -12755,7 +12760,7 @@ snapshots:
|
|||||||
storybook: 10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
storybook: 10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
type-fest: 2.19.0
|
type-fest: 2.19.0
|
||||||
vue: 3.5.27(typescript@5.9.3)
|
vue: 3.5.27(typescript@5.9.3)
|
||||||
vue-component-type-helpers: 3.2.5
|
vue-component-type-helpers: 3.2.6
|
||||||
|
|
||||||
'@stripe/stripe-js@7.9.0': {}
|
'@stripe/stripe-js@7.9.0': {}
|
||||||
|
|
||||||
@@ -19524,7 +19529,7 @@ snapshots:
|
|||||||
|
|
||||||
vue-component-type-helpers@3.2.4: {}
|
vue-component-type-helpers@3.2.4: {}
|
||||||
|
|
||||||
vue-component-type-helpers@3.2.5: {}
|
vue-component-type-helpers@3.2.6: {}
|
||||||
|
|
||||||
vue-confetti-explosion@1.0.2(vue@3.5.27(typescript@5.9.3)):
|
vue-confetti-explosion@1.0.2(vue@3.5.27(typescript@5.9.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user