mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-18 22:33:50 +02:00
improve fedd widget
This commit is contained in:
@@ -125,7 +125,9 @@
|
||||
"feed_offline": "offline",
|
||||
"feed_avatar": "→",
|
||||
"feed_bio": "updated bio",
|
||||
"instance_not_in_game": "Not in game"
|
||||
"instance_players": "players",
|
||||
"instance_not_in_game": "Not in game",
|
||||
"time_ago": "{time} ago"
|
||||
},
|
||||
"confirmations": {
|
||||
"delete_title": "Delete Dashboard",
|
||||
|
||||
@@ -6,46 +6,70 @@
|
||||
route-name="feed" />
|
||||
|
||||
<div class="min-h-0 flex-1 overflow-y-auto" ref="listRef">
|
||||
<template v-if="filteredData.length">
|
||||
<div
|
||||
v-for="(item, index) in filteredData"
|
||||
:key="`${item.type}-${item.created_at}-${index}`"
|
||||
class="flex items-center gap-1.5 border-b border-border/30 px-2.5 py-0.75 text-[13px] leading-snug hover:bg-accent/50"
|
||||
:class="{ 'border-l-2 border-l-chart-4': item.isFavorite }">
|
||||
<span class="shrink-0 text-[11px] tabular-nums text-muted-foreground">{{ formatTime(item.created_at) }}</span>
|
||||
|
||||
<template v-if="item.type === 'GPS'">
|
||||
<span class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="truncate text-muted-foreground">→ {{ item.worldName || t('dashboard.widget.unknown_world') }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Online'">
|
||||
<span class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="truncate text-chart-2">{{ t('dashboard.widget.feed_online') }}</span>
|
||||
<span v-if="item.worldName" class="truncate text-muted-foreground">→ {{ item.worldName }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Offline'">
|
||||
<span class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="truncate text-muted-foreground/60">{{ t('dashboard.widget.feed_offline') }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Status'">
|
||||
<span class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<i class="x-user-status" :class="statusClass(item.status)"></i>
|
||||
<span class="truncate text-muted-foreground">{{ item.statusDescription }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Avatar'">
|
||||
<span class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="truncate text-muted-foreground">{{ t('dashboard.widget.feed_avatar') }} {{ item.avatarName }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Bio'">
|
||||
<span class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="truncate text-muted-foreground">{{ t('dashboard.widget.feed_bio') }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="truncate text-muted-foreground">{{ item.type }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<Table v-if="filteredData.length" class="is-compact-table">
|
||||
<TableBody>
|
||||
<TableRow
|
||||
v-for="(item, index) in filteredData"
|
||||
:key="`${item.type}-${item.created_at}-${index}`"
|
||||
class="cursor-default"
|
||||
:class="{ 'border-l-2 border-l-chart-4': item.isFavorite }">
|
||||
<TableCell class="w-14 text-[11px] tabular-nums text-muted-foreground">
|
||||
<TooltipWrapper :content="formatExactTime(item.created_at)" side="top">
|
||||
<span>{{ timeAgo(item.created_at) }}</span>
|
||||
</TooltipWrapper>
|
||||
</TableCell>
|
||||
<TableCell class="truncate">
|
||||
<template v-if="item.type === 'GPS'">
|
||||
<MapPin class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span class="cursor-pointer font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="text-muted-foreground"> → </span>
|
||||
<Location
|
||||
class="inline [&>div]:inline-flex"
|
||||
:location="item.location"
|
||||
:hint="item.worldName"
|
||||
:grouphint="item.groupName"
|
||||
disable-tooltip />
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Online'">
|
||||
<i class="x-user-status online mr-1"></i>
|
||||
<span class="cursor-pointer font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<template v-if="item.location">
|
||||
<span class="text-muted-foreground"> → </span>
|
||||
<Location
|
||||
class="inline [&>div]:inline-flex"
|
||||
:location="item.location"
|
||||
:hint="item.worldName"
|
||||
:grouphint="item.groupName"
|
||||
disable-tooltip />
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Offline'">
|
||||
<i class="x-user-status mr-1"></i>
|
||||
<span class="cursor-pointer font-medium text-muted-foreground/70 hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Status'">
|
||||
<i class="x-user-status mr-1" :class="statusClass(item.status)"></i>
|
||||
<span class="cursor-pointer font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="text-muted-foreground"> {{ item.statusDescription }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Avatar'">
|
||||
<Box class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span class="cursor-pointer font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="text-muted-foreground"> → {{ item.avatarName }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'Bio'">
|
||||
<Pencil class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span class="cursor-pointer font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="ml-1 text-muted-foreground">{{ t('dashboard.widget.feed_bio') }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="cursor-pointer font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="text-muted-foreground"> {{ item.type }}</span>
|
||||
</template>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div v-else class="flex h-full items-center justify-center text-[13px] text-muted-foreground">
|
||||
{{ t('dashboard.widget.no_data') }}
|
||||
</div>
|
||||
@@ -56,12 +80,17 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Box, MapPin, Pencil } from 'lucide-vue-next';
|
||||
|
||||
import { statusClass } from '@/shared/utils/user';
|
||||
import { timeToText, formatDateFilter } from '@/shared/utils';
|
||||
import { showUserDialog } from '@/coordinators/userCoordinator';
|
||||
import { useFeedStore } from '@/stores';
|
||||
|
||||
import Location from '@/components/Location.vue';
|
||||
import { TooltipWrapper } from '@/components/ui/tooltip';
|
||||
import WidgetHeader from './WidgetHeader.vue';
|
||||
import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table';
|
||||
|
||||
const FEED_TYPES = ['GPS', 'Online', 'Offline', 'Status', 'Avatar', 'Bio'];
|
||||
|
||||
@@ -85,15 +114,22 @@
|
||||
|
||||
const filteredData = computed(() => {
|
||||
const filters = activeFilters.value;
|
||||
return feedStore.feedTableData.filter((item) => filters.includes(item.type));
|
||||
return feedStore.feedTableData.filter((item) => filters.includes(item.type)).slice(0, 100);
|
||||
});
|
||||
|
||||
function formatTime(dateStr) {
|
||||
function timeAgo(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
let diff = Date.now() - new Date(dateStr).getTime();
|
||||
if (diff < 0) return 'now';
|
||||
// Over 1 hour: drop minutes
|
||||
if (diff >= 3600000) {
|
||||
diff = Math.floor(diff / 3600000) * 3600000;
|
||||
}
|
||||
return t('dashboard.widget.time_ago', { time: timeToText(diff) });
|
||||
}
|
||||
|
||||
function formatExactTime(dateStr) {
|
||||
return formatDateFilter(dateStr, 'long');
|
||||
}
|
||||
|
||||
function openUser(userId) {
|
||||
|
||||
@@ -6,44 +6,65 @@
|
||||
route-name="game-log" />
|
||||
|
||||
<div class="min-h-0 flex-1 overflow-y-auto">
|
||||
<template v-if="filteredData.length">
|
||||
<div
|
||||
v-for="(item, index) in filteredData"
|
||||
:key="`${item.type}-${item.created_at}-${index}`"
|
||||
class="flex items-center gap-1.5 border-b border-border/30 px-2.5 py-0.75 text-[13px] leading-snug hover:bg-accent/50"
|
||||
:class="{ 'border-l-2 border-l-chart-4': item.isFavorite }">
|
||||
<span class="shrink-0 text-[11px] tabular-nums text-muted-foreground">{{ formatTime(item.created_at) }}</span>
|
||||
|
||||
<template v-if="item.type === 'Location'">
|
||||
<span class="truncate font-medium text-foreground">🌍 {{ item.worldName || item.location }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'OnPlayerJoined'">
|
||||
<span class="shrink-0 font-semibold text-chart-2">→</span>
|
||||
<span
|
||||
class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline"
|
||||
:style="item.tagColour ? { color: item.tagColour } : null"
|
||||
@click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'OnPlayerLeft'">
|
||||
<span class="shrink-0 font-semibold text-muted-foreground/60">←</span>
|
||||
<span
|
||||
class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline"
|
||||
:style="item.tagColour ? { color: item.tagColour } : null"
|
||||
@click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'VideoPlay'">
|
||||
<span class="truncate text-muted-foreground">🎬 {{ item.videoName || item.videoUrl }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'PortalSpawn'">
|
||||
<span class="shrink-0 max-w-[140px] cursor-pointer truncate font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="truncate text-muted-foreground">🌀 {{ item.worldName || '' }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="shrink-0 max-w-[140px] truncate font-medium">{{ item.displayName }}</span>
|
||||
<span class="truncate text-muted-foreground">{{ item.type }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<Table v-if="filteredData.length" class="is-compact-table">
|
||||
<TableBody>
|
||||
<TableRow
|
||||
v-for="(item, index) in filteredData"
|
||||
:key="`${item.type}-${item.created_at}-${index}`"
|
||||
class="cursor-default"
|
||||
:class="{ 'border-l-2 border-l-chart-4': item.isFavorite }">
|
||||
<TableCell class="w-14 text-[11px] tabular-nums text-muted-foreground">
|
||||
<TooltipWrapper :content="formatExactTime(item.created_at)" side="top">
|
||||
<span>{{ timeAgo(item.created_at) }}</span>
|
||||
</TooltipWrapper>
|
||||
</TableCell>
|
||||
<TableCell class="truncate">
|
||||
<template v-if="item.type === 'Location'">
|
||||
<Globe class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<Location
|
||||
class="inline [&>div]:inline-flex"
|
||||
:location="item.location"
|
||||
:hint="item.worldName"
|
||||
disable-tooltip />
|
||||
</template>
|
||||
<template v-else-if="item.type === 'OnPlayerJoined'">
|
||||
<i class="x-user-status online mr-1"></i>
|
||||
<span
|
||||
class="cursor-pointer font-medium hover:underline"
|
||||
:style="item.tagColour ? { color: item.tagColour } : null"
|
||||
@click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'OnPlayerLeft'">
|
||||
<i class="x-user-status mr-1"></i>
|
||||
<span
|
||||
class="cursor-pointer font-medium text-muted-foreground/70 hover:underline"
|
||||
:style="item.tagColour ? { color: item.tagColour } : null"
|
||||
@click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'VideoPlay'">
|
||||
<Video class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span class="text-muted-foreground">{{ item.videoName || item.videoUrl }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'PortalSpawn'">
|
||||
<Waypoints class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<span class="cursor-pointer font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
||||
<span class="text-muted-foreground"> → </span>
|
||||
<Location
|
||||
v-if="item.location"
|
||||
class="inline [&>div]:inline-flex"
|
||||
:location="item.location"
|
||||
:hint="item.worldName"
|
||||
disable-tooltip />
|
||||
<span v-else class="text-muted-foreground">{{ item.worldName || '' }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="font-medium">{{ item.displayName }}</span>
|
||||
<span class="text-muted-foreground"> {{ item.type }}</span>
|
||||
</template>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div v-else class="flex h-full items-center justify-center text-[13px] text-muted-foreground">
|
||||
{{ t('dashboard.widget.no_data') }}
|
||||
</div>
|
||||
@@ -54,13 +75,18 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, shallowRef, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Globe, Video, Waypoints } from 'lucide-vue-next';
|
||||
|
||||
import { database } from '@/services/database';
|
||||
import { showUserDialog } from '@/coordinators/userCoordinator';
|
||||
import { useFriendStore, useGameLogStore } from '@/stores';
|
||||
import { timeToText, formatDateFilter } from '@/shared/utils';
|
||||
import { watchState } from '@/services/watchState';
|
||||
|
||||
import Location from '@/components/Location.vue';
|
||||
import { TooltipWrapper } from '@/components/ui/tooltip';
|
||||
import WidgetHeader from './WidgetHeader.vue';
|
||||
import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table';
|
||||
|
||||
const GAMELOG_TYPES = ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'VideoPlay', 'PortalSpawn', 'Event', 'External'];
|
||||
|
||||
@@ -76,7 +102,7 @@
|
||||
const gameLogStore = useGameLogStore();
|
||||
|
||||
const widgetData = shallowRef([]);
|
||||
const maxEntries = 100;
|
||||
const maxEntries = 200;
|
||||
|
||||
const activeFilters = computed(() => {
|
||||
if (props.config.filters && Array.isArray(props.config.filters) && props.config.filters.length > 0) {
|
||||
@@ -87,7 +113,7 @@
|
||||
|
||||
const filteredData = computed(() => {
|
||||
const filters = activeFilters.value;
|
||||
return widgetData.value.filter((item) => filters.includes(item.type));
|
||||
return widgetData.value.filter((item) => filters.includes(item.type)).slice(0, maxEntries);
|
||||
});
|
||||
|
||||
async function loadInitialData() {
|
||||
@@ -135,12 +161,19 @@
|
||||
}
|
||||
});
|
||||
|
||||
function formatTime(dateStr) {
|
||||
function timeAgo(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
let diff = Date.now() - new Date(dateStr).getTime();
|
||||
if (diff < 0) return 'now';
|
||||
// Over 1 hour: drop minutes
|
||||
if (diff >= 3600000) {
|
||||
diff = Math.floor(diff / 3600000) * 3600000;
|
||||
}
|
||||
return t('dashboard.widget.time_ago', { time: timeToText(diff) });
|
||||
}
|
||||
|
||||
function formatExactTime(dateStr) {
|
||||
return formatDateFilter(dateStr, 'long');
|
||||
}
|
||||
|
||||
function openUser(userId) {
|
||||
|
||||
@@ -1,55 +1,69 @@
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
<WidgetHeader
|
||||
:title="headerTitle"
|
||||
:title="t('dashboard.widget.instance')"
|
||||
icon="ri-group-3-line"
|
||||
route-name="player-list" />
|
||||
|
||||
<div class="min-h-0 flex-1 overflow-y-auto" v-if="hasPlayers">
|
||||
<div
|
||||
v-for="player in playersData"
|
||||
:key="player.ref?.id || player.displayName"
|
||||
class="flex cursor-pointer items-center gap-1.5 border-b border-border/30 px-2.5 py-0.75 text-[13px] leading-snug hover:bg-accent/50"
|
||||
@click="openUser(player)">
|
||||
<span v-if="isColumnVisible('icon')" class="shrink-0 min-w-5 text-[11px]">
|
||||
<span v-if="player.isMaster">👑</span>
|
||||
<span v-else-if="player.isModerator">⚔️</span>
|
||||
<span v-if="player.isFriend">💚</span>
|
||||
<span v-if="player.isBlocked" class="text-destructive">⛔</span>
|
||||
<span v-if="player.isMuted" class="text-muted-foreground">🔇</span>
|
||||
</span>
|
||||
|
||||
<span class="flex-1 truncate font-medium" :class="player.ref?.$trustClass">
|
||||
{{ player.displayName }}
|
||||
</span>
|
||||
|
||||
<span v-if="isColumnVisible('rank')" class="shrink-0 max-w-20 truncate text-[11px]" :class="player.ref?.$trustClass">
|
||||
{{ player.ref?.$trustLevel || '' }}
|
||||
</span>
|
||||
|
||||
<span v-if="isColumnVisible('platform')" class="flex shrink-0 items-center">
|
||||
<Monitor v-if="player.ref?.$platform === 'standalonewindows'" class="h-3 w-3 x-tag-platform-pc" />
|
||||
<Smartphone v-else-if="player.ref?.$platform === 'android'" class="h-3 w-3 x-tag-platform-quest" />
|
||||
<Apple v-else-if="player.ref?.$platform === 'ios'" class="h-3 w-3 x-tag-platform-ios" />
|
||||
</span>
|
||||
|
||||
<span v-if="isColumnVisible('language')" class="flex shrink-0 items-center">
|
||||
<span
|
||||
v-for="lang in (player.ref?.$languages || []).slice(0, 2)"
|
||||
:key="lang.key"
|
||||
:class="['flags', 'inline-block', 'mr-0.5', languageClass(lang.key)]">
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span v-if="isColumnVisible('status')" class="shrink-0">
|
||||
<i class="x-user-status shrink-0" :class="player.ref?.status ? statusClass(player.ref.status) : ''"></i>
|
||||
</span>
|
||||
|
||||
<span v-if="isColumnVisible('timer')" class="shrink-0 text-[11px] tabular-nums text-muted-foreground">
|
||||
<Timer v-if="player.timer" :epoch="player.timer" />
|
||||
</span>
|
||||
<template v-if="hasPlayers">
|
||||
<!-- Info bar -->
|
||||
<div class="flex items-center gap-2 border-b px-2.5 py-1.5 text-[12px] text-muted-foreground">
|
||||
<span class="truncate font-medium text-foreground">{{ worldName }}</span>
|
||||
<span v-if="locationInfo" class="shrink-0">· {{ locationInfo }}</span>
|
||||
<span class="shrink-0">· {{ playerCount }} {{ t('dashboard.widget.instance_players') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Player table -->
|
||||
<div class="min-h-0 flex-1 overflow-y-auto">
|
||||
<Table class="is-compact-table">
|
||||
<TableBody>
|
||||
<TableRow
|
||||
v-for="player in playersData"
|
||||
:key="player.ref?.id || player.displayName"
|
||||
class="cursor-pointer"
|
||||
@click="openUser(player)">
|
||||
<TableCell v-if="isColumnVisible('icon')" class="w-12 text-center text-[11px]">
|
||||
<span v-if="player.isMaster">👑</span>
|
||||
<span v-else-if="player.isModerator">⚔️</span>
|
||||
<span v-if="player.isFriend">💚</span>
|
||||
<span v-if="player.isBlocked" class="text-destructive">⛔</span>
|
||||
<span v-if="player.isMuted" class="text-muted-foreground">🔇</span>
|
||||
</TableCell>
|
||||
|
||||
<TableCell class="truncate font-medium" :class="player.ref?.$trustClass">
|
||||
{{ player.displayName }}
|
||||
</TableCell>
|
||||
|
||||
<TableCell v-if="isColumnVisible('rank')" class="w-24 truncate text-[11px]" :class="player.ref?.$trustClass">
|
||||
{{ player.ref?.$trustLevel || '' }}
|
||||
</TableCell>
|
||||
|
||||
<TableCell v-if="isColumnVisible('platform')" class="w-10 text-center">
|
||||
<Monitor v-if="player.ref?.$platform === 'standalonewindows'" class="inline-block h-3 w-3 x-tag-platform-pc" />
|
||||
<Smartphone v-else-if="player.ref?.$platform === 'android'" class="inline-block h-3 w-3 x-tag-platform-quest" />
|
||||
<Apple v-else-if="player.ref?.$platform === 'ios'" class="inline-block h-3 w-3 x-tag-platform-ios" />
|
||||
</TableCell>
|
||||
|
||||
<TableCell v-if="isColumnVisible('language')" class="w-14 text-center">
|
||||
<span
|
||||
v-for="lang in (player.ref?.$languages || []).slice(0, 2)"
|
||||
:key="lang.key"
|
||||
:class="['flags', 'inline-block', 'mr-0.5', languageClass(lang.key)]">
|
||||
</span>
|
||||
</TableCell>
|
||||
|
||||
<TableCell v-if="isColumnVisible('status')" class="w-10 text-center">
|
||||
<i class="x-user-status" :class="player.ref?.status ? statusClass(player.ref.status) : ''"></i>
|
||||
</TableCell>
|
||||
|
||||
<TableCell v-if="isColumnVisible('timer')" class="w-20 text-right text-[11px] tabular-nums text-muted-foreground">
|
||||
<Timer v-if="player.timer" :epoch="player.timer" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="flex h-full items-center justify-center text-[13px] text-muted-foreground">
|
||||
{{ t('dashboard.widget.instance_not_in_game') }}
|
||||
</div>
|
||||
@@ -69,6 +83,7 @@
|
||||
|
||||
import Timer from '@/components/Timer.vue';
|
||||
import WidgetHeader from './WidgetHeader.vue';
|
||||
import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table';
|
||||
|
||||
const ALL_COLUMNS = ['icon', 'displayName', 'rank', 'timer', 'platform', 'language', 'status'];
|
||||
const DEFAULT_COLUMNS = ['icon', 'displayName', 'rank', 'timer'];
|
||||
@@ -104,18 +119,18 @@
|
||||
return currentInstanceUsersData.value || [];
|
||||
});
|
||||
|
||||
const headerTitle = computed(() => {
|
||||
if (!hasPlayers.value) {
|
||||
return t('dashboard.widget.instance');
|
||||
}
|
||||
const worldName = computed(() => {
|
||||
const loc = lastLocation.value;
|
||||
const worldName = loc.name || t('dashboard.widget.unknown_world');
|
||||
const playerCount = loc.playerList?.size || 0;
|
||||
const locationInfo = loc.location ? displayLocation(loc.location, worldName) : '';
|
||||
if (locationInfo) {
|
||||
return `${worldName} · ${locationInfo} · ${playerCount}`;
|
||||
}
|
||||
return `${worldName} · ${playerCount}`;
|
||||
return loc.name || t('dashboard.widget.unknown_world');
|
||||
});
|
||||
|
||||
const locationInfo = computed(() => {
|
||||
const loc = lastLocation.value;
|
||||
return loc.location ? displayLocation(loc.location, worldName.value) : '';
|
||||
});
|
||||
|
||||
const playerCount = computed(() => {
|
||||
return lastLocation.value.playerList?.size || 0;
|
||||
});
|
||||
|
||||
function openUser(player) {
|
||||
|
||||
Reference in New Issue
Block a user