mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-24 17:23:50 +02:00
344 lines
15 KiB
Vue
344 lines
15 KiB
Vue
<template>
|
|
<div class="x-container" ref="playerListRef">
|
|
<div class="flex h-full min-h-0 flex-col overflow-y-auto overflow-x-hidden">
|
|
<div
|
|
v-if="currentInstanceWorld.ref.id"
|
|
ref="playerListHeaderRef"
|
|
style="display: flex; min-height: 120px"
|
|
class="mb-7">
|
|
<img
|
|
:src="currentInstanceWorld.ref.thumbnailImageUrl"
|
|
class="cursor-pointer"
|
|
style="flex: none; width: 160px; height: 120px; border-radius: var(--radius-md)"
|
|
@click="showFullscreenImageDialog(currentInstanceWorld.ref.imageUrl)"
|
|
loading="lazy" />
|
|
<div class="ml-2" style="display: flex; flex-direction: column; min-width: 320px; width: 100%">
|
|
<div class="flex items-center">
|
|
<span
|
|
class="cursor-pointer"
|
|
style="
|
|
font-weight: bold;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
display: -webkit-box;
|
|
-webkit-box-orient: vertical;
|
|
line-clamp: 1;
|
|
"
|
|
@click="showWorldDialog(currentInstanceWorld.ref.id)">
|
|
<Home
|
|
v-if="
|
|
currentUser.$homeLocation &&
|
|
currentUser.$homeLocation.worldId === currentInstanceWorld.ref.id
|
|
"
|
|
class="inline-block" />
|
|
{{ currentInstanceWorld.ref.name }}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span
|
|
class="cursor-pointer x-grey font-mono"
|
|
@click="showUserDialog(currentInstanceWorld.ref.authorId)"
|
|
v-text="currentInstanceWorld.ref.authorName"></span>
|
|
</div>
|
|
<div class="mt-1.5">
|
|
<Badge class="mr-1.5" v-if="currentInstanceWorld.ref.$isLabs" variant="outline">
|
|
{{ t('dialog.world.tags.labs') }}
|
|
</Badge>
|
|
<Badge
|
|
class="mr-1.5"
|
|
v-else-if="currentInstanceWorld.ref.releaseStatus === 'public'"
|
|
variant="outline">
|
|
{{ t('dialog.world.tags.public') }}
|
|
</Badge>
|
|
<Badge
|
|
class="mr-1.5"
|
|
v-else-if="currentInstanceWorld.ref.releaseStatus === 'private'"
|
|
variant="outline">
|
|
{{ t('dialog.world.tags.private') }}
|
|
</Badge>
|
|
<TooltipWrapper v-if="currentInstanceWorld.isPC" side="top" content="PC">
|
|
<Badge class="text-platform-pc border-platform-pc! mr-1.5" variant="outline"
|
|
><Monitor class="h-4 w-4" />
|
|
<span
|
|
v-if="currentInstanceWorld.fileAnalysis.standalonewindows?._fileSize"
|
|
class="x-grey text-platform-pc border-l-[0.8px] border-solid ml-1.5 pl-1.5 pb-px"
|
|
>{{ currentInstanceWorld.fileAnalysis.standalonewindows._fileSize }}</span
|
|
>
|
|
</Badge>
|
|
</TooltipWrapper>
|
|
<TooltipWrapper v-if="currentInstanceWorld.isQuest" side="top" content="Android">
|
|
<Badge class="text-platform-quest border-platform-quest! mr-1.5" variant="outline"
|
|
><Smartphone class="h-4 w-4" />
|
|
<span
|
|
v-if="currentInstanceWorld.fileAnalysis.android?._fileSize"
|
|
class="x-grey text-platform-quest border-l-[0.8px] border-solid ml-1.5 pl-1.5 pb-px"
|
|
>{{ currentInstanceWorld.fileAnalysis.android._fileSize }}</span
|
|
>
|
|
</Badge>
|
|
</TooltipWrapper>
|
|
<TooltipWrapper v-if="currentInstanceWorld.isIos" side="top" content="iOS">
|
|
<Badge class="text-platform-ios border-platform-ios mr-1.5" variant="outline"
|
|
><Apple class="h-4 w-4 text-platform-ios" />
|
|
<span
|
|
v-if="currentInstanceWorld.fileAnalysis.ios?._fileSize"
|
|
class="x-grey text-platform-ios border-platform-ios border-l-[0.8px] border-solid ml-1.5 pl-1.5 pb-px"
|
|
>{{ currentInstanceWorld.fileAnalysis.ios._fileSize }}</span
|
|
>
|
|
</Badge>
|
|
</TooltipWrapper>
|
|
<Badge
|
|
class="mr-1.5 mt-1.5"
|
|
v-if="currentInstanceWorld.avatarScalingDisabled"
|
|
variant="outline">
|
|
{{ t('dialog.world.tags.avatar_scaling_disabled') }}
|
|
</Badge>
|
|
<Badge class="mr-1.5" v-if="currentInstanceWorld.inCache" variant="outline">
|
|
<span>{{ currentInstanceWorld.cacheSize }} {{ t('dialog.world.tags.cache') }}</span>
|
|
</Badge>
|
|
</div>
|
|
<div class="mt-1.5">
|
|
<LocationWorld :locationobject="currentInstanceLocation" :currentuserid="currentUser.id" />
|
|
<span class="ml-1.5" v-if="lastLocation.playerList.size > 0">
|
|
{{ lastLocation.playerList.size }}
|
|
<template v-if="lastLocation.friendList.size > 0"
|
|
>({{ lastLocation.friendList.size }})</template
|
|
>
|
|
― <Timer v-if="lastLocation.date" :epoch="lastLocation.date" />
|
|
</span>
|
|
</div>
|
|
<div class="mt-1.5">
|
|
<span
|
|
v-show="currentInstanceWorld.ref.name !== currentInstanceWorld.ref.description"
|
|
class="inline-block max-w-full align-middle text-xs break-words"
|
|
v-text="currentInstanceWorld.ref.description"></span>
|
|
</div>
|
|
</div>
|
|
<div class="ml-5" style="display: flex; flex-direction: column">
|
|
<div class="box-border flex items-center p-1.5 text-[13px] cursor-default">
|
|
<div class="flex-1 overflow-hidden">
|
|
<span class="block truncate font-medium leading-[18px]">{{
|
|
t('dialog.world.info.capacity')
|
|
}}</span>
|
|
<span class="block truncate text-xs"
|
|
>{{ commaNumber(currentInstanceWorld.ref.recommendedCapacity) }} ({{
|
|
commaNumber(currentInstanceWorld.ref.capacity)
|
|
}})</span
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="box-border flex items-center p-1.5 text-[13px] cursor-default">
|
|
<div class="flex-1 overflow-hidden">
|
|
<span class="block truncate font-medium leading-[18px]">{{
|
|
t('dialog.world.info.last_updated')
|
|
}}</span>
|
|
<span class="block truncate text-xs">{{
|
|
formatDateFilter(
|
|
currentInstanceWorld.fileAnalysis.standalonewindows?.created_at,
|
|
'long'
|
|
)
|
|
}}</span>
|
|
</div>
|
|
</div>
|
|
<div class="box-border flex items-center p-1.5 text-[13px] cursor-default">
|
|
<div class="flex-1 overflow-hidden">
|
|
<span class="block truncate font-medium leading-[18px]">{{
|
|
t('dialog.world.info.created_at')
|
|
}}</span>
|
|
<span class="block truncate text-xs">{{
|
|
formatDateFilter(currentInstanceWorld.ref.created_at, 'long')
|
|
}}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-2" v-if="photonLoggingEnabled" ref="playerListPhotonRef">
|
|
<PhotonEventTable @show-chatbox-blacklist="showChatboxBlacklistDialog" />
|
|
</div>
|
|
|
|
<div class="current-instance-table flex min-h-0 min-w-0 flex-1">
|
|
<DataTableLayout
|
|
class="[&_th]:px-2.5! [&_th]:py-0.75! [&_td]:px-2.5! [&_td]:py-0.75! [&_tr]:h-7!"
|
|
:table="playerListTable"
|
|
:table-style="playerListTableStyle"
|
|
:loading="false"
|
|
:show-pagination="false"
|
|
:on-row-click="handlePlayerListRowClick" />
|
|
</div>
|
|
</div>
|
|
<ChatboxBlacklistDialog
|
|
:chatbox-blacklist-dialog="chatboxBlacklistDialog"
|
|
@delete-chatbox-user-blacklist="deleteChatboxUserBlacklist" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, defineAsyncComponent, onActivated, onMounted, ref, watch } from 'vue';
|
|
import { Apple, Home, Monitor, Smartphone } from 'lucide-vue-next';
|
|
import { storeToRefs } from 'pinia';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import {
|
|
useAppearanceSettingsStore,
|
|
useGalleryStore,
|
|
useInstanceStore,
|
|
useLocationStore,
|
|
usePhotonStore,
|
|
useUserStore,
|
|
useWorldStore
|
|
} from '../../stores';
|
|
import { commaNumber, formatDateFilter } from '../../shared/utils';
|
|
import { Badge } from '../../components/ui/badge';
|
|
import { DataTableLayout } from '../../components/ui/data-table';
|
|
import { createColumns } from './columns.jsx';
|
|
import { useDataTableScrollHeight } from '../../composables/useDataTableScrollHeight';
|
|
import { useVrcxVueTable } from '../../lib/table/useVrcxVueTable';
|
|
|
|
import ChatboxBlacklistDialog from './dialogs/ChatboxBlacklistDialog.vue';
|
|
import Timer from '../../components/Timer.vue';
|
|
import { showUserDialog, lookupUser } from '../../coordinators/userCoordinator';
|
|
|
|
const PhotonEventTable = defineAsyncComponent(() => import('./components/PhotonEventTable.vue'));
|
|
|
|
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
|
|
const photonStore = usePhotonStore();
|
|
const { photonLoggingEnabled, chatboxUserBlacklist } = storeToRefs(photonStore);
|
|
const { saveChatboxUserBlacklist } = photonStore;
|
|
|
|
const { showWorldDialog } = useWorldStore();
|
|
const { lastLocation } = storeToRefs(useLocationStore());
|
|
const { currentInstanceLocation, currentInstanceWorld, currentInstanceUsersData } = storeToRefs(useInstanceStore());
|
|
const { getCurrentInstanceUserList } = useInstanceStore();
|
|
const { showFullscreenImageDialog } = useGalleryStore();
|
|
const { currentUser } = storeToRefs(useUserStore());
|
|
|
|
const playerListRef = ref(null);
|
|
const playerListHeaderRef = ref(null);
|
|
const playerListPhotonRef = ref(null);
|
|
const { tableStyle: playerListTableStyle } = useDataTableScrollHeight(playerListRef, {
|
|
offset: 30,
|
|
paginationHeight: 0,
|
|
subtractContainerPadding: true,
|
|
extraOffsetRefs: [playerListHeaderRef, playerListPhotonRef]
|
|
});
|
|
|
|
const { t } = useI18n();
|
|
|
|
const chatboxBlacklistDialog = ref({
|
|
visible: false,
|
|
loading: false
|
|
});
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function showChatboxBlacklistDialog() {
|
|
const D = chatboxBlacklistDialog.value;
|
|
D.visible = true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param val
|
|
*/
|
|
function selectCurrentInstanceRow(val) {
|
|
if (val === null) {
|
|
return;
|
|
}
|
|
const ref = val.ref;
|
|
if (ref.id) {
|
|
showUserDialog(ref.id);
|
|
} else {
|
|
lookupUser(ref);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param userId
|
|
*/
|
|
async function deleteChatboxUserBlacklist(userId) {
|
|
chatboxUserBlacklist.value.delete(userId);
|
|
await saveChatboxUserBlacklist();
|
|
getCurrentInstanceUserList();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param user
|
|
*/
|
|
async function addChatboxUserBlacklist(user) {
|
|
chatboxUserBlacklist.value.set(user.id, user.displayName);
|
|
await saveChatboxUserBlacklist();
|
|
getCurrentInstanceUserList();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param a
|
|
* @param b
|
|
* @param field
|
|
*/
|
|
function sortAlphabetically(a, b, field) {
|
|
if (!a[field] || !b[field]) return 0;
|
|
return a[field].toLowerCase().localeCompare(b[field].toLowerCase());
|
|
}
|
|
|
|
const playerListColumns = computed(() =>
|
|
createColumns({
|
|
randomUserColours,
|
|
chatboxUserBlacklist,
|
|
onBlockChatbox: addChatboxUserBlacklist,
|
|
onUnblockChatbox: deleteChatboxUserBlacklist,
|
|
sortAlphabetically
|
|
})
|
|
);
|
|
|
|
const { table: playerListTable } = useVrcxVueTable({
|
|
persistKey: 'playerList',
|
|
get data() {
|
|
return currentInstanceUsersData.value;
|
|
},
|
|
columns: playerListColumns,
|
|
enablePagination: false,
|
|
getRowId: (row) => `${row?.ref?.id ?? ''}:${row?.displayName ?? ''}`
|
|
});
|
|
|
|
watch(
|
|
playerListColumns,
|
|
(next) => {
|
|
playerListTable.setOptions((prev) => ({
|
|
...prev,
|
|
columns: next
|
|
}));
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
watch(
|
|
photonLoggingEnabled,
|
|
(enabled) => {
|
|
const column = playerListTable?.getColumn?.('photonId');
|
|
if (!column) {
|
|
return;
|
|
}
|
|
column.toggleVisibility(Boolean(enabled));
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
const playerListTotalItems = computed(() => playerListTable.getRowModel().rows.length);
|
|
|
|
const handlePlayerListRowClick = (row) => {
|
|
selectCurrentInstanceRow(row?.original ?? null);
|
|
};
|
|
|
|
onMounted(() => {
|
|
getCurrentInstanceUserList();
|
|
});
|
|
|
|
onActivated(() => {
|
|
getCurrentInstanceUserList();
|
|
});
|
|
</script>
|