mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 12:05:53 +00:00
* add account switcher, migrate context menus to overflow menu system, update version filter controls, only pad instance icons * app account switcher, improve some context menus, theme stuff * prepr * handle reauthentication * fix a couple sign in issues, admin page cleanup, fix org status badges, fix official account badge, open in local keybind, publish plus icons * rename generic menus to ButtonMenu (& types) * fix hydration issue w/ dropdowns + middleware error
189 lines
5.4 KiB
Vue
189 lines
5.4 KiB
Vue
<script setup lang="ts">
|
|
import { LeftArrowIcon, TagCategoryGamepad2Icon as Gamepad2Icon } from '@modrinth/assets'
|
|
import type { Component } from 'vue'
|
|
import { computed, ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
import Admonition from '#ui/components/base/Admonition.vue'
|
|
import Avatar from '#ui/components/base/Avatar.vue'
|
|
import { IconButton } from '#ui/components/base/buttons'
|
|
import PageHeader from '#ui/components/base/page-header/index.vue'
|
|
import PageHeaderMetadata from '#ui/components/base/page-header/metadata/index.vue'
|
|
import PageHeaderMetadataItem from '#ui/components/base/page-header/metadata/page-header-metadata-item.vue'
|
|
import TagIcon from '#ui/components/base/TagIcon.vue'
|
|
import { useServerImage } from '#ui/composables/use-server-image'
|
|
import { formatLoaderLabel } from '#ui/utils/loaders'
|
|
|
|
import SelectedProjectsLeaveModal from './components/SelectedProjectsLeaveModal.vue'
|
|
import { injectBrowseManager } from './providers/browse-manager'
|
|
import type { BrowseInstallContext } from './types'
|
|
|
|
const MEDAL_ICON_URL = 'https://cdn-raw.modrinth.com/medal_icon.webp'
|
|
|
|
const router = useRouter()
|
|
const props = defineProps<{
|
|
installContext?: BrowseInstallContext | null
|
|
divider?: boolean
|
|
bottomPadding?: boolean
|
|
}>()
|
|
type SelectedProjectsLeaveResult = 'cancel' | 'discard' | 'install'
|
|
type BrowseHeaderMetadataItem = {
|
|
id: string
|
|
label: string
|
|
icon?: Component
|
|
iconProps?: Record<string, unknown>
|
|
class?: string
|
|
}
|
|
|
|
const ctx = injectBrowseManager(null)
|
|
const installContext = computed(() => props.installContext ?? ctx?.installContext?.value ?? null)
|
|
const selectedProjectsLeaveModal = ref<InstanceType<typeof SelectedProjectsLeaveModal>>()
|
|
|
|
const serverId = computed(() => installContext.value?.serverId ?? '')
|
|
const upstream = computed(() => installContext.value?.upstream ?? null)
|
|
|
|
const { image: fetchedIcon } = useServerImage(serverId, upstream, {
|
|
enabled: computed(() => !!installContext.value?.serverId),
|
|
})
|
|
|
|
const iconSrc = computed(() => {
|
|
if (installContext.value?.isMedal) return MEDAL_ICON_URL
|
|
return fetchedIcon.value ?? installContext.value?.iconSrc ?? null
|
|
})
|
|
|
|
const isInstanceIcon = computed(() => !installContext.value?.serverId)
|
|
|
|
const metadataItems = computed(() => {
|
|
const context = installContext.value
|
|
if (!context) return []
|
|
|
|
const items: BrowseHeaderMetadataItem[] = []
|
|
if (context.heading) {
|
|
items.push({
|
|
id: 'heading',
|
|
label: context.heading,
|
|
class: '!text-primary',
|
|
})
|
|
}
|
|
if (context.gameVersion) {
|
|
items.push({
|
|
id: 'game-version',
|
|
label: `Minecraft ${context.gameVersion}`,
|
|
icon: Gamepad2Icon,
|
|
class: '!text-primary',
|
|
})
|
|
}
|
|
if (context.loader) {
|
|
const loaderName = formatLoaderLabel(context.loader)
|
|
const loaderLabel = [loaderName, context.loaderVersion].filter(Boolean).join(' ')
|
|
items.push({
|
|
id: 'loader',
|
|
label: loaderLabel,
|
|
icon: TagIcon,
|
|
iconProps: { tag: loaderName, enforceType: 'loader' },
|
|
class: '!text-primary',
|
|
})
|
|
}
|
|
return items
|
|
})
|
|
|
|
const selectedCount = computed(() => installContext.value?.selectedProjects?.length ?? 0)
|
|
const isInstallingSelected = computed(() => installContext.value?.isInstallingSelected ?? false)
|
|
|
|
async function handleBack() {
|
|
const context = installContext.value
|
|
if (!context) return
|
|
|
|
if (selectedCount.value > 0 && !isInstallingSelected.value) {
|
|
if (context.skipNonEssentialWarnings) {
|
|
await handleSelectedProjectsLeaveResult('discard', context)
|
|
return
|
|
}
|
|
|
|
const result = await selectedProjectsLeaveModal.value?.prompt()
|
|
await handleSelectedProjectsLeaveResult(result ?? 'cancel', context)
|
|
return
|
|
}
|
|
|
|
const shouldNavigate = await context.onBack?.()
|
|
if (shouldNavigate === false) return
|
|
|
|
await router.push(context.backUrl)
|
|
}
|
|
|
|
async function handleSelectedProjectsLeaveResult(
|
|
result: SelectedProjectsLeaveResult,
|
|
context: BrowseInstallContext,
|
|
) {
|
|
if (result === 'cancel') return
|
|
if (result === 'install') {
|
|
const shouldNavigate = await context.installSelected?.()
|
|
if (shouldNavigate === false) return
|
|
return
|
|
}
|
|
|
|
if (context.discardSelectedAndBack) {
|
|
await context.discardSelectedAndBack()
|
|
return
|
|
}
|
|
|
|
await (context.clearSelected ?? context.clearQueued)?.()
|
|
await router.push(context.backUrl)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<template v-if="installContext">
|
|
<SelectedProjectsLeaveModal
|
|
ref="selectedProjectsLeaveModal"
|
|
:count="selectedCount"
|
|
:installing="isInstallingSelected"
|
|
/>
|
|
<PageHeader
|
|
:title="installContext.name"
|
|
:divider="props.divider ?? false"
|
|
:bottom-padding="props.bottomPadding ?? false"
|
|
main-class="items-center"
|
|
title-class="leading-8"
|
|
truncate-title
|
|
>
|
|
<template #leading>
|
|
<IconButton
|
|
v-tooltip="installContext.backLabel"
|
|
size="xl"
|
|
:label="installContext.backLabel"
|
|
native-type="button"
|
|
@click="handleBack"
|
|
>
|
|
<LeftArrowIcon />
|
|
</IconButton>
|
|
<Avatar
|
|
v-if="iconSrc"
|
|
:src="iconSrc"
|
|
:alt="installContext.name"
|
|
size="48px"
|
|
class="shrink-0"
|
|
:pad-transparent-corners="isInstanceIcon"
|
|
/>
|
|
</template>
|
|
|
|
<template v-if="metadataItems.length" #metadata>
|
|
<PageHeaderMetadata>
|
|
<PageHeaderMetadataItem
|
|
v-for="item in metadataItems"
|
|
:key="item.id"
|
|
:icon="item.icon"
|
|
:icon-props="item.iconProps"
|
|
:class="item.class"
|
|
>
|
|
{{ item.label }}
|
|
</PageHeaderMetadataItem>
|
|
</PageHeaderMetadata>
|
|
</template>
|
|
</PageHeader>
|
|
<Admonition v-if="installContext.warning" type="warning" class="mt-4 mb-1">
|
|
{{ installContext.warning }}
|
|
</Admonition>
|
|
</template>
|
|
</template>
|