Skip to content
Merged
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
48 changes: 34 additions & 14 deletions apps/app-frontend/src/components/ui/world/RecentWorldsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useVIntl,
} from '@modrinth/ui'
import { useQueries, useQuery, useQueryClient } from '@tanstack/vue-query'
import { useResizeObserver } from '@vueuse/core'
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { computed, onUnmounted, ref, watch } from 'vue'
Expand Down Expand Up @@ -66,26 +67,36 @@ const gameVersions = computed(() => gameVersionsQuery.data.value ?? [])

const MAX_JUMP_BACK_IN = 5
const MAX_NEW_INSTANCES = 3
const ITEM_DRAG_DISTANCE = 92
const ROW_DRAG_DISTANCE = 92
const STORAGE_KEY = 'modrinth-jump-back-in-count'

const itemsGrid = ref<HTMLElement>()
const columnCount = ref(1)
useResizeObserver(itemsGrid, () => {
if (!itemsGrid.value) return
columnCount.value = getComputedStyle(itemsGrid.value).gridTemplateColumns.split(' ').length
})

const storedVisibleCount = Number.parseInt(localStorage.getItem(STORAGE_KEY) ?? '', 10)
const visibleItemLimit = ref(
Number.isFinite(storedVisibleCount)
? Math.max(1, Math.min(storedVisibleCount, MAX_JUMP_BACK_IN))
: MAX_JUMP_BACK_IN,
)
const maxVisibleItems = computed(() => Math.min(jumpBackInItems.value.length, MAX_JUMP_BACK_IN))
const visibleItemCount = computed(() => Math.min(visibleItemLimit.value, maxVisibleItems.value))
const minVisibleItems = computed(() => Math.min(columnCount.value, maxVisibleItems.value))
const visibleItemCount = computed(() =>
Math.max(minVisibleItems.value, Math.min(visibleItemLimit.value, maxVisibleItems.value)),
)
const visibleJumpBackInItems = computed(() =>
jumpBackInItems.value.slice(0, visibleItemCount.value),
)
const canResize = computed(() => maxVisibleItems.value > 1)
const canResize = computed(() => maxVisibleItems.value > minVisibleItems.value)
const resizing = ref(false)
const showOverdrag = ref(false)

function setVisibleItemLimit(count: number) {
const clamped = Math.max(1, Math.min(count, maxVisibleItems.value))
const clamped = Math.max(minVisibleItems.value, Math.min(count, maxVisibleItems.value))
if (clamped >= maxVisibleItems.value) {
visibleItemLimit.value = MAX_JUMP_BACK_IN
localStorage.removeItem(STORAGE_KEY)
Expand All @@ -95,9 +106,15 @@ function setVisibleItemLimit(count: number) {
}
}

function adjustVisibleItemLimit(delta: number) {
const target = visibleItemCount.value + delta
if (target < 1 || target > maxVisibleItems.value) {
function getRowResizeTarget(count: number, rowDelta: number) {
return rowDelta < 0
? (Math.ceil(count / columnCount.value) + rowDelta) * columnCount.value
: count + rowDelta * columnCount.value
}

function adjustVisibleItemLimit(rowDelta: number) {
const target = getRowResizeTarget(visibleItemCount.value, rowDelta)
if (target < minVisibleItems.value || target > maxVisibleItems.value) {
flashOverdrag()
}
setVisibleItemLimit(target)
Expand Down Expand Up @@ -140,8 +157,9 @@ function onResizePointerDown(event: PointerEvent) {

function onResizePointerMove(event: PointerEvent) {
if (!resizing.value) return
const target = dragStartCount + Math.round((event.clientY - dragStartY) / ITEM_DRAG_DISTANCE)
const isOverdragging = target < 1 || target > maxVisibleItems.value
const rowDelta = Math.round((event.clientY - dragStartY) / ROW_DRAG_DISTANCE)
const target = getRowResizeTarget(dragStartCount, rowDelta)
const isOverdragging = target < minVisibleItems.value || target > maxVisibleItems.value
if (isOverdragging && !wasOverdragging) {
flashOverdrag()
}
Expand Down Expand Up @@ -180,8 +198,10 @@ type JumpBackInItem = InstanceJumpBackInItem | WorldJumpBackInItem

const showWorlds = computed(() => appSettings.showJumpIn)

const recentWorldsOptions = recentWorldsQueryOptions(MAX_JUMP_BACK_IN)
const recentWorldsQuery = useQuery({
...recentWorldsQueryOptions(MAX_JUMP_BACK_IN),
...recentWorldsOptions,
queryKey: [...recentWorldsOptions.queryKey] as const,
enabled: showWorlds,
})
const loading = computed(() => showWorlds.value && recentWorldsQuery.isLoading.value)
Expand Down Expand Up @@ -423,7 +443,7 @@ onUnmounted(() => {
<div v-if="loading" class="text-center py-4">
<LoaderCircleIcon class="mx-auto size-8 animate-spin text-contrast" />
</div>
<div v-else class="grid-when-huge relative flex w-full flex-col gap-3">
<div v-else ref="itemsGrid" class="grid-when-huge relative flex w-full flex-col gap-3">
<TransitionGroup name="jump-back-in-item">
<div
v-for="item in visibleJumpBackInItems"
Expand Down Expand Up @@ -501,17 +521,17 @@ onUnmounted(() => {
tabindex="0"
aria-orientation="horizontal"
:aria-label="formatMessage(messages.resize)"
:aria-valuemin="1"
:aria-valuemin="minVisibleItems"
:aria-valuemax="maxVisibleItems"
:aria-valuenow="visibleItemCount"
class="group/resize relative inset-x-0 bottom-0 flex h-6 -mt-3 w-1/3 mx-auto cursor-ns-resize touch-none select-none items-center justify-center"
class="group/resize relative col-span-full inset-x-0 bottom-0 flex h-6 -mt-3 w-1/3 mx-auto cursor-ns-resize touch-none select-none items-center justify-center"
@pointerdown="onResizePointerDown"
@pointermove="onResizePointerMove"
@pointerup="endResize"
@pointercancel="endResize"
@keydown.up.prevent="adjustVisibleItemLimit(-1)"
@keydown.down.prevent="adjustVisibleItemLimit(1)"
@keydown.home.prevent="setVisibleItemLimit(1)"
@keydown.home.prevent="setVisibleItemLimit(minVisibleItems)"
@keydown.end.prevent="setVisibleItemLimit(maxVisibleItems)"
>
<div class="flex flex-col gap-0.5">
Expand Down
Loading