99 useVIntl ,
1010} from ' @modrinth/ui'
1111import { useQueries , useQuery , useQueryClient } from ' @tanstack/vue-query'
12+ import { useResizeObserver } from ' @vueuse/core'
1213import type { Dayjs } from ' dayjs'
1314import dayjs from ' dayjs'
1415import { computed , onUnmounted , ref , watch } from ' vue'
@@ -66,26 +67,36 @@ const gameVersions = computed(() => gameVersionsQuery.data.value ?? [])
6667
6768const MAX_JUMP_BACK_IN = 5
6869const MAX_NEW_INSTANCES = 3
69- const ITEM_DRAG_DISTANCE = 92
70+ const ROW_DRAG_DISTANCE = 92
7071const STORAGE_KEY = ' modrinth-jump-back-in-count'
7172
73+ const itemsGrid = ref <HTMLElement >()
74+ const columnCount = ref (1 )
75+ useResizeObserver (itemsGrid , () => {
76+ if (! itemsGrid .value ) return
77+ columnCount .value = getComputedStyle (itemsGrid .value ).gridTemplateColumns .split (' ' ).length
78+ })
79+
7280const storedVisibleCount = Number .parseInt (localStorage .getItem (STORAGE_KEY ) ?? ' ' , 10 )
7381const visibleItemLimit = ref (
7482 Number .isFinite (storedVisibleCount )
7583 ? Math .max (1 , Math .min (storedVisibleCount , MAX_JUMP_BACK_IN ))
7684 : MAX_JUMP_BACK_IN ,
7785)
7886const maxVisibleItems = computed (() => Math .min (jumpBackInItems .value .length , MAX_JUMP_BACK_IN ))
79- const visibleItemCount = computed (() => Math .min (visibleItemLimit .value , maxVisibleItems .value ))
87+ const minVisibleItems = computed (() => Math .min (columnCount .value , maxVisibleItems .value ))
88+ const visibleItemCount = computed (() =>
89+ Math .max (minVisibleItems .value , Math .min (visibleItemLimit .value , maxVisibleItems .value )),
90+ )
8091const visibleJumpBackInItems = computed (() =>
8192 jumpBackInItems .value .slice (0 , visibleItemCount .value ),
8293)
83- const canResize = computed (() => maxVisibleItems .value > 1 )
94+ const canResize = computed (() => maxVisibleItems .value > minVisibleItems . value )
8495const resizing = ref (false )
8596const showOverdrag = ref (false )
8697
8798function setVisibleItemLimit(count : number ) {
88- const clamped = Math .max (1 , Math .min (count , maxVisibleItems .value ))
99+ const clamped = Math .max (minVisibleItems . value , Math .min (count , maxVisibleItems .value ))
89100 if (clamped >= maxVisibleItems .value ) {
90101 visibleItemLimit .value = MAX_JUMP_BACK_IN
91102 localStorage .removeItem (STORAGE_KEY )
@@ -95,9 +106,15 @@ function setVisibleItemLimit(count: number) {
95106 }
96107}
97108
98- function adjustVisibleItemLimit(delta : number ) {
99- const target = visibleItemCount .value + delta
100- if (target < 1 || target > maxVisibleItems .value ) {
109+ function getRowResizeTarget(count : number , rowDelta : number ) {
110+ return rowDelta < 0
111+ ? (Math .ceil (count / columnCount .value ) + rowDelta ) * columnCount .value
112+ : count + rowDelta * columnCount .value
113+ }
114+
115+ function adjustVisibleItemLimit(rowDelta : number ) {
116+ const target = getRowResizeTarget (visibleItemCount .value , rowDelta )
117+ if (target < minVisibleItems .value || target > maxVisibleItems .value ) {
101118 flashOverdrag ()
102119 }
103120 setVisibleItemLimit (target )
@@ -140,8 +157,9 @@ function onResizePointerDown(event: PointerEvent) {
140157
141158function onResizePointerMove(event : PointerEvent ) {
142159 if (! resizing .value ) return
143- const target = dragStartCount + Math .round ((event .clientY - dragStartY ) / ITEM_DRAG_DISTANCE )
144- const isOverdragging = target < 1 || target > maxVisibleItems .value
160+ const rowDelta = Math .round ((event .clientY - dragStartY ) / ROW_DRAG_DISTANCE )
161+ const target = getRowResizeTarget (dragStartCount , rowDelta )
162+ const isOverdragging = target < minVisibleItems .value || target > maxVisibleItems .value
145163 if (isOverdragging && ! wasOverdragging ) {
146164 flashOverdrag ()
147165 }
@@ -180,8 +198,10 @@ type JumpBackInItem = InstanceJumpBackInItem | WorldJumpBackInItem
180198
181199const showWorlds = computed (() => appSettings .showJumpIn )
182200
201+ const recentWorldsOptions = recentWorldsQueryOptions (MAX_JUMP_BACK_IN )
183202const recentWorldsQuery = useQuery ({
184- ... recentWorldsQueryOptions (MAX_JUMP_BACK_IN ),
203+ ... recentWorldsOptions ,
204+ queryKey: [... recentWorldsOptions .queryKey ] as const ,
185205 enabled: showWorlds ,
186206})
187207const loading = computed (() => showWorlds .value && recentWorldsQuery .isLoading .value )
@@ -423,7 +443,7 @@ onUnmounted(() => {
423443 <div v-if =" loading" class =" text-center py-4" >
424444 <LoaderCircleIcon class="mx-auto size-8 animate-spin text-contrast" />
425445 </div >
426- <div v-else class =" grid-when-huge relative flex w-full flex-col gap-3" >
446+ <div v-else ref = " itemsGrid " class =" grid-when-huge relative flex w-full flex-col gap-3" >
427447 <TransitionGroup name="jump-back-in-item">
428448 <div
429449 v-for =" item in visibleJumpBackInItems"
@@ -501,17 +521,17 @@ onUnmounted(() => {
501521 tabindex =" 0"
502522 aria-orientation =" horizontal"
503523 :aria-label =" formatMessage(messages.resize)"
504- :aria-valuemin =" 1 "
524+ :aria-valuemin =" minVisibleItems "
505525 :aria-valuemax =" maxVisibleItems"
506526 :aria-valuenow =" visibleItemCount"
507- 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"
527+ 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"
508528 @pointerdown =" onResizePointerDown"
509529 @pointermove =" onResizePointerMove"
510530 @pointerup =" endResize"
511531 @pointercancel =" endResize"
512532 @keydown.up.prevent =" adjustVisibleItemLimit(-1)"
513533 @keydown.down.prevent =" adjustVisibleItemLimit(1)"
514- @keydown.home.prevent =" setVisibleItemLimit(1 )"
534+ @keydown.home.prevent =" setVisibleItemLimit(minVisibleItems )"
515535 @keydown.end.prevent =" setVisibleItemLimit(maxVisibleItems)"
516536 >
517537 <div class =" flex flex-col gap-0.5" >
0 commit comments