feat:移除了弹窗,服务器添加sls
This commit is contained in:
@ -0,0 +1,298 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckIcon, DownloadIcon, PlusIcon, TrashIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, defineMessages, NewModal, useVIntl } from '@modrinth/ui'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import InstancePickerList from '@/components/ui/instance/InstancePickerList.vue'
|
||||
import InstanceIcon from '@/components/ui/InstanceIcon.vue'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
|
||||
const props = defineProps<{
|
||||
instances: GameInstance[]
|
||||
selectedInstance: GameInstance | null
|
||||
selectedCount: number
|
||||
installCurrent: () => Promise<boolean>
|
||||
clearCurrent: () => void
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [instance: GameInstance]
|
||||
cancelSwitch: []
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const router = useRouter()
|
||||
const pickerModal = ref<InstanceType<typeof NewModal>>()
|
||||
const switchModal = ref<InstanceType<typeof NewModal>>()
|
||||
const instancePicker = ref<InstanceType<typeof InstancePickerList>>()
|
||||
const pendingInstance = ref<GameInstance | null>(null)
|
||||
const installingCurrent = ref(false)
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'app.browse.instance-selector.title',
|
||||
defaultMessage: 'Choose an instance',
|
||||
},
|
||||
search: {
|
||||
id: 'app.browse.instance-selector.search',
|
||||
defaultMessage: 'Search instances',
|
||||
},
|
||||
noInstances: {
|
||||
id: 'app.browse.instance-selector.no-instances',
|
||||
defaultMessage: 'Create an instance before installing content',
|
||||
},
|
||||
noResults: {
|
||||
id: 'app.browse.instance-selector.no-results',
|
||||
defaultMessage: 'No matching instances',
|
||||
},
|
||||
select: {
|
||||
id: 'app.browse.instance-selector.select',
|
||||
defaultMessage: 'Choose {name}',
|
||||
},
|
||||
create: {
|
||||
id: 'app.browse.instance-selector.create',
|
||||
defaultMessage: 'Create instance',
|
||||
},
|
||||
switchTitle: {
|
||||
id: 'app.browse.instance-selector.switch-title',
|
||||
defaultMessage: 'Switch installation target?',
|
||||
},
|
||||
switchDescription: {
|
||||
id: 'app.browse.instance-selector.switch-description',
|
||||
defaultMessage:
|
||||
'{count, plural, one {# selected project is} other {# selected projects are}} resolved for the current target. Switching will discard the resolved versions.',
|
||||
},
|
||||
currentTarget: {
|
||||
id: 'app.browse.instance-selector.current-target',
|
||||
defaultMessage: 'Current target',
|
||||
},
|
||||
newTarget: {
|
||||
id: 'app.browse.instance-selector.new-target',
|
||||
defaultMessage: 'New target',
|
||||
},
|
||||
installCurrent: {
|
||||
id: 'app.browse.instance-selector.install-current',
|
||||
defaultMessage: 'Install to current instance',
|
||||
},
|
||||
installingCurrent: {
|
||||
id: 'app.browse.instance-selector.installing-current',
|
||||
defaultMessage: 'Preparing installation…',
|
||||
},
|
||||
clearAndSwitch: {
|
||||
id: 'app.browse.instance-selector.clear-and-switch',
|
||||
defaultMessage: 'Clear and switch',
|
||||
},
|
||||
cancel: {
|
||||
id: 'app.browse.instance-selector.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
},
|
||||
})
|
||||
|
||||
function show() {
|
||||
instancePicker.value?.reset()
|
||||
pickerModal.value?.show()
|
||||
void nextTick(() => instancePicker.value?.focus())
|
||||
}
|
||||
|
||||
function choose(instance: GameInstance) {
|
||||
if (instance.id === props.selectedInstance?.id) {
|
||||
pickerModal.value?.hide()
|
||||
return
|
||||
}
|
||||
if (props.selectedCount > 0 && props.selectedInstance) {
|
||||
pendingInstance.value = instance
|
||||
pickerModal.value?.hide()
|
||||
void nextTick(() => switchModal.value?.show())
|
||||
return
|
||||
}
|
||||
emit('select', instance)
|
||||
pickerModal.value?.hide()
|
||||
}
|
||||
|
||||
function requestSwitch(instance: GameInstance) {
|
||||
choose(instance)
|
||||
}
|
||||
|
||||
async function installAndSwitch() {
|
||||
if (!pendingInstance.value || installingCurrent.value) return
|
||||
installingCurrent.value = true
|
||||
try {
|
||||
if (!(await props.installCurrent())) return
|
||||
const next = pendingInstance.value
|
||||
pendingInstance.value = null
|
||||
installingCurrent.value = false
|
||||
switchModal.value?.hide()
|
||||
emit('select', next)
|
||||
} finally {
|
||||
installingCurrent.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function clearAndSwitch() {
|
||||
if (!pendingInstance.value) return
|
||||
props.clearCurrent()
|
||||
const next = pendingInstance.value
|
||||
pendingInstance.value = null
|
||||
switchModal.value?.hide()
|
||||
emit('select', next)
|
||||
}
|
||||
|
||||
function createInstance() {
|
||||
pickerModal.value?.hide()
|
||||
void router.push('/create')
|
||||
}
|
||||
|
||||
function cancelSwitch() {
|
||||
pendingInstance.value = null
|
||||
switchModal.value?.hide()
|
||||
emit('cancelSwitch')
|
||||
}
|
||||
|
||||
defineExpose({ show, requestSwitch })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NewModal
|
||||
ref="pickerModal"
|
||||
:header="formatMessage(messages.title)"
|
||||
max-width="560px"
|
||||
width="min(560px, calc(100vw - 2rem))"
|
||||
scrollable
|
||||
max-content-height="min(28rem, calc(100dvh - 18rem))"
|
||||
actions-divider
|
||||
>
|
||||
<div class="flex min-w-0 flex-col">
|
||||
<InstancePickerList
|
||||
ref="instancePicker"
|
||||
:instances="instances"
|
||||
:search-placeholder="formatMessage(messages.search)"
|
||||
:no-instances-message="formatMessage(messages.noInstances)"
|
||||
:no-matches-message="formatMessage(messages.noResults)"
|
||||
:select-label="(instance) => formatMessage(messages.select, { name: instance.name })"
|
||||
@select="choose"
|
||||
>
|
||||
<template #action="{ instance }">
|
||||
<CheckIcon
|
||||
v-if="instance.id === selectedInstance?.id"
|
||||
class="size-5 shrink-0 text-brand"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</template>
|
||||
</InstancePickerList>
|
||||
</div>
|
||||
<template #actions>
|
||||
<div class="flex justify-start">
|
||||
<ButtonStyled type="transparent">
|
||||
<button type="button" @click="createInstance">
|
||||
<PlusIcon />
|
||||
{{ formatMessage(messages.create) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
</NewModal>
|
||||
|
||||
<NewModal
|
||||
ref="switchModal"
|
||||
:header="formatMessage(messages.switchTitle)"
|
||||
width="min(600px, calc(100vw - 2rem))"
|
||||
max-width="600px"
|
||||
scrollable
|
||||
max-content-height="min(32rem, 70vh)"
|
||||
actions-divider
|
||||
:disable-close="installingCurrent"
|
||||
>
|
||||
<div v-if="pendingInstance && selectedInstance" class="flex min-w-0 flex-col gap-5">
|
||||
<div
|
||||
class="grid min-w-0 gap-3 sm:grid-cols-[minmax(0,1fr)_1px_minmax(0,1fr)] sm:items-stretch"
|
||||
>
|
||||
<div class="flex min-w-0 flex-col gap-2 px-1 py-1">
|
||||
<span class="text-xs font-semibold text-secondary">
|
||||
{{ formatMessage(messages.currentTarget) }}
|
||||
</span>
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<InstanceIcon
|
||||
class="size-10 shrink-0"
|
||||
:icon-path="selectedInstance.icon_path"
|
||||
:instance-id="selectedInstance.id"
|
||||
:loader="selectedInstance.loader"
|
||||
/>
|
||||
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<span class="truncate font-semibold text-contrast">{{ selectedInstance.name }}</span>
|
||||
<span class="truncate text-sm capitalize text-secondary">
|
||||
{{ selectedInstance.loader }} {{ selectedInstance.game_version }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-hidden="true" class="h-px w-full bg-surface-4 sm:h-auto sm:w-px" />
|
||||
|
||||
<div class="flex min-w-0 flex-col gap-2 px-1 py-1">
|
||||
<span class="text-xs font-semibold text-secondary">
|
||||
{{ formatMessage(messages.newTarget) }}
|
||||
</span>
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<InstanceIcon
|
||||
class="size-10 shrink-0"
|
||||
:icon-path="pendingInstance.icon_path"
|
||||
:instance-id="pendingInstance.id"
|
||||
:loader="pendingInstance.loader"
|
||||
/>
|
||||
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<span class="truncate font-semibold text-contrast">{{ pendingInstance.name }}</span>
|
||||
<span class="truncate text-sm capitalize text-secondary">
|
||||
{{ pendingInstance.loader }} {{ pendingInstance.game_version }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="m-0 text-sm text-secondary">
|
||||
{{ formatMessage(messages.switchDescription, { count: selectedCount }) }}
|
||||
</p>
|
||||
</div>
|
||||
<template #actions>
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-end">
|
||||
<ButtonStyled type="outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full sm:w-auto"
|
||||
:disabled="installingCurrent"
|
||||
@click="cancelSwitch"
|
||||
>
|
||||
{{ formatMessage(messages.cancel) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red" type="outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full sm:w-auto"
|
||||
:disabled="installingCurrent"
|
||||
@click="clearAndSwitch"
|
||||
>
|
||||
<TrashIcon />
|
||||
{{ formatMessage(messages.clearAndSwitch) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full sm:w-auto"
|
||||
:disabled="installingCurrent"
|
||||
@click="installAndSwitch"
|
||||
>
|
||||
<DownloadIcon />
|
||||
{{
|
||||
formatMessage(
|
||||
installingCurrent ? messages.installingCurrent : messages.installCurrent,
|
||||
)
|
||||
}}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
</NewModal>
|
||||
</template>
|
||||
Reference in New Issue
Block a user