diff --git a/src/components/dialogs/GroupDialog/GroupDialog.vue b/src/components/dialogs/GroupDialog/GroupDialog.vue index 028c5f53..0161d393 100644 --- a/src/components/dialogs/GroupDialog/GroupDialog.vue +++ b/src/components/dialogs/GroupDialog/GroupDialog.vue @@ -150,7 +150,7 @@ size="icon-lg" style="margin-left: 5px" @click="clearGroupRepresentation(groupDialog.id)"> - + @@ -161,7 +161,7 @@ size="icon-lg" :disabled="groupDialog.ref.privacy === 'private'" @click="setGroupRepresentation(groupDialog.id)"> - + @@ -1117,6 +1117,8 @@ import { Bell, BellOff, + Bookmark, + BookmarkCheck, Check, CheckCircle, Copy, @@ -1128,7 +1130,6 @@ RefreshCw, Settings, Share2, - Star, Tag, Ticket, Trash2, diff --git a/src/composables/useVirtualizerAnchor.js b/src/composables/useVirtualizerAnchor.js new file mode 100644 index 00000000..b063e7d6 --- /dev/null +++ b/src/composables/useVirtualizerAnchor.js @@ -0,0 +1,62 @@ +import { nextTick } from 'vue'; + +export function useVirtualizerAnchor({ + virtualizer, + virtualRows, + scrollViewportRef +}) { + const captureScrollAnchor = () => { + const viewport = scrollViewportRef.value; + const items = virtualizer.value?.getVirtualItems?.() ?? []; + if (!viewport || !items.length) { + return null; + } + const firstItem = items[0]; + const row = virtualRows.value[firstItem.index]; + const key = row?.key ?? firstItem.key; + if (typeof key === 'undefined') { + return null; + } + return { + key, + offset: viewport.scrollTop - firstItem.start + }; + }; + + const restoreScrollAnchor = (anchor) => { + if (!anchor) { + return; + } + const index = virtualRows.value.findIndex( + (row) => row?.key === anchor.key + ); + if (index === -1) { + return; + } + const offsetInfo = virtualizer.value?.getOffsetForIndex?.( + index, + 'start' + ); + const targetStart = Array.isArray(offsetInfo) ? offsetInfo[0] : null; + if (typeof targetStart !== 'number') { + return; + } + virtualizer.value?.scrollToOffset?.(targetStart + anchor.offset); + }; + + const measureWithAnchor = (measureFn) => { + const anchor = captureScrollAnchor(); + nextTick(() => { + if (measureFn) { + measureFn(); + } + restoreScrollAnchor(anchor); + }); + }; + + return { + captureScrollAnchor, + restoreScrollAnchor, + measureWithAnchor + }; +} diff --git a/src/views/Sidebar/components/FriendsSidebar.vue b/src/views/Sidebar/components/FriendsSidebar.vue index a03391da..aa914c7d 100644 --- a/src/views/Sidebar/components/FriendsSidebar.vue +++ b/src/views/Sidebar/components/FriendsSidebar.vue @@ -99,6 +99,7 @@ } from '../../../stores'; import { isRealInstance, userImage, userStatusClass } from '../../../shared/utils'; import { getFriendsLocations } from '../../../shared/utils/location.js'; + import { useVirtualizerAnchor } from '../../../composables/useVirtualizerAnchor'; import FriendItem from './FriendItem.vue'; import Location from '../../../components/Location.vue'; @@ -422,6 +423,12 @@ }; }; + const { measureWithAnchor } = useVirtualizerAnchor({ + virtualizer, + virtualRows, + scrollViewportRef + }); + function saveFriendsGroupStates() { configRepository.setBool('VRCX_isFriendsGroupMe', isFriendsGroupMe.value); configRepository.setBool('VRCX_isFriendsGroupFavorites', isVIPFriends.value); @@ -482,7 +489,7 @@ }); watch(virtualRows, () => { - nextTick(() => { + measureWithAnchor(() => { virtualizer.value?.measure?.(); }); }); diff --git a/src/views/Sidebar/components/GroupsSidebar.vue b/src/views/Sidebar/components/GroupsSidebar.vue index 6c55d95c..d371ee37 100644 --- a/src/views/Sidebar/components/GroupsSidebar.vue +++ b/src/views/Sidebar/components/GroupsSidebar.vue @@ -56,6 +56,7 @@ import { useAppearanceSettingsStore, useGroupStore } from '../../../stores'; import { convertFileUrlToImageUrl } from '../../../shared/utils'; + import { useVirtualizerAnchor } from '../../../composables/useVirtualizerAnchor'; import Location from '../../../components/Location.vue'; @@ -168,6 +169,12 @@ transform: `translateY(${item.virtualItem.start}px)` }); + const { measureWithAnchor } = useVirtualizerAnchor({ + virtualizer, + virtualRows, + scrollViewportRef + }); + function getSmallGroupIconUrl(url) { return convertFileUrlToImageUrl(url); } @@ -188,7 +195,7 @@ }); watch(virtualRows, () => { - nextTick(() => { + measureWithAnchor(() => { virtualizer.value?.measure?.(); }); });