Files
Starlight_Lancher/packages/ui/src/layouts/shared/browse-tab/header.vue

157 lines
5.0 KiB
Vue

<script setup lang="ts">
import { BoxIcon, getLoaderIcon, LeftArrowIcon } from '@modrinth/assets'
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 ButtonStyled from '#ui/components/base/ButtonStyled.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
}>()
type SelectedProjectsLeaveResult = 'cancel' | 'discard' | 'install'
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 loaderIcon = computed(() => {
const loader = installContext.value?.loader
return loader ? getLoaderIcon(loader) : undefined
})
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"
/>
<div class="flex flex-col gap-2">
<div class="flex flex-wrap items-center justify-between gap-4">
<div class="flex min-w-0 items-center gap-4">
<ButtonStyled circular size="large">
<button :aria-label="installContext.backLabel" @click="handleBack">
<LeftArrowIcon />
</button>
</ButtonStyled>
<Avatar
v-if="iconSrc"
:src="iconSrc"
size="48px"
class="shrink-0"
:class="{
'!border-0 !rounded-none !bg-transparent !shadow-none': installContext.iconFrameless,
}"
/>
<div class="flex min-w-0 flex-col justify-center gap-1">
<h1 class="m-0 truncate text-2xl font-semibold leading-8 text-contrast">
{{ installContext.name }}
</h1>
<div
v-if="installContext.heading || installContext.gameVersion || installContext.loader"
class="flex flex-wrap items-center gap-2 text-base font-medium leading-6 text-primary"
>
<span v-if="installContext.heading">{{ installContext.heading }}</span>
<div
v-if="
installContext.heading && (installContext.gameVersion || installContext.loader)
"
class="h-1.5 w-1.5 shrink-0 rounded-full bg-current opacity-60"
/>
<div v-if="installContext.gameVersion" class="flex items-center gap-1.5">
<BoxIcon class="h-4 w-4" />
{{ installContext.gameVersion }}
</div>
<div
v-if="installContext.gameVersion && installContext.loader"
class="h-1.5 w-1.5 shrink-0 rounded-full bg-current opacity-60"
/>
<div v-if="installContext.loader" class="flex items-center gap-1.5 capitalize">
<component :is="loaderIcon" v-if="loaderIcon" class="h-4 w-4" />
{{ formatLoaderLabel(installContext.loader) }}
</div>
</div>
</div>
</div>
</div>
</div>
<Admonition v-if="installContext.warning" type="warning" class="mb-1">
{{ installContext.warning }}
</Admonition>
</template>
</template>