mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-27 02:33:48 +02:00
improve dashboard wideget
This commit is contained in:
@@ -32,6 +32,24 @@
|
|||||||
{{ filterType }}
|
{{ filterType }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<label
|
||||||
|
v-if="widgetType === 'game-log'"
|
||||||
|
class="flex items-center gap-1 text-xs cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="panelConfig.showDetail || false"
|
||||||
|
@change="toggleBooleanConfig('showDetail')" />
|
||||||
|
Show Detail
|
||||||
|
</label>
|
||||||
|
<label v-if="widgetType === 'feed'" class="flex items-center gap-1 text-xs cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="panelConfig.showType || false"
|
||||||
|
@change="toggleBooleanConfig('showType')" />
|
||||||
|
Show Type
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Instance: column visibility -->
|
<!-- Instance: column visibility -->
|
||||||
@@ -89,7 +107,15 @@
|
|||||||
import { panelComponentMap } from './panelRegistry';
|
import { panelComponentMap } from './panelRegistry';
|
||||||
|
|
||||||
const FEED_TYPES = ['GPS', 'Online', 'Offline', 'Status', 'Avatar', 'Bio'];
|
const FEED_TYPES = ['GPS', 'Online', 'Offline', 'Status', 'Avatar', 'Bio'];
|
||||||
const GAMELOG_TYPES = ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'VideoPlay', 'PortalSpawn', 'Event', 'External'];
|
const GAMELOG_TYPES = [
|
||||||
|
'Location',
|
||||||
|
'OnPlayerJoined',
|
||||||
|
'OnPlayerLeft',
|
||||||
|
'VideoPlay',
|
||||||
|
'PortalSpawn',
|
||||||
|
'Event',
|
||||||
|
'External'
|
||||||
|
];
|
||||||
const INSTANCE_COLUMNS = ['icon', 'displayName', 'rank', 'timer', 'platform', 'language', 'status'];
|
const INSTANCE_COLUMNS = ['icon', 'displayName', 'rank', 'timer', 'platform', 'language', 'status'];
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -189,19 +215,19 @@
|
|||||||
emitConfigUpdate({ ...panelConfig.value, filters });
|
emitConfigUpdate({ ...panelConfig.value, filters });
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableColumns = computed(() => INSTANCE_COLUMNS);
|
const availableColumns = computed(() => INSTANCE_COLUMNS);
|
||||||
|
|
||||||
function isColumnActive(col) {
|
function isColumnActive(col) {
|
||||||
const columns = panelConfig.value.columns;
|
const columns = panelConfig.value.columns;
|
||||||
if (!columns || !Array.isArray(columns) || columns.length === 0) {
|
if (!columns || !Array.isArray(columns) || columns.length === 0) {
|
||||||
return ['icon', 'displayName', 'rank', 'timer'].includes(col);
|
return ['icon', 'displayName', 'timer'].includes(col);
|
||||||
}
|
}
|
||||||
return columns.includes(col);
|
return columns.includes(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleColumn(col) {
|
function toggleColumn(col) {
|
||||||
if (col === 'displayName') return; // Always visible
|
if (col === 'displayName') return; // Always visible
|
||||||
const currentColumns = panelConfig.value.columns || ['icon', 'displayName', 'rank', 'timer'];
|
const currentColumns = panelConfig.value.columns || ['icon', 'displayName', 'timer'];
|
||||||
let columns;
|
let columns;
|
||||||
if (currentColumns.includes(col)) {
|
if (currentColumns.includes(col)) {
|
||||||
columns = currentColumns.filter((c) => c !== col);
|
columns = currentColumns.filter((c) => c !== col);
|
||||||
@@ -211,6 +237,10 @@
|
|||||||
emitConfigUpdate({ ...panelConfig.value, columns });
|
emitConfigUpdate({ ...panelConfig.value, columns });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleBooleanConfig(key) {
|
||||||
|
emitConfigUpdate({ ...panelConfig.value, [key]: !panelConfig.value[key] });
|
||||||
|
}
|
||||||
|
|
||||||
function emitConfigUpdate(newConfig) {
|
function emitConfigUpdate(newConfig) {
|
||||||
emit('select', { key: panelKey.value, config: newConfig });
|
emit('select', { key: panelKey.value, config: newConfig });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex h-full min-h-0 flex-col">
|
<div class="flex h-full min-h-0 flex-col">
|
||||||
<WidgetHeader
|
<WidgetHeader :title="t('dashboard.widget.feed')" icon="ri-rss-line" route-name="feed" />
|
||||||
:title="t('dashboard.widget.feed')"
|
|
||||||
icon="ri-rss-line"
|
|
||||||
route-name="feed" />
|
|
||||||
|
|
||||||
<div class="min-h-0 flex-1 overflow-y-auto" ref="listRef">
|
<div class="min-h-0 flex-1 overflow-y-auto" ref="listRef">
|
||||||
<Table v-if="filteredData.length" class="is-compact-table">
|
<Table v-if="filteredData.length" class="is-compact-table">
|
||||||
@@ -13,15 +10,22 @@
|
|||||||
:key="`${item.type}-${item.created_at}-${index}`"
|
:key="`${item.type}-${item.created_at}-${index}`"
|
||||||
class="cursor-default"
|
class="cursor-default"
|
||||||
:class="{ 'border-l-2 border-l-chart-4': item.isFavorite }">
|
:class="{ 'border-l-2 border-l-chart-4': item.isFavorite }">
|
||||||
<TableCell class="w-14 text-[11px] tabular-nums text-muted-foreground">
|
<TableCell class="w-28 text-[11px] tabular-nums text-muted-foreground">
|
||||||
<TooltipWrapper :content="formatExactTime(item.created_at)" side="top">
|
<TooltipWrapper :content="formatExactTime(item.created_at)" side="top">
|
||||||
<span>{{ timeAgo(item.created_at) }}</span>
|
<span>{{ formatTime(item.created_at) }}</span>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell v-if="showType" class="w-16 text-[11px] text-muted-foreground">
|
||||||
|
{{ item.type }}
|
||||||
|
</TableCell>
|
||||||
<TableCell class="truncate">
|
<TableCell class="truncate">
|
||||||
<template v-if="item.type === 'GPS'">
|
<template v-if="item.type === 'GPS'">
|
||||||
<MapPin class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
<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="cursor-pointer font-medium hover:underline"
|
||||||
|
@click="openUser(item.userId)"
|
||||||
|
>{{ item.displayName }}</span
|
||||||
|
>
|
||||||
<span class="text-muted-foreground"> → </span>
|
<span class="text-muted-foreground"> → </span>
|
||||||
<Location
|
<Location
|
||||||
class="inline [&>div]:inline-flex"
|
class="inline [&>div]:inline-flex"
|
||||||
@@ -32,7 +36,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'Online'">
|
<template v-else-if="item.type === 'Online'">
|
||||||
<i class="x-user-status online mr-1"></i>
|
<i class="x-user-status online mr-1"></i>
|
||||||
<span class="cursor-pointer font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
<span
|
||||||
|
class="cursor-pointer font-medium hover:underline"
|
||||||
|
@click="openUser(item.userId)"
|
||||||
|
>{{ item.displayName }}</span
|
||||||
|
>
|
||||||
<template v-if="item.location">
|
<template v-if="item.location">
|
||||||
<span class="text-muted-foreground"> → </span>
|
<span class="text-muted-foreground"> → </span>
|
||||||
<Location
|
<Location
|
||||||
@@ -45,25 +53,45 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'Offline'">
|
<template v-else-if="item.type === 'Offline'">
|
||||||
<i class="x-user-status mr-1"></i>
|
<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>
|
<span
|
||||||
|
class="cursor-pointer font-medium text-muted-foreground/70 hover:underline"
|
||||||
|
@click="openUser(item.userId)"
|
||||||
|
>{{ item.displayName }}</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'Status'">
|
<template v-else-if="item.type === 'Status'">
|
||||||
<i class="x-user-status mr-1" :class="statusClass(item.status)"></i>
|
<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="cursor-pointer font-medium hover:underline"
|
||||||
|
@click="openUser(item.userId)"
|
||||||
|
>{{ item.displayName }}</span
|
||||||
|
>
|
||||||
<span class="text-muted-foreground"> {{ item.statusDescription }}</span>
|
<span class="text-muted-foreground"> {{ item.statusDescription }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'Avatar'">
|
<template v-else-if="item.type === 'Avatar'">
|
||||||
<Box class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
<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="cursor-pointer font-medium hover:underline"
|
||||||
|
@click="openUser(item.userId)"
|
||||||
|
>{{ item.displayName }}</span
|
||||||
|
>
|
||||||
<span class="text-muted-foreground"> → {{ item.avatarName }}</span>
|
<span class="text-muted-foreground"> → {{ item.avatarName }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'Bio'">
|
<template v-else-if="item.type === 'Bio'">
|
||||||
<Pencil class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
<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="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>
|
<span class="ml-1 text-muted-foreground">{{ t('dashboard.widget.feed_bio') }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="cursor-pointer font-medium hover:underline" @click="openUser(item.userId)">{{ item.displayName }}</span>
|
<span
|
||||||
|
class="cursor-pointer font-medium hover:underline"
|
||||||
|
@click="openUser(item.userId)"
|
||||||
|
>{{ item.displayName }}</span
|
||||||
|
>
|
||||||
<span class="text-muted-foreground"> {{ item.type }}</span>
|
<span class="text-muted-foreground"> {{ item.type }}</span>
|
||||||
</template>
|
</template>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -83,7 +111,7 @@
|
|||||||
import { Box, MapPin, Pencil } from 'lucide-vue-next';
|
import { Box, MapPin, Pencil } from 'lucide-vue-next';
|
||||||
|
|
||||||
import { statusClass } from '@/shared/utils/user';
|
import { statusClass } from '@/shared/utils/user';
|
||||||
import { timeToText, formatDateFilter } from '@/shared/utils';
|
import { formatDateFilter } from '@/shared/utils';
|
||||||
import { showUserDialog } from '@/coordinators/userCoordinator';
|
import { showUserDialog } from '@/coordinators/userCoordinator';
|
||||||
import { useFeedStore } from '@/stores';
|
import { useFeedStore } from '@/stores';
|
||||||
|
|
||||||
@@ -112,20 +140,17 @@
|
|||||||
return FEED_TYPES;
|
return FEED_TYPES;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showType = computed(() => {
|
||||||
|
return props.config.showType || false;
|
||||||
|
});
|
||||||
|
|
||||||
const filteredData = computed(() => {
|
const filteredData = computed(() => {
|
||||||
const filters = activeFilters.value;
|
const filters = activeFilters.value;
|
||||||
return feedStore.feedTableData.filter((item) => filters.includes(item.type)).slice(0, 100);
|
return feedStore.feedTableData.filter((item) => filters.includes(item.type)).slice(0, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
function timeAgo(dateStr) {
|
function formatTime(dateStr) {
|
||||||
if (!dateStr) return '';
|
return formatDateFilter(dateStr, 'short');
|
||||||
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) {
|
function formatExactTime(dateStr) {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex h-full min-h-0 flex-col">
|
<div class="flex h-full min-h-0 flex-col">
|
||||||
<WidgetHeader
|
<WidgetHeader :title="t('dashboard.widget.game_log')" icon="ri-history-line" route-name="game-log" />
|
||||||
:title="t('dashboard.widget.game_log')"
|
|
||||||
icon="ri-history-line"
|
|
||||||
route-name="game-log" />
|
|
||||||
|
|
||||||
<div class="min-h-0 flex-1 overflow-y-auto">
|
<div class="min-h-0 flex-1 overflow-y-auto">
|
||||||
<Table v-if="filteredData.length" class="is-compact-table">
|
<Table v-if="filteredData.length" class="is-compact-table">
|
||||||
@@ -13,14 +10,14 @@
|
|||||||
:key="`${item.type}-${item.created_at}-${index}`"
|
:key="`${item.type}-${item.created_at}-${index}`"
|
||||||
class="cursor-default"
|
class="cursor-default"
|
||||||
:class="{ 'border-l-2 border-l-chart-4': item.isFavorite }">
|
:class="{ 'border-l-2 border-l-chart-4': item.isFavorite }">
|
||||||
<TableCell class="w-14 text-[11px] tabular-nums text-muted-foreground">
|
<TableCell class="w-28 text-[11px] tabular-nums text-muted-foreground">
|
||||||
<TooltipWrapper :content="formatExactTime(item.created_at)" side="top">
|
<TooltipWrapper :content="formatExactTime(item.created_at)" side="top">
|
||||||
<span>{{ timeAgo(item.created_at) }}</span>
|
<span>{{ formatTime(item.created_at) }}</span>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="truncate">
|
<TableCell class="truncate">
|
||||||
<template v-if="item.type === 'Location'">
|
<template v-if="item.type === 'Location'">
|
||||||
<Globe class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
<MapPin class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
<Location
|
<Location
|
||||||
class="inline [&>div]:inline-flex"
|
class="inline [&>div]:inline-flex"
|
||||||
:location="item.location"
|
:location="item.location"
|
||||||
@@ -28,18 +25,22 @@
|
|||||||
disable-tooltip />
|
disable-tooltip />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'OnPlayerJoined'">
|
<template v-else-if="item.type === 'OnPlayerJoined'">
|
||||||
<i class="x-user-status online mr-1"></i>
|
<LogIn class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
<span
|
<span
|
||||||
class="cursor-pointer font-medium hover:underline"
|
class="cursor-pointer font-medium hover:underline"
|
||||||
:style="item.tagColour ? { color: item.tagColour } : null"
|
:style="item.tagColour ? { color: item.tagColour } : null"
|
||||||
@click="openUser(item.userId)">{{ item.displayName }}</span>
|
@click="openUser(item.userId)"
|
||||||
|
>{{ item.displayName }}</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'OnPlayerLeft'">
|
<template v-else-if="item.type === 'OnPlayerLeft'">
|
||||||
<i class="x-user-status mr-1"></i>
|
<LogOut class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
<span
|
<span
|
||||||
class="cursor-pointer font-medium text-muted-foreground/70 hover:underline"
|
class="cursor-pointer font-medium text-muted-foreground/70 hover:underline"
|
||||||
:style="item.tagColour ? { color: item.tagColour } : null"
|
:style="item.tagColour ? { color: item.tagColour } : null"
|
||||||
@click="openUser(item.userId)">{{ item.displayName }}</span>
|
@click="openUser(item.userId)"
|
||||||
|
>{{ item.displayName }}</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'VideoPlay'">
|
<template v-else-if="item.type === 'VideoPlay'">
|
||||||
<Video class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
<Video class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
@@ -47,7 +48,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.type === 'PortalSpawn'">
|
<template v-else-if="item.type === 'PortalSpawn'">
|
||||||
<Waypoints class="mr-1 inline-block h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
<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="cursor-pointer font-medium hover:underline"
|
||||||
|
@click="openUser(item.userId)"
|
||||||
|
>{{ item.displayName }}</span
|
||||||
|
>
|
||||||
<span class="text-muted-foreground"> → </span>
|
<span class="text-muted-foreground"> → </span>
|
||||||
<Location
|
<Location
|
||||||
v-if="item.location"
|
v-if="item.location"
|
||||||
@@ -58,8 +63,22 @@
|
|||||||
<span v-else class="text-muted-foreground">{{ item.worldName || '' }}</span>
|
<span v-else class="text-muted-foreground">{{ item.worldName || '' }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="font-medium">{{ item.displayName }}</span>
|
<TooltipWrapper
|
||||||
<span class="text-muted-foreground"> {{ item.type }}</span>
|
v-if="!showDetail"
|
||||||
|
:content="item.data || item.message || ''"
|
||||||
|
side="top">
|
||||||
|
<span>
|
||||||
|
<span class="font-medium">{{ item.displayName }}</span>
|
||||||
|
<span class="text-muted-foreground"> {{ item.type }}</span>
|
||||||
|
</span>
|
||||||
|
</TooltipWrapper>
|
||||||
|
<template v-else>
|
||||||
|
<span class="font-medium">{{ item.displayName }}</span>
|
||||||
|
<span class="text-muted-foreground"> {{ item.type }}</span>
|
||||||
|
<span v-if="item.data || item.message" class="ml-1 text-muted-foreground"
|
||||||
|
>— {{ item.data || item.message }}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -75,12 +94,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, shallowRef, watch } from 'vue';
|
import { computed, onMounted, shallowRef, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { Globe, Video, Waypoints } from 'lucide-vue-next';
|
import { LogIn, LogOut, MapPin, Video, Waypoints } from 'lucide-vue-next';
|
||||||
|
|
||||||
import { database } from '@/services/database';
|
import { database } from '@/services/database';
|
||||||
import { showUserDialog } from '@/coordinators/userCoordinator';
|
import { showUserDialog } from '@/coordinators/userCoordinator';
|
||||||
import { useFriendStore, useGameLogStore } from '@/stores';
|
import { useFriendStore, useGameLogStore } from '@/stores';
|
||||||
import { timeToText, formatDateFilter } from '@/shared/utils';
|
import { formatDateFilter } from '@/shared/utils';
|
||||||
import { watchState } from '@/services/watchState';
|
import { watchState } from '@/services/watchState';
|
||||||
|
|
||||||
import Location from '@/components/Location.vue';
|
import Location from '@/components/Location.vue';
|
||||||
@@ -88,7 +107,15 @@
|
|||||||
import WidgetHeader from './WidgetHeader.vue';
|
import WidgetHeader from './WidgetHeader.vue';
|
||||||
import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table';
|
import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table';
|
||||||
|
|
||||||
const GAMELOG_TYPES = ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'VideoPlay', 'PortalSpawn', 'Event', 'External'];
|
const GAMELOG_TYPES = [
|
||||||
|
'Location',
|
||||||
|
'OnPlayerJoined',
|
||||||
|
'OnPlayerLeft',
|
||||||
|
'VideoPlay',
|
||||||
|
'PortalSpawn',
|
||||||
|
'Event',
|
||||||
|
'External'
|
||||||
|
];
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
config: {
|
config: {
|
||||||
@@ -111,6 +138,10 @@
|
|||||||
return GAMELOG_TYPES;
|
return GAMELOG_TYPES;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showDetail = computed(() => {
|
||||||
|
return props.config.showDetail || false;
|
||||||
|
});
|
||||||
|
|
||||||
const filteredData = computed(() => {
|
const filteredData = computed(() => {
|
||||||
const filters = activeFilters.value;
|
const filters = activeFilters.value;
|
||||||
return widgetData.value.filter((item) => filters.includes(item.type)).slice(0, maxEntries);
|
return widgetData.value.filter((item) => filters.includes(item.type)).slice(0, maxEntries);
|
||||||
@@ -161,15 +192,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function timeAgo(dateStr) {
|
function formatTime(dateStr) {
|
||||||
if (!dateStr) return '';
|
return formatDateFilter(dateStr, 'short');
|
||||||
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) {
|
function formatExactTime(dateStr) {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex h-full min-h-0 flex-col">
|
<div class="flex h-full min-h-0 flex-col">
|
||||||
<WidgetHeader
|
<WidgetHeader :title="t('dashboard.widget.instance')" icon="ri-group-3-line" route-name="player-list" />
|
||||||
:title="t('dashboard.widget.instance')"
|
|
||||||
icon="ri-group-3-line"
|
|
||||||
route-name="player-list" />
|
|
||||||
|
|
||||||
<template v-if="hasPlayers">
|
<template v-if="hasPlayers">
|
||||||
<!-- Info bar -->
|
<!-- Info bar -->
|
||||||
@@ -28,20 +25,32 @@
|
|||||||
<span v-if="player.isFriend">💚</span>
|
<span v-if="player.isFriend">💚</span>
|
||||||
<span v-if="player.isBlocked" class="text-destructive">⛔</span>
|
<span v-if="player.isBlocked" class="text-destructive">⛔</span>
|
||||||
<span v-if="player.isMuted" class="text-muted-foreground">🔇</span>
|
<span v-if="player.isMuted" class="text-muted-foreground">🔇</span>
|
||||||
|
<IdCard
|
||||||
|
v-if="player.ageVerified"
|
||||||
|
class="inline-block h-3.5 w-3.5 x-tag-age-verification" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell class="truncate font-medium" :class="player.ref?.$trustClass">
|
<TableCell class="truncate font-medium" :class="player.ref?.$trustClass">
|
||||||
{{ player.displayName }}
|
{{ player.displayName }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell v-if="isColumnVisible('rank')" class="w-24 truncate text-[11px]" :class="player.ref?.$trustClass">
|
<TableCell
|
||||||
|
v-if="isColumnVisible('rank')"
|
||||||
|
class="w-24 truncate text-[11px]"
|
||||||
|
:class="player.ref?.$trustClass">
|
||||||
{{ player.ref?.$trustLevel || '' }}
|
{{ player.ref?.$trustLevel || '' }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell v-if="isColumnVisible('platform')" class="w-10 text-center">
|
<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" />
|
<Monitor
|
||||||
<Smartphone v-else-if="player.ref?.$platform === 'android'" class="inline-block h-3 w-3 x-tag-platform-quest" />
|
v-if="player.ref?.$platform === 'standalonewindows'"
|
||||||
<Apple v-else-if="player.ref?.$platform === 'ios'" class="inline-block h-3 w-3 x-tag-platform-ios" />
|
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>
|
||||||
|
|
||||||
<TableCell v-if="isColumnVisible('language')" class="w-14 text-center">
|
<TableCell v-if="isColumnVisible('language')" class="w-14 text-center">
|
||||||
@@ -53,10 +62,14 @@
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell v-if="isColumnVisible('status')" class="w-10 text-center">
|
<TableCell v-if="isColumnVisible('status')" class="w-10 text-center">
|
||||||
<i class="x-user-status" :class="player.ref?.status ? statusClass(player.ref.status) : ''"></i>
|
<i
|
||||||
|
class="x-user-status"
|
||||||
|
:class="player.ref?.status ? statusClass(player.ref.status) : ''"></i>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell v-if="isColumnVisible('timer')" class="w-20 text-right text-[11px] tabular-nums text-muted-foreground">
|
<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" />
|
<Timer v-if="player.timer" :epoch="player.timer" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -72,7 +85,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onActivated, onMounted } from 'vue';
|
import { computed, onActivated, onMounted } from 'vue';
|
||||||
import { Apple, Monitor, Smartphone } from 'lucide-vue-next';
|
import { Apple, IdCard, Monitor, Smartphone } from 'lucide-vue-next';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@@ -86,7 +99,7 @@
|
|||||||
import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table';
|
import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table';
|
||||||
|
|
||||||
const ALL_COLUMNS = ['icon', 'displayName', 'rank', 'timer', 'platform', 'language', 'status'];
|
const ALL_COLUMNS = ['icon', 'displayName', 'rank', 'timer', 'platform', 'language', 'status'];
|
||||||
const DEFAULT_COLUMNS = ['icon', 'displayName', 'rank', 'timer'];
|
const DEFAULT_COLUMNS = ['icon', 'displayName', 'timer'];
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
config: {
|
config: {
|
||||||
|
|||||||
Reference in New Issue
Block a user