Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 48 additions & 18 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
} from '@modrinth/api-client'
import {
ArrowBigUpDashIcon,
ChevronLeftIcon,
ChevronRightIcon,
CompassIcon,
HomeIcon,
LeftArrowIcon,
LibraryIcon,
LogInIcon,
LogOutIcon,
Expand Down Expand Up @@ -46,6 +47,7 @@ import {
provideNotificationManager,
providePageContext,
providePopupNotificationManager,
TextLogo,
useDebugLogger,
useFormatBytes,
useHostingIntercom,
Expand All @@ -63,7 +65,6 @@ import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'

import ModrinthAppLogo from '@/assets/modrinth_app.svg?component'
import AccountsCard from '@/components/ui/AccountsCard.vue'
import AppActionBar from '@/components/ui/AppActionBar.vue'
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
Expand Down Expand Up @@ -129,6 +130,7 @@ import {
openAppUpdateChangelog,
setAppUpdateActions,
} from '@/providers/app-update.ts'
import { createBreadcrumbManager, provideBreadcrumbManager } from '@/providers/breadcrumbs'
import { createContentInstall, provideContentInstall } from '@/providers/content-install'
import {
provideAppUpdateDownloadProgress,
Expand All @@ -151,6 +153,19 @@ import { appSettingsModalOpenProfileKey } from './providers/app-settings-modal'
const themeStore = useTheming()
const router = useRouter()
const route = useRoute()
const breadcrumbManager = createBreadcrumbManager()
provideBreadcrumbManager(breadcrumbManager)
const canNavigateBack = ref(false)
const canNavigateForward = ref(false)

function updateHistoryNavigationState() {
const historyState = window.history.state
canNavigateBack.value = historyState?.back != null
canNavigateForward.value = historyState?.forward != null
}

updateHistoryNavigationState()

const APP_LEFT_NAV_WIDTH = '4rem'
const APP_SIDEBAR_WIDTH = 300
const INTERCOM_BUBBLE_DEFAULT_PADDING = 20
Expand Down Expand Up @@ -655,6 +670,7 @@ router.beforeEach(() => {
routerToken = loading.begin()
})
router.afterEach((to, from, failure) => {
updateHistoryNavigationState()
trackEvent('PageView', {
path: to.path,
fromPath: from.path,
Expand Down Expand Up @@ -1623,23 +1639,37 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
</NavButton>
</div>
<div data-tauri-drag-region class="app-grid-statusbar bg-bg-raised h-[--top-bar-height] flex">
<div data-tauri-drag-region class="flex min-w-0 flex-1 overflow-hidden p-3">
<ModrinthAppLogo class="h-full w-auto shrink-0 text-contrast pointer-events-none" />
<div data-tauri-drag-region class="flex shrink-0 items-center gap-1 ml-3">
<button
class="cursor-pointer p-0 m-0 text-contrast border-none outline-none bg-button-bg rounded-full flex items-center justify-center w-6 h-6 hover:brightness-75 transition-all"
@click="router.back()"
>
<LeftArrowIcon />
</button>
<button
class="cursor-pointer p-0 m-0 text-contrast border-none outline-none bg-button-bg rounded-full flex items-center justify-center w-6 h-6 hover:brightness-75 transition-all"
@click="router.forward()"
>
<RightArrowIcon />
</button>
<div data-tauri-drag-region class="flex min-w-0 flex-1 items-center overflow-hidden p-2">
<TextLogo class="h-7 w-auto shrink-0 text-contrast pointer-events-none" />
<div data-tauri-drag-region class="ml-2 flex shrink-0 items-center gap-2">
<ButtonStyled type="outlined" circular>
<button
class="!h-7 !min-w-7 !w-7 !border !border-surface-4 !p-0 !opacity-100"
:disabled="!canNavigateBack"
aria-label="Go back"
@click="router.back()"
>
<ChevronLeftIcon
class="!size-4 !text-primary"
:class="{ 'opacity-20': !canNavigateBack }"
/>
</button>
</ButtonStyled>
<ButtonStyled type="outlined" circular>
<button
class="!h-7 !min-w-7 !w-7 !border !border-surface-4 !p-0 !opacity-100"
:disabled="!canNavigateForward"
aria-label="Go forward"
@click="router.forward()"
>
<ChevronRightIcon
class="!size-4 !text-primary"
:class="{ 'opacity-20': !canNavigateForward }"
/>
</button>
</ButtonStyled>
</div>
<Breadcrumbs class="pt-[2px]" />
<Breadcrumbs />
</div>
<section data-tauri-drag-region class="flex shrink-0 ml-auto items-center">
<ButtonStyled
Expand Down
97 changes: 49 additions & 48 deletions apps/app-frontend/src/components/ui/Breadcrumbs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div
ref="outerRef"
data-tauri-drag-region
class="min-w-0 overflow-hidden pl-3"
class="min-w-0 overflow-hidden pl-4"
:class="{ 'breadcrumb-fade-mask': isOverflowing }"
:style="isOverflowing ? { '--scroll-distance': `-${overflowAmount}px` } : undefined"
@mouseenter="onMouseEnter"
Expand All @@ -11,65 +11,62 @@
<div
ref="innerRef"
data-tauri-drag-region
class="flex w-fit items-center gap-1"
class="flex w-fit items-center gap-2 pr-4"
:class="{ 'breadcrumbs-scroll': isAnimating }"
@animationiteration="onAnimationIteration"
>
{{ breadcrumbData.resetToNames(breadcrumbs) }}
<template v-for="breadcrumb in breadcrumbs" :key="breadcrumb.name">
<router-link
v-if="breadcrumb.link"
:to="{
path: breadcrumb.link.replace('{id}', encodeURIComponent($route.params.id as string)),
query: breadcrumb.query,
}"
class="shrink-0 whitespace-nowrap text-primary"
<template v-for="(breadcrumb, index) in breadcrumbs" :key="breadcrumb.slot">
<component
:is="index < breadcrumbs.length - 1 && breadcrumb.to ? RouterLink : 'span'"
v-bind="index < breadcrumbs.length - 1 && breadcrumb.to ? { to: breadcrumb.to } : {}"
:data-tauri-drag-region="index === breadcrumbs.length - 1 ? '' : undefined"
class="flex shrink-0 items-center gap-1.5 whitespace-nowrap text-base font-medium leading-6"
:class="
index === breadcrumbs.length - 1
? 'cursor-default select-none text-contrast'
: 'text-primary hover:text-contrast'
"
:aria-current="index === breadcrumbs.length - 1 ? 'page' : undefined"
>
{{ resolveLabel(breadcrumb.name) }}
</router-link>
<span
v-else
<Avatar
v-if="breadcrumb.visual?.type === 'image'"
:src="breadcrumb.visual.src"
:alt="breadcrumb.visual.alt ?? breadcrumb.label"
:circle="breadcrumb.visual.circle"
:tint-by="breadcrumb.visual.tintBy ?? breadcrumb.id"
size="20px"
no-shadow
raised
class="inline-block shrink-0 align-middle"
:class="{ '!rounded-md': !breadcrumb.visual.circle }"
/>
<component
:is="breadcrumb.visual.component"
v-else-if="breadcrumb.visual?.type === 'icon'"
class="size-5 shrink-0 text-primary"
aria-hidden="true"
/>
<span>{{ breadcrumb.label }}</span>
</component>
<ChevronRightIcon
v-if="index < breadcrumbs.length - 1"
data-tauri-drag-region
class="shrink-0 whitespace-nowrap text-contrast font-semibold cursor-default select-none"
>
{{ resolveLabel(breadcrumb.name) }}
</span>
<ChevronRightIcon v-if="breadcrumb.link" data-tauri-drag-region class="w-5 h-5 shrink-0" />
class="size-5 shrink-0 text-primary"
/>
</template>
</div>
</div>
</template>

<script setup lang="ts">
import { ChevronRightIcon } from '@modrinth/assets'
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'

import { useBreadcrumbs } from '@/store/breadcrumbs'

interface Breadcrumb {
name: string
link?: string
query?: Record<string, string>
}
import { Avatar } from '@modrinth/ui'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { RouterLink } from 'vue-router'

const route = useRoute()
const breadcrumbData = useBreadcrumbs()

const breadcrumbs = computed<Breadcrumb[]>(() => {
const additionalContext =
route.meta.useContext === true
? breadcrumbData.context
: route.meta.useRootContext === true
? breadcrumbData.rootContext
: null
const crumbs = (route.meta.breadcrumb ?? []) as Breadcrumb[]
return additionalContext ? [additionalContext as Breadcrumb, ...crumbs] : crumbs
})
import { injectBreadcrumbManager } from '@/providers/breadcrumbs'

function resolveLabel(name: string): string {
return name.charAt(0) === '?' ? breadcrumbData.getName(name.slice(1)) : name
}
const { entries: breadcrumbs } = injectBreadcrumbManager()

// Overflow detection
const outerRef = ref<HTMLDivElement | null>(null)
Expand All @@ -83,9 +80,13 @@ let stopping = false

function checkOverflow() {
if (!outerRef.value || !innerRef.value) return
const overflow = innerRef.value.scrollWidth - outerRef.value.clientWidth
const outerStyles = window.getComputedStyle(outerRef.value)
const horizontalPadding =
Number.parseFloat(outerStyles.paddingLeft) + Number.parseFloat(outerStyles.paddingRight)
const availableWidth = outerRef.value.clientWidth - horizontalPadding
const overflow = innerRef.value.scrollWidth - availableWidth
isOverflowing.value = overflow > 0
overflowAmount.value = overflow + 12
overflowAmount.value = overflow
}

function onMouseEnter() {
Expand Down
6 changes: 6 additions & 0 deletions apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup>
import { SpinnerIcon } from '@modrinth/assets'
import { Avatar, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import { convertFileSrc } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import { computed, onMounted, onUnmounted, ref } from 'vue'
Expand All @@ -14,6 +15,7 @@ const APPROX_USED_VERTICAL_SPACE = 513 // doesn't need to be exact lol just clos
const STORAGE_KEY = 'modrinth-quick-instance-count'

const { handleError } = injectNotificationManager()
const queryClient = useQueryClient()

const { formatMessage } = useVIntl()

Expand Down Expand Up @@ -120,6 +122,10 @@ const onDividerPointerUp = (event) => {
const getInstances = async () => {
const instances = await list().catch(handleError)

for (const instance of instances) {
queryClient.setQueryData(['instances', 'summary', instance.id], instance)
}

allInstances.value = instances.sort((a, b) => {
const dateACreated = dayjs(a.created)
const dateAPlayed = a.last_played ? dayjs(a.last_played) : dayjs(0)
Expand Down
6 changes: 6 additions & 0 deletions apps/app-frontend/src/locales/en-US/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@
"app.browse.back-to-instance": {
"message": "Back to instance"
},
"app.browse.discover-project-type": {
"message": "Discover {projectType}"
},
"app.browse.discover-servers": {
"message": "Discover servers"
},
Expand Down Expand Up @@ -803,6 +806,9 @@
"app.project.install-context.back-to-browse": {
"message": "Back to discover"
},
"app.project.install-context.back-to-instance": {
"message": "Back to instance"
},
"app.project.version.all-versions": {
"message": "All versions"
},
Expand Down
Loading
Loading