replace el-splitter

This commit is contained in:
pa
2026-01-13 14:00:37 +09:00
committed by Natsumi
parent 9e693e0e97
commit 911969289e
4 changed files with 382 additions and 54 deletions
@@ -111,11 +111,14 @@
</div> </div>
</div> </div>
<div class="status-online"> <div class="status-online">
<el-statistic <div class="text-center">
:title="t('view.charts.instance_activity.online_time')" <div class="text-sm text-muted-foreground">
:formatter="(val) => timeToText(val, true)" {{ t('view.charts.instance_activity.online_time') }}
:value="totalOnlineTime"> </div>
</el-statistic> <div class="text-2xl font-semibold">
{{ timeToText(totalOnlineTime, true) }}
</div>
</div>
</div> </div>
<div ref="activityChartRef" style="width: 100%"></div> <div ref="activityChartRef" style="width: 100%"></div>
@@ -713,9 +716,6 @@
.status-online { .status-online {
display: flex; display: flex;
justify-content: center; justify-content: center;
:deep(.el-statistic__head) { text-align: center;
display: flex;
justify-content: center;
}
} }
</style> </style>
+123 -14
View File
@@ -72,8 +72,19 @@
</DropdownMenu> </DropdownMenu>
</div> </div>
</div> </div>
<el-splitter class="favorites-splitter" @resize-end="handleAvatarSplitterResize"> <ResizablePanelGroup
<el-splitter-panel :size="avatarSplitterSize" :min="0" :max="360" collapsible> ref="avatarSplitterGroupRef"
direction="horizontal"
class="favorites-splitter"
@layout="handleAvatarSplitterLayout">
<ResizablePanel
ref="avatarSplitterPanelRef"
:default-size="avatarSplitterDefaultSize"
:min-size="avatarSplitterMinSize"
:max-size="avatarSplitterMaxSize"
:collapsed-size="0"
collapsible
:order="1">
<div class="favorites-groups-panel"> <div class="favorites-groups-panel">
<div class="group-section"> <div class="group-section">
<div class="group-section__header"> <div class="group-section__header">
@@ -329,8 +340,9 @@
</div> </div>
</div> </div>
</div> </div>
</el-splitter-panel> </ResizablePanel>
<el-splitter-panel> <ResizableHandle with-handle @dragging="setAvatarSplitterDragging" />
<ResizablePanel :order="2">
<div class="favorites-content"> <div class="favorites-content">
<div class="favorites-content__header"> <div class="favorites-content__header">
<div class="favorites-content__title"> <div class="favorites-content__title">
@@ -502,8 +514,8 @@
</template> </template>
</div> </div>
</div> </div>
</el-splitter-panel> </ResizablePanel>
</el-splitter> </ResizablePanelGroup>
<AvatarExportDialog v-model:avatarExportDialogVisible="avatarExportDialogVisible" /> <AvatarExportDialog v-model:avatarExportDialogVisible="avatarExportDialogVisible" />
</div> </div>
</template> </template>
@@ -513,8 +525,8 @@
import { ElMessageBox, ElNotification, ElProgress } from 'element-plus'; import { ElMessageBox, ElNotification, ElProgress } from 'element-plus';
import { MoreFilled, Plus, Refresh } from '@element-plus/icons-vue'; import { MoreFilled, Plus, Refresh } from '@element-plus/icons-vue';
import { Ellipsis, RefreshCcw } from 'lucide-vue-next'; import { Ellipsis, RefreshCcw } from 'lucide-vue-next';
import { Button } from '@/components/ui/button';
import { InputGroupField, InputGroupSearch } from '@/components/ui/input-group'; import { InputGroupField, InputGroupSearch } from '@/components/ui/input-group';
import { Button } from '@/components/ui/button';
import { Loader } from 'lucide-vue-next'; import { Loader } from 'lucide-vue-next';
import { Spinner } from '@/components/ui/spinner'; import { Spinner } from '@/components/ui/spinner';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
@@ -538,6 +550,7 @@
} from '../../components/ui/dropdown-menu'; } from '../../components/ui/dropdown-menu';
import { useAppearanceSettingsStore, useAvatarStore, useFavoriteStore, useUserStore } from '../../stores'; import { useAppearanceSettingsStore, useAvatarStore, useFavoriteStore, useUserStore } from '../../stores';
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { avatarRequest, favoriteRequest } from '../../api'; import { avatarRequest, favoriteRequest } from '../../api';
import { Badge } from '../../components/ui/badge'; import { Badge } from '../../components/ui/badge';
import { Slider } from '../../components/ui/slider'; import { Slider } from '../../components/ui/slider';
@@ -563,6 +576,12 @@
const avatarGroupVisibilityOptions = ref(['public', 'friends', 'private']); const avatarGroupVisibilityOptions = ref(['public', 'friends', 'private']);
const historyGroupKey = 'local-history'; const historyGroupKey = 'local-history';
const avatarSplitterSize = ref(260); const avatarSplitterSize = ref(260);
const avatarSplitterFallbackWidth = typeof window !== 'undefined' && window.innerWidth ? window.innerWidth : 1200;
const avatarSplitterGroupRef = ref(null);
const avatarSplitterPanelRef = ref(null);
const avatarSplitterWidth = ref(avatarSplitterFallbackWidth);
const avatarSplitterDraggingCount = ref(0);
let avatarSplitterObserver = null;
const { sortFavorites } = storeToRefs(useAppearanceSettingsStore()); const { sortFavorites } = storeToRefs(useAppearanceSettingsStore());
const { setSortFavorites } = useAppearanceSettingsStore(); const { setSortFavorites } = useAppearanceSettingsStore();
@@ -698,23 +717,109 @@
async function loadAvatarSplitterPreferences() { async function loadAvatarSplitterPreferences() {
const storedSize = await configRepository.getString('VRCX_FavoritesAvatarSplitter', '260'); const storedSize = await configRepository.getString('VRCX_FavoritesAvatarSplitter', '260');
if (typeof storedSize === 'string' && !Number.isNaN(Number(storedSize)) && Number(storedSize) > 0) { const parsedSize = Number(storedSize);
avatarSplitterSize.value = Number(storedSize); if (Number.isFinite(parsedSize) && parsedSize >= 0) {
avatarSplitterSize.value = parsedSize;
} }
} }
function handleAvatarSplitterResize(panelIndex, sizes) { const getAvatarSplitterWidthRaw = () => {
const element = avatarSplitterGroupRef.value?.$el ?? avatarSplitterGroupRef.value;
const width = element?.getBoundingClientRect?.().width;
return Number.isFinite(width) ? width : null;
};
const getAvatarSplitterWidth = () => {
const width = getAvatarSplitterWidthRaw();
return Number.isFinite(width) && width > 0 ? width : avatarSplitterFallbackWidth;
};
const resolveDraggingPayload = (payload) => {
if (typeof payload === 'boolean') {
return payload;
}
if (payload && typeof payload === 'object') {
if (typeof payload.detail === 'boolean') {
return payload.detail;
}
if (typeof payload.dragging === 'boolean') {
return payload.dragging;
}
}
return Boolean(payload);
};
const setAvatarSplitterDragging = (payload) => {
const isDragging = resolveDraggingPayload(payload);
const next = avatarSplitterDraggingCount.value + (isDragging ? 1 : -1);
avatarSplitterDraggingCount.value = Math.max(0, next);
};
const pxToPercent = (px, groupWidth, min = 0) => {
const width = groupWidth ?? getAvatarSplitterWidth();
return Math.min(100, Math.max(min, (px / width) * 100));
};
const percentToPx = (percent, groupWidth) => (percent / 100) * groupWidth;
const avatarSplitterDefaultSize = computed(() =>
pxToPercent(avatarSplitterSize.value, avatarSplitterWidth.value, 0)
);
const avatarSplitterMinSize = computed(() => pxToPercent(0, avatarSplitterWidth.value, 0));
const avatarSplitterMaxSize = computed(() => pxToPercent(360, avatarSplitterWidth.value, 0));
const handleAvatarSplitterLayout = (sizes) => {
if (!Array.isArray(sizes) || !sizes.length) { if (!Array.isArray(sizes) || !sizes.length) {
return; return;
} }
const nextSize = sizes[0];
if (nextSize <= 0) { if (avatarSplitterDraggingCount.value === 0) {
return; return;
} }
avatarSplitterSize.value = nextSize;
configRepository.setString('VRCX_FavoritesAvatarSplitter', nextSize.toString()); const rawWidth = getAvatarSplitterWidthRaw();
if (!Number.isFinite(rawWidth) || rawWidth <= 0) {
return;
} }
const nextSize = sizes[0];
if (!Number.isFinite(nextSize)) {
return;
}
const nextPx = Math.round(percentToPx(nextSize, rawWidth));
const clampedPx = Math.min(360, Math.max(0, nextPx));
avatarSplitterSize.value = clampedPx;
configRepository.setString('VRCX_FavoritesAvatarSplitter', clampedPx.toString());
};
const updateAvatarSplitterWidth = () => {
const width = getAvatarSplitterWidth();
avatarSplitterWidth.value = width;
const targetSize = pxToPercent(avatarSplitterSize.value, width, 0);
avatarSplitterPanelRef.value?.resize?.(targetSize);
};
onMounted(async () => {
await nextTick();
updateAvatarSplitterWidth();
const element = avatarSplitterGroupRef.value?.$el ?? avatarSplitterGroupRef.value;
if (element && typeof ResizeObserver !== 'undefined') {
avatarSplitterObserver = new ResizeObserver(updateAvatarSplitterWidth);
avatarSplitterObserver.observe(element);
}
});
watch(avatarSplitterSize, (value, previous) => {
if (value === previous) {
return;
}
if (avatarSplitterDraggingCount.value > 0) {
return;
}
updateAvatarSplitterWidth();
});
const groupedAvatarFavorites = computed(() => { const groupedAvatarFavorites = computed(() => {
const grouped = {}; const grouped = {};
favoriteAvatars.value.forEach((avatar) => { favoriteAvatars.value.forEach((avatar) => {
@@ -1536,6 +1641,10 @@
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.removeEventListener('resize', maybeFillLocalAvatarViewport); window.removeEventListener('resize', maybeFillLocalAvatarViewport);
} }
if (avatarSplitterObserver) {
avatarSplitterObserver.disconnect();
avatarSplitterObserver = null;
}
}); });
function formatVisibility(value) { function formatVisibility(value) {
+127 -15
View File
@@ -72,8 +72,19 @@
</DropdownMenu> </DropdownMenu>
</div> </div>
</div> </div>
<el-splitter class="favorites-splitter" @resize-end="handleFriendSplitterResize"> <ResizablePanelGroup
<el-splitter-panel :size="friendSplitterSize" :min="0" :max="360" collapsible> ref="friendSplitterGroupRef"
direction="horizontal"
class="favorites-splitter"
@layout="handleFriendSplitterLayout">
<ResizablePanel
ref="friendSplitterPanelRef"
:default-size="friendSplitterDefaultSize"
:min-size="friendSplitterMinSize"
:max-size="friendSplitterMaxSize"
:collapsed-size="0"
collapsible
:order="1">
<div class="favorites-groups-panel"> <div class="favorites-groups-panel">
<div class="group-section"> <div class="group-section">
<div class="group-section__header"> <div class="group-section__header">
@@ -174,8 +185,9 @@
</div> </div>
</div> </div>
</div> </div>
</el-splitter-panel> </ResizablePanel>
<el-splitter-panel> <ResizableHandle with-handle @dragging="setFriendSplitterDragging" />
<ResizablePanel :order="2">
<div class="favorites-content"> <div class="favorites-content">
<div class="favorites-content__header"> <div class="favorites-content__header">
<div class="favorites-content__title"> <div class="favorites-content__title">
@@ -286,19 +298,19 @@
</template> </template>
</div> </div>
</div> </div>
</el-splitter-panel> </ResizablePanel>
</el-splitter> </ResizablePanelGroup>
<FriendExportDialog v-model:friendExportDialogVisible="friendExportDialogVisible" /> <FriendExportDialog v-model:friendExportDialogVisible="friendExportDialogVisible" />
</div> </div>
</template> </template>
<script setup> <script setup>
import { computed, onBeforeMount, ref, watch } from 'vue'; import { computed, nextTick, onBeforeMount, onMounted, onUnmounted, ref, watch } from 'vue';
import { MoreFilled, Refresh } from '@element-plus/icons-vue'; import { MoreFilled, Refresh } from '@element-plus/icons-vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { InputGroupSearch } from '@/components/ui/input-group';
import { ElMessageBox } from 'element-plus'; import { ElMessageBox } from 'element-plus';
import { Ellipsis } from 'lucide-vue-next'; import { Ellipsis } from 'lucide-vue-next';
import { InputGroupSearch } from '@/components/ui/input-group';
import { Spinner } from '@/components/ui/spinner'; import { Spinner } from '@/components/ui/spinner';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
@@ -320,6 +332,7 @@
DropdownMenuTrigger DropdownMenuTrigger
} from '../../components/ui/dropdown-menu'; } from '../../components/ui/dropdown-menu';
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { useAppearanceSettingsStore, useFavoriteStore, useUserStore } from '../../stores'; import { useAppearanceSettingsStore, useFavoriteStore, useUserStore } from '../../stores';
import { Badge } from '../../components/ui/badge'; import { Badge } from '../../components/ui/badge';
import { Slider } from '../../components/ui/slider'; import { Slider } from '../../components/ui/slider';
@@ -335,6 +348,12 @@
const friendGroupVisibilityOptions = ref(['public', 'friends', 'private']); const friendGroupVisibilityOptions = ref(['public', 'friends', 'private']);
const friendSplitterSize = ref(260); const friendSplitterSize = ref(260);
const friendSplitterFallbackWidth = typeof window !== 'undefined' && window.innerWidth ? window.innerWidth : 1200;
const friendSplitterGroupRef = ref(null);
const friendSplitterPanelRef = ref(null);
const friendSplitterWidth = ref(friendSplitterFallbackWidth);
const friendSplitterDraggingCount = ref(0);
let friendSplitterObserver = null;
const { sortFavorites } = storeToRefs(useAppearanceSettingsStore()); const { sortFavorites } = storeToRefs(useAppearanceSettingsStore());
const { setSortFavorites } = useAppearanceSettingsStore(); const { setSortFavorites } = useAppearanceSettingsStore();
@@ -437,23 +456,116 @@
async function loadFriendSplitterPreferences() { async function loadFriendSplitterPreferences() {
const storedSize = await configRepository.getString('VRCX_FavoritesFriendSplitter', '260'); const storedSize = await configRepository.getString('VRCX_FavoritesFriendSplitter', '260');
if (typeof storedSize === 'string' && !Number.isNaN(Number(storedSize)) && Number(storedSize) > 0) { const parsedSize = Number(storedSize);
friendSplitterSize.value = Number(storedSize); if (Number.isFinite(parsedSize) && parsedSize >= 0) {
friendSplitterSize.value = parsedSize;
} }
} }
function handleFriendSplitterResize(panelIndex, sizes) { const getFriendSplitterWidthRaw = () => {
const element = friendSplitterGroupRef.value?.$el ?? friendSplitterGroupRef.value;
const width = element?.getBoundingClientRect?.().width;
return Number.isFinite(width) ? width : null;
};
const getFriendSplitterWidth = () => {
const width = getFriendSplitterWidthRaw();
return Number.isFinite(width) && width > 0 ? width : friendSplitterFallbackWidth;
};
const resolveDraggingPayload = (payload) => {
if (typeof payload === 'boolean') {
return payload;
}
if (payload && typeof payload === 'object') {
if (typeof payload.detail === 'boolean') {
return payload.detail;
}
if (typeof payload.dragging === 'boolean') {
return payload.dragging;
}
}
return Boolean(payload);
};
const setFriendSplitterDragging = (payload) => {
const isDragging = resolveDraggingPayload(payload);
const next = friendSplitterDraggingCount.value + (isDragging ? 1 : -1);
friendSplitterDraggingCount.value = Math.max(0, next);
};
const pxToPercent = (px, groupWidth, min = 0) => {
const width = groupWidth ?? getFriendSplitterWidth();
return Math.min(100, Math.max(min, (px / width) * 100));
};
const percentToPx = (percent, groupWidth) => (percent / 100) * groupWidth;
const friendSplitterDefaultSize = computed(() =>
pxToPercent(friendSplitterSize.value, friendSplitterWidth.value, 0)
);
const friendSplitterMinSize = computed(() => pxToPercent(0, friendSplitterWidth.value, 0));
const friendSplitterMaxSize = computed(() => pxToPercent(360, friendSplitterWidth.value, 0));
const handleFriendSplitterLayout = (sizes) => {
if (!Array.isArray(sizes) || !sizes.length) { if (!Array.isArray(sizes) || !sizes.length) {
return; return;
} }
const nextSize = sizes[0];
if (nextSize <= 0) { if (friendSplitterDraggingCount.value === 0) {
return; return;
} }
friendSplitterSize.value = nextSize;
configRepository.setString('VRCX_FavoritesFriendSplitter', nextSize.toString()); const rawWidth = getFriendSplitterWidthRaw();
if (!Number.isFinite(rawWidth) || rawWidth <= 0) {
return;
} }
const nextSize = sizes[0];
if (!Number.isFinite(nextSize)) {
return;
}
const nextPx = Math.round(percentToPx(nextSize, rawWidth));
const clampedPx = Math.min(360, Math.max(0, nextPx));
friendSplitterSize.value = clampedPx;
configRepository.setString('VRCX_FavoritesFriendSplitter', clampedPx.toString());
};
const updateFriendSplitterWidth = () => {
const width = getFriendSplitterWidth();
friendSplitterWidth.value = width;
const targetSize = pxToPercent(friendSplitterSize.value, width, 0);
friendSplitterPanelRef.value?.resize?.(targetSize);
};
onMounted(async () => {
await nextTick();
updateFriendSplitterWidth();
const element = friendSplitterGroupRef.value?.$el ?? friendSplitterGroupRef.value;
if (element && typeof ResizeObserver !== 'undefined') {
friendSplitterObserver = new ResizeObserver(updateFriendSplitterWidth);
friendSplitterObserver.observe(element);
}
});
onUnmounted(() => {
if (friendSplitterObserver) {
friendSplitterObserver.disconnect();
friendSplitterObserver = null;
}
});
watch(friendSplitterSize, (value, previous) => {
if (value === previous) {
return;
}
if (friendSplitterDraggingCount.value > 0) {
return;
}
updateFriendSplitterWidth();
});
const remoteGroupMenuKey = (key) => `remote:${key}`; const remoteGroupMenuKey = (key) => `remote:${key}`;
const searchableFriendEntries = computed(() => { const searchableFriendEntries = computed(() => {
+120 -13
View File
@@ -72,8 +72,19 @@
</DropdownMenu> </DropdownMenu>
</div> </div>
</div> </div>
<el-splitter class="favorites-splitter" @resize-end="handleWorldSplitterResize"> <ResizablePanelGroup
<el-splitter-panel :size="worldSplitterSize" :min="0" :max="360" collapsible> ref="worldSplitterGroupRef"
direction="horizontal"
class="favorites-splitter"
@layout="handleWorldSplitterLayout">
<ResizablePanel
ref="worldSplitterPanelRef"
:default-size="worldSplitterDefaultSize"
:min-size="worldSplitterMinSize"
:max-size="worldSplitterMaxSize"
:collapsed-size="0"
collapsible
:order="1">
<div class="favorites-groups-panel"> <div class="favorites-groups-panel">
<div class="group-section"> <div class="group-section">
<div class="group-section__header"> <div class="group-section__header">
@@ -278,8 +289,9 @@
</div> </div>
</div> </div>
</div> </div>
</el-splitter-panel> </ResizablePanel>
<el-splitter-panel> <ResizableHandle with-handle @dragging="setWorldSplitterDragging" />
<ResizablePanel :order="2">
<div class="favorites-content"> <div class="favorites-content">
<div class="favorites-content__header"> <div class="favorites-content__header">
<div class="favorites-content__title"> <div class="favorites-content__title">
@@ -416,8 +428,8 @@
</template> </template>
</div> </div>
</div> </div>
</el-splitter-panel> </ResizablePanel>
</el-splitter> </ResizablePanelGroup>
<WorldExportDialog v-model:worldExportDialogVisible="worldExportDialogVisible" /> <WorldExportDialog v-model:worldExportDialogVisible="worldExportDialogVisible" />
</div> </div>
</template> </template>
@@ -450,6 +462,7 @@
DropdownMenuTrigger DropdownMenuTrigger
} from '../../components/ui/dropdown-menu'; } from '../../components/ui/dropdown-menu';
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { useAppearanceSettingsStore, useFavoriteStore, useWorldStore } from '../../stores'; import { useAppearanceSettingsStore, useFavoriteStore, useWorldStore } from '../../stores';
import { favoriteRequest, worldRequest } from '../../api'; import { favoriteRequest, worldRequest } from '../../api';
import { Badge } from '../../components/ui/badge'; import { Badge } from '../../components/ui/badge';
@@ -548,6 +561,12 @@
const worldGroupVisibilityOptions = ref(['public', 'friends', 'private']); const worldGroupVisibilityOptions = ref(['public', 'friends', 'private']);
const worldSplitterSize = ref(260); const worldSplitterSize = ref(260);
const worldSplitterFallbackWidth = typeof window !== 'undefined' && window.innerWidth ? window.innerWidth : 1200;
const worldSplitterGroupRef = ref(null);
const worldSplitterPanelRef = ref(null);
const worldSplitterWidth = ref(worldSplitterFallbackWidth);
const worldSplitterDraggingCount = ref(0);
let worldSplitterObserver = null;
const worldExportDialogVisible = ref(false); const worldExportDialogVisible = ref(false);
const worldFavoriteSearch = ref(''); const worldFavoriteSearch = ref('');
const worldFavoriteSearchResults = ref([]); const worldFavoriteSearchResults = ref([]);
@@ -595,23 +614,107 @@
async function loadWorldSplitterPreferences() { async function loadWorldSplitterPreferences() {
const storedSize = await configRepository.getString('VRCX_FavoritesWorldSplitter', '260'); const storedSize = await configRepository.getString('VRCX_FavoritesWorldSplitter', '260');
if (typeof storedSize === 'string' && !Number.isNaN(Number(storedSize)) && Number(storedSize) > 0) { const parsedSize = Number(storedSize);
worldSplitterSize.value = Number(storedSize); if (Number.isFinite(parsedSize) && parsedSize >= 0) {
worldSplitterSize.value = parsedSize;
} }
} }
function handleWorldSplitterResize(panelIndex, sizes) { const getWorldSplitterWidthRaw = () => {
const element = worldSplitterGroupRef.value?.$el ?? worldSplitterGroupRef.value;
const width = element?.getBoundingClientRect?.().width;
return Number.isFinite(width) ? width : null;
};
const getWorldSplitterWidth = () => {
const width = getWorldSplitterWidthRaw();
return Number.isFinite(width) && width > 0 ? width : worldSplitterFallbackWidth;
};
const resolveDraggingPayload = (payload) => {
if (typeof payload === 'boolean') {
return payload;
}
if (payload && typeof payload === 'object') {
if (typeof payload.detail === 'boolean') {
return payload.detail;
}
if (typeof payload.dragging === 'boolean') {
return payload.dragging;
}
}
return Boolean(payload);
};
const setWorldSplitterDragging = (payload) => {
const isDragging = resolveDraggingPayload(payload);
const next = worldSplitterDraggingCount.value + (isDragging ? 1 : -1);
worldSplitterDraggingCount.value = Math.max(0, next);
};
const pxToPercent = (px, groupWidth, min = 0) => {
const width = groupWidth ?? getWorldSplitterWidth();
return Math.min(100, Math.max(min, (px / width) * 100));
};
const percentToPx = (percent, groupWidth) => (percent / 100) * groupWidth;
const worldSplitterDefaultSize = computed(() => pxToPercent(worldSplitterSize.value, worldSplitterWidth.value, 0));
const worldSplitterMinSize = computed(() => pxToPercent(0, worldSplitterWidth.value, 0));
const worldSplitterMaxSize = computed(() => pxToPercent(360, worldSplitterWidth.value, 0));
const handleWorldSplitterLayout = (sizes) => {
if (!Array.isArray(sizes) || !sizes.length) { if (!Array.isArray(sizes) || !sizes.length) {
return; return;
} }
const nextSize = sizes[0];
if (nextSize <= 0) { if (worldSplitterDraggingCount.value === 0) {
return; return;
} }
worldSplitterSize.value = nextSize;
configRepository.setString('VRCX_FavoritesWorldSplitter', nextSize.toString()); const rawWidth = getWorldSplitterWidthRaw();
if (!Number.isFinite(rawWidth) || rawWidth <= 0) {
return;
} }
const nextSize = sizes[0];
if (!Number.isFinite(nextSize)) {
return;
}
const nextPx = Math.round(percentToPx(nextSize, rawWidth));
const clampedPx = Math.min(360, Math.max(0, nextPx));
worldSplitterSize.value = clampedPx;
configRepository.setString('VRCX_FavoritesWorldSplitter', clampedPx.toString());
};
const updateWorldSplitterWidth = () => {
const width = getWorldSplitterWidth();
worldSplitterWidth.value = width;
const targetSize = pxToPercent(worldSplitterSize.value, width, 0);
worldSplitterPanelRef.value?.resize?.(targetSize);
};
onMounted(async () => {
await nextTick();
updateWorldSplitterWidth();
const element = worldSplitterGroupRef.value?.$el ?? worldSplitterGroupRef.value;
if (element && typeof ResizeObserver !== 'undefined') {
worldSplitterObserver = new ResizeObserver(updateWorldSplitterWidth);
worldSplitterObserver.observe(element);
}
});
watch(worldSplitterSize, (value, previous) => {
if (value === previous) {
return;
}
if (worldSplitterDraggingCount.value > 0) {
return;
}
updateWorldSplitterWidth();
});
const groupedWorldFavorites = computed(() => { const groupedWorldFavorites = computed(() => {
const grouped = {}; const grouped = {};
favoriteWorlds.value.forEach((world) => { favoriteWorlds.value.forEach((world) => {
@@ -1249,6 +1352,10 @@
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.removeEventListener('resize', maybeFillLocalFavoritesViewport); window.removeEventListener('resize', maybeFillLocalFavoritesViewport);
} }
if (worldSplitterObserver) {
worldSplitterObserver.disconnect();
worldSplitterObserver = null;
}
}); });
</script> </script>