mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 06:56:04 +02:00
feat: custom show/hide datatable col
This commit is contained in:
@@ -10,7 +10,75 @@
|
|||||||
<colgroup>
|
<colgroup>
|
||||||
<col v-for="col in table.getVisibleLeafColumns()" :key="col.id" :style="getColStyle(col)" />
|
<col v-for="col in table.getVisibleLeafColumns()" :key="col.id" :style="getColStyle(col)" />
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<TableHeader>
|
<ContextMenu v-if="enableColumnVisibility">
|
||||||
|
<ContextMenuTrigger as-child>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||||
|
<template v-if="enableColumnReorder">
|
||||||
|
<DragDropProvider @dragEnd="onHeaderDragEnd">
|
||||||
|
<template v-for="(header, hIdx) in headerGroup.headers" :key="header.id">
|
||||||
|
<SortableTableHead
|
||||||
|
v-if="isReorderable(header)"
|
||||||
|
:header="header"
|
||||||
|
:index="reorderableIndex(headerGroup.headers, hIdx)"
|
||||||
|
:header-class="getHeaderClass(header)"
|
||||||
|
:pinned-style="getPinnedStyle(header.column)" />
|
||||||
|
<TableHead
|
||||||
|
v-else
|
||||||
|
:class="getHeaderClass(header)"
|
||||||
|
:style="getPinnedStyle(header.column)">
|
||||||
|
<template v-if="!header.isPlaceholder">
|
||||||
|
<FlexRender
|
||||||
|
:render="header.column.columnDef.header"
|
||||||
|
:props="header.getContext()" />
|
||||||
|
<div
|
||||||
|
v-if="header.column.getCanResize?.()"
|
||||||
|
class="absolute right-0 top-0 h-full w-2 cursor-col-resize touch-none select-none opacity-0 transition-opacity group-hover:opacity-100"
|
||||||
|
@mousedown.stop="header.getResizeHandler?.()($event)"
|
||||||
|
@touchstart.stop="header.getResizeHandler?.()($event)">
|
||||||
|
<div
|
||||||
|
class="absolute right-0 top-0 h-full w-px bg-border dark:bg-border dark:brightness-[2]" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</TableHead>
|
||||||
|
</template>
|
||||||
|
</DragDropProvider>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<TableHead
|
||||||
|
v-for="header in headerGroup.headers"
|
||||||
|
:key="header.id"
|
||||||
|
:class="getHeaderClass(header)"
|
||||||
|
:style="getPinnedStyle(header.column)">
|
||||||
|
<template v-if="!header.isPlaceholder">
|
||||||
|
<FlexRender
|
||||||
|
:render="header.column.columnDef.header"
|
||||||
|
:props="header.getContext()" />
|
||||||
|
<div
|
||||||
|
v-if="header.column.getCanResize?.()"
|
||||||
|
class="absolute right-0 top-0 h-full w-2 cursor-col-resize touch-none select-none opacity-0 transition-opacity group-hover:opacity-100"
|
||||||
|
@mousedown.stop="header.getResizeHandler?.()($event)"
|
||||||
|
@touchstart.stop="header.getResizeHandler?.()($event)">
|
||||||
|
<div
|
||||||
|
class="absolute right-0 top-0 h-full w-px bg-border dark:bg-border dark:brightness-[2]" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</TableHead>
|
||||||
|
</template>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
<ContextMenuContent class="w-48">
|
||||||
|
<ContextMenuCheckboxItem
|
||||||
|
v-for="col in toggleableColumns"
|
||||||
|
:key="col.id"
|
||||||
|
:model-value="col.getIsVisible()"
|
||||||
|
@update:model-value="col.toggleVisibility(!!$event)">
|
||||||
|
{{ resolveHeaderLabel(col) }}
|
||||||
|
</ContextMenuCheckboxItem>
|
||||||
|
</ContextMenuContent>
|
||||||
|
</ContextMenu>
|
||||||
|
<TableHeader v-else>
|
||||||
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||||
<template v-if="enableColumnReorder">
|
<template v-if="enableColumnReorder">
|
||||||
<DragDropProvider @dragEnd="onHeaderDragEnd">
|
<DragDropProvider @dragEnd="onHeaderDragEnd">
|
||||||
@@ -196,7 +264,7 @@
|
|||||||
} from '../pagination';
|
} from '../pagination';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../table';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../select';
|
||||||
import { ContextMenu, ContextMenuTrigger } from '../context-menu';
|
import { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuTrigger } from '../context-menu';
|
||||||
|
|
||||||
import DataTableEmpty from './DataTableEmpty.vue';
|
import DataTableEmpty from './DataTableEmpty.vue';
|
||||||
import SortableTableHead from './SortableTableHead.vue';
|
import SortableTableHead from './SortableTableHead.vue';
|
||||||
@@ -256,6 +324,10 @@
|
|||||||
enableColumnReorder: {
|
enableColumnReorder: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
enableColumnVisibility: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -389,6 +461,23 @@
|
|||||||
props.table.setColumnOrder(fullOrder);
|
props.table.setColumnOrder(fullOrder);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleableColumns = computed(() => {
|
||||||
|
const cols = props.table?.getAllLeafColumns?.() ?? [];
|
||||||
|
return cols.filter((col) => {
|
||||||
|
if (isSpacer(col)) return false;
|
||||||
|
if (isStretch(col)) return false;
|
||||||
|
if (col.columnDef?.meta?.disableVisibilityToggle) return false;
|
||||||
|
if (!col.columnDef?.meta?.label) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolveHeaderLabel = (col) => {
|
||||||
|
const label = col?.columnDef?.meta?.label;
|
||||||
|
if (typeof label === 'function') return label();
|
||||||
|
return label ?? col?.id ?? '';
|
||||||
|
};
|
||||||
|
|
||||||
const getColStyle = (col) => {
|
const getColStyle = (col) => {
|
||||||
if (isSpacer(col)) return { width: '0px' };
|
if (isSpacer(col)) return { width: '0px' };
|
||||||
|
|
||||||
|
|||||||
@@ -198,4 +198,73 @@ describe('useVrcxVueTable persistence', () => {
|
|||||||
);
|
);
|
||||||
expect(stored?.columnOrder).toBeUndefined();
|
expect(stored?.columnOrder).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('persists columnVisibility to localStorage when visibility changes', async () => {
|
||||||
|
const { columnVisibility } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date', 'status'),
|
||||||
|
persistKey: 'test-col-vis'
|
||||||
|
});
|
||||||
|
|
||||||
|
columnVisibility.value = { name: false, status: true };
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
|
||||||
|
const stored = JSON.parse(
|
||||||
|
localStorage.getItem('vrcx:table:test-col-vis')
|
||||||
|
);
|
||||||
|
expect(stored).toBeTruthy();
|
||||||
|
expect(stored.columnVisibility).toEqual({ name: false, status: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('restores persisted columnVisibility on init', () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'vrcx:table:test-restore-vis',
|
||||||
|
JSON.stringify({ columnVisibility: { date: false } })
|
||||||
|
);
|
||||||
|
|
||||||
|
const { columnVisibility } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date', 'status'),
|
||||||
|
persistKey: 'test-restore-vis'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(columnVisibility.value).toEqual({ date: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters stale columnVisibility entries on persist', async () => {
|
||||||
|
const { columnVisibility } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date'),
|
||||||
|
persistKey: 'test-stale-vis'
|
||||||
|
});
|
||||||
|
|
||||||
|
columnVisibility.value = { removed_col: false, name: false };
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
|
||||||
|
const stored = JSON.parse(
|
||||||
|
localStorage.getItem('vrcx:table:test-stale-vis')
|
||||||
|
);
|
||||||
|
expect(stored).toBeTruthy();
|
||||||
|
expect(stored.columnVisibility).toEqual({ name: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not persist columnVisibility when persistColumnVisibility is false', async () => {
|
||||||
|
const { columnVisibility } = useVrcxVueTable({
|
||||||
|
data: [],
|
||||||
|
columns: makeColumns('name', 'date'),
|
||||||
|
persistKey: 'test-no-persist-vis',
|
||||||
|
persistColumnVisibility: false
|
||||||
|
});
|
||||||
|
|
||||||
|
columnVisibility.value = { name: false };
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
|
||||||
|
const stored = JSON.parse(
|
||||||
|
localStorage.getItem('vrcx:table:test-no-persist-vis')
|
||||||
|
);
|
||||||
|
expect(stored?.columnVisibility).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -84,6 +84,25 @@ function filterOrderByColumns(order, columns) {
|
|||||||
return order.filter((id) => ids.has(id));
|
return order.filter((id) => ids.has(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param visibility
|
||||||
|
* @param columns
|
||||||
|
*/
|
||||||
|
function filterVisibilityByColumns(visibility, columns) {
|
||||||
|
if (!visibility || typeof visibility !== 'object') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const ids = new Set((columns ?? []).map((c) => c?.id).filter(Boolean));
|
||||||
|
const out = {};
|
||||||
|
for (const [key, value] of Object.entries(visibility)) {
|
||||||
|
if (ids.has(key)) {
|
||||||
|
out[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param col
|
* @param col
|
||||||
@@ -200,6 +219,9 @@ export function useVrcxVueTable(options) {
|
|||||||
enableColumnReorder = true,
|
enableColumnReorder = true,
|
||||||
initialColumnOrder,
|
initialColumnOrder,
|
||||||
|
|
||||||
|
enableColumnVisibility = true,
|
||||||
|
initialColumnVisibility,
|
||||||
|
|
||||||
fillRemainingSpace = true,
|
fillRemainingSpace = true,
|
||||||
spacerColumnId = '__spacer',
|
spacerColumnId = '__spacer',
|
||||||
|
|
||||||
@@ -207,6 +229,7 @@ export function useVrcxVueTable(options) {
|
|||||||
persistColumnSizing = true,
|
persistColumnSizing = true,
|
||||||
persistSorting = true,
|
persistSorting = true,
|
||||||
persistColumnOrder = true,
|
persistColumnOrder = true,
|
||||||
|
persistColumnVisibility = true,
|
||||||
persistDebounceMs = 200,
|
persistDebounceMs = 200,
|
||||||
|
|
||||||
tableOptions = {}
|
tableOptions = {}
|
||||||
@@ -222,6 +245,7 @@ export function useVrcxVueTable(options) {
|
|||||||
const columnPinning = ref(initialColumnPinning ?? { left: [], right: [] });
|
const columnPinning = ref(initialColumnPinning ?? { left: [], right: [] });
|
||||||
const columnSizing = ref(initialColumnSizing ?? {});
|
const columnSizing = ref(initialColumnSizing ?? {});
|
||||||
const columnOrder = ref(initialColumnOrder ?? []);
|
const columnOrder = ref(initialColumnOrder ?? []);
|
||||||
|
const columnVisibility = ref(initialColumnVisibility ?? {});
|
||||||
|
|
||||||
const storageKey = persistKey ? `vrcx:table:${persistKey}` : null;
|
const storageKey = persistKey ? `vrcx:table:${persistKey}` : null;
|
||||||
|
|
||||||
@@ -268,6 +292,10 @@ export function useVrcxVueTable(options) {
|
|||||||
columnOrder.value = persisted.columnOrder;
|
columnOrder.value = persisted.columnOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (persisted && persistColumnVisibility && persisted.columnVisibility) {
|
||||||
|
columnVisibility.value = persisted.columnVisibility;
|
||||||
|
}
|
||||||
|
|
||||||
const state = {};
|
const state = {};
|
||||||
const handlers = {};
|
const handlers = {};
|
||||||
const rowModels = {};
|
const rowModels = {};
|
||||||
@@ -337,6 +365,13 @@ export function useVrcxVueTable(options) {
|
|||||||
'onColumnOrderChange'
|
'onColumnOrderChange'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
register(
|
||||||
|
enableColumnVisibility,
|
||||||
|
'columnVisibility',
|
||||||
|
columnVisibility,
|
||||||
|
'onColumnVisibilityChange'
|
||||||
|
);
|
||||||
|
|
||||||
if (enableFiltering) {
|
if (enableFiltering) {
|
||||||
Object.assign(rowModels, {
|
Object.assign(rowModels, {
|
||||||
getFilteredRowModel: getFilteredRowModel()
|
getFilteredRowModel: getFilteredRowModel()
|
||||||
@@ -433,6 +468,19 @@ export function useVrcxVueTable(options) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (storageKey && persistColumnVisibility) {
|
||||||
|
watch(
|
||||||
|
columnVisibility,
|
||||||
|
(val) => {
|
||||||
|
const cols = table.getAllLeafColumns?.() ?? [];
|
||||||
|
persistWrite({
|
||||||
|
columnVisibility: filterVisibilityByColumns(val, cols)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
table,
|
table,
|
||||||
sorting,
|
sorting,
|
||||||
@@ -440,6 +488,7 @@ export function useVrcxVueTable(options) {
|
|||||||
expanded,
|
expanded,
|
||||||
columnPinning,
|
columnPinning,
|
||||||
columnSizing,
|
columnSizing,
|
||||||
columnOrder
|
columnOrder,
|
||||||
|
columnVisibility
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ export const columns = [
|
|||||||
{
|
{
|
||||||
accessorKey: 'created_at',
|
accessorKey: 'created_at',
|
||||||
size: 140,
|
size: 140,
|
||||||
|
meta: { label: () => t('table.feed.date') },
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -257,6 +258,7 @@ export const columns = [
|
|||||||
accessorKey: 'type',
|
accessorKey: 'type',
|
||||||
size: 130,
|
size: 130,
|
||||||
header: () => t('table.feed.type'),
|
header: () => t('table.feed.type'),
|
||||||
|
meta: { label: () => t('table.feed.type') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const type = row.getValue('type');
|
const type = row.getValue('type');
|
||||||
return (
|
return (
|
||||||
@@ -272,6 +274,7 @@ export const columns = [
|
|||||||
accessorKey: 'displayName',
|
accessorKey: 'displayName',
|
||||||
size: 190,
|
size: 190,
|
||||||
header: () => t('table.feed.user'),
|
header: () => t('table.feed.user'),
|
||||||
|
meta: { label: () => t('table.feed.user') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const { showUserDialog } = useUserStore();
|
const { showUserDialog } = useUserStore();
|
||||||
const original = row.original;
|
const original = row.original;
|
||||||
@@ -291,7 +294,8 @@ export const columns = [
|
|||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
minSize: 100,
|
minSize: 100,
|
||||||
meta: {
|
meta: {
|
||||||
stretch: true
|
stretch: true,
|
||||||
|
label: () => t('table.feed.detail')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const original = row.original;
|
const original = row.original;
|
||||||
|
|||||||
@@ -17,24 +17,27 @@ import {
|
|||||||
|
|
||||||
const { t } = i18n.global;
|
const { t } = i18n.global;
|
||||||
|
|
||||||
const sortButton = ({ column, label, descFirst = false }) => (
|
const sortButton = ({ column, label, descFirst = false }) => {
|
||||||
<Button
|
const resolvedLabel = typeof label === 'function' ? label() : label;
|
||||||
variant="ghost"
|
return (
|
||||||
size="sm"
|
<Button
|
||||||
class="-ml-2 h-8 px-2"
|
variant="ghost"
|
||||||
onClick={() => {
|
size="sm"
|
||||||
const sorted = column.getIsSorted();
|
class="-ml-2 h-8 px-2"
|
||||||
if (!sorted && descFirst) {
|
onClick={() => {
|
||||||
column.toggleSorting(true);
|
const sorted = column.getIsSorted();
|
||||||
return;
|
if (!sorted && descFirst) {
|
||||||
}
|
column.toggleSorting(true);
|
||||||
column.toggleSorting(sorted === 'asc');
|
return;
|
||||||
}}
|
}
|
||||||
>
|
column.toggleSorting(sorted === 'asc');
|
||||||
{label}
|
}}
|
||||||
<ArrowUpDown class="ml-1 h-4 w-4" />
|
>
|
||||||
</Button>
|
{resolvedLabel}
|
||||||
);
|
<ArrowUpDown class="ml-1 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const compareNumbers = (a, b) => (a ?? 0) - (b ?? 0);
|
const compareNumbers = (a, b) => (a ?? 0) - (b ?? 0);
|
||||||
|
|
||||||
@@ -143,10 +146,11 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.friendList.no'),
|
label: () => t('table.friendList.no'),
|
||||||
descFirst: true
|
descFirst: true
|
||||||
}),
|
}),
|
||||||
size: 100,
|
size: 100,
|
||||||
|
meta: { label: () => t('table.friendList.no') },
|
||||||
sortingFn: sortByNumber((row) => row?.$friendNumber ?? 0),
|
sortingFn: sortByNumber((row) => row?.$friendNumber ?? 0),
|
||||||
cell: ({ row }) => <span>{row.original?.$friendNumber || ''}</span>
|
cell: ({ row }) => <span>{row.original?.$friendNumber || ''}</span>
|
||||||
},
|
},
|
||||||
@@ -156,6 +160,7 @@ export const createColumns = ({
|
|||||||
header: () => t('table.friendList.avatar'),
|
header: () => t('table.friendList.avatar'),
|
||||||
size: 90,
|
size: 90,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
meta: { label: () => t('table.friendList.avatar') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const src = userImage(row.original, true);
|
const src = userImage(row.original, true);
|
||||||
return src ? (
|
return src ? (
|
||||||
@@ -175,9 +180,10 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.friendList.displayName')
|
label: () => t('table.friendList.displayName')
|
||||||
}),
|
}),
|
||||||
size: 200,
|
size: 200,
|
||||||
|
meta: { label: () => t('table.friendList.displayName') },
|
||||||
sortingFn: sortByString((row) => row?.displayName ?? ''),
|
sortingFn: sortByString((row) => row?.displayName ?? ''),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const style = randomUserColours?.value
|
const style = randomUserColours?.value
|
||||||
@@ -196,9 +202,10 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.friendList.rank')
|
label: () => t('table.friendList.rank')
|
||||||
}),
|
}),
|
||||||
size: 140,
|
size: 140,
|
||||||
|
meta: { label: () => t('table.friendList.rank') },
|
||||||
sortingFn: sortByNumber((row) => row?.$trustSortNum ?? 0),
|
sortingFn: sortByNumber((row) => row?.$trustSortNum ?? 0),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
if (randomUserColours?.value) {
|
if (randomUserColours?.value) {
|
||||||
@@ -222,11 +229,15 @@ export const createColumns = ({
|
|||||||
id: 'status',
|
id: 'status',
|
||||||
accessorFn: (row) => row?.status,
|
accessorFn: (row) => row?.status,
|
||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({ column, label: t('table.friendList.status') }),
|
sortButton({
|
||||||
|
column,
|
||||||
|
label: () => t('table.friendList.status')
|
||||||
|
}),
|
||||||
minSize: 200,
|
minSize: 200,
|
||||||
sortingFn: sortByStatus,
|
sortingFn: sortByStatus,
|
||||||
meta: {
|
meta: {
|
||||||
stretch: true
|
stretch: true,
|
||||||
|
label: () => t('table.friendList.status')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const status = row.original?.status;
|
const status = row.original?.status;
|
||||||
@@ -250,8 +261,12 @@ export const createColumns = ({
|
|||||||
id: 'language',
|
id: 'language',
|
||||||
accessorFn: (row) => row?.$languages,
|
accessorFn: (row) => row?.$languages,
|
||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({ column, label: t('table.friendList.language') }),
|
sortButton({
|
||||||
|
column,
|
||||||
|
label: () => t('table.friendList.language')
|
||||||
|
}),
|
||||||
size: 130,
|
size: 130,
|
||||||
|
meta: { label: () => t('table.friendList.language') },
|
||||||
sortingFn: sortByLanguages,
|
sortingFn: sortByLanguages,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -279,6 +294,7 @@ export const createColumns = ({
|
|||||||
header: () => t('table.friendList.bioLink'),
|
header: () => t('table.friendList.bioLink'),
|
||||||
size: 130,
|
size: 130,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
meta: { label: () => t('table.friendList.bioLink') },
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{(row.original?.bioLinks ?? [])
|
{(row.original?.bioLinks ?? [])
|
||||||
@@ -305,12 +321,13 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.friendList.joinCount')
|
label: () => t('table.friendList.joinCount')
|
||||||
}),
|
}),
|
||||||
size: 120,
|
size: 120,
|
||||||
sortingFn: sortByNumber((row) => row?.$joinCount ?? 0),
|
sortingFn: sortByNumber((row) => row?.$joinCount ?? 0),
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-right'
|
class: 'text-right',
|
||||||
|
label: () => t('table.friendList.joinCount')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -319,12 +336,13 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.friendList.timeTogether')
|
label: () => t('table.friendList.timeTogether')
|
||||||
}),
|
}),
|
||||||
size: 140,
|
size: 140,
|
||||||
sortingFn: sortByNumber((row) => row?.$timeSpent ?? 0),
|
sortingFn: sortByNumber((row) => row?.$timeSpent ?? 0),
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-right'
|
class: 'text-right',
|
||||||
|
label: () => t('table.friendList.timeTogether')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const time = row.original?.$timeSpent;
|
const time = row.original?.$timeSpent;
|
||||||
@@ -337,9 +355,10 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.friendList.lastSeen')
|
label: () => t('table.friendList.lastSeen')
|
||||||
}),
|
}),
|
||||||
size: 170,
|
size: 170,
|
||||||
|
meta: { label: () => t('table.friendList.lastSeen') },
|
||||||
sortingFn: sortByString((row) => row?.$lastSeen ?? ''),
|
sortingFn: sortByString((row) => row?.$lastSeen ?? ''),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const text = formatDateFilter(row.original?.$lastSeen, 'long');
|
const text = formatDateFilter(row.original?.$lastSeen, 'long');
|
||||||
@@ -352,12 +371,13 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.friendList.mutualFriends')
|
label: () => t('table.friendList.mutualFriends')
|
||||||
}),
|
}),
|
||||||
size: 120,
|
size: 120,
|
||||||
sortingFn: sortByNumber((row) => row?.$mutualCount ?? 0),
|
sortingFn: sortByNumber((row) => row?.$mutualCount ?? 0),
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-right'
|
class: 'text-right',
|
||||||
|
label: () => t('table.friendList.mutualFriends')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const count = row.original?.$mutualCount;
|
const count = row.original?.$mutualCount;
|
||||||
@@ -370,9 +390,10 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.friendList.lastActivity')
|
label: () => t('table.friendList.lastActivity')
|
||||||
}),
|
}),
|
||||||
size: 200,
|
size: 200,
|
||||||
|
meta: { label: () => t('table.friendList.lastActivity') },
|
||||||
sortingFn: sortByString((row) => row?.last_activity ?? ''),
|
sortingFn: sortByString((row) => row?.last_activity ?? ''),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span>
|
<span>
|
||||||
@@ -384,8 +405,12 @@ export const createColumns = ({
|
|||||||
id: 'lastLogin',
|
id: 'lastLogin',
|
||||||
accessorFn: (row) => row?.last_login,
|
accessorFn: (row) => row?.last_login,
|
||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({ column, label: t('table.friendList.lastLogin') }),
|
sortButton({
|
||||||
|
column,
|
||||||
|
label: () => t('table.friendList.lastLogin')
|
||||||
|
}),
|
||||||
size: 200,
|
size: 200,
|
||||||
|
meta: { label: () => t('table.friendList.lastLogin') },
|
||||||
sortingFn: sortByString((row) => row?.last_login ?? ''),
|
sortingFn: sortByString((row) => row?.last_login ?? ''),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span>
|
<span>
|
||||||
@@ -399,9 +424,10 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.friendList.dateJoined')
|
label: () => t('table.friendList.dateJoined')
|
||||||
}),
|
}),
|
||||||
size: 120,
|
size: 120,
|
||||||
|
meta: { label: () => t('table.friendList.dateJoined') },
|
||||||
sortingFn: sortByString((row) => row?.date_joined ?? ''),
|
sortingFn: sortByString((row) => row?.date_joined ?? ''),
|
||||||
cell: ({ row }) => <span>{row.original?.date_joined ?? ''}</span>
|
cell: ({ row }) => <span>{row.original?.date_joined ?? ''}</span>
|
||||||
},
|
},
|
||||||
@@ -411,7 +437,8 @@ export const createColumns = ({
|
|||||||
size: 100,
|
size: 100,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-center'
|
class: 'text-center',
|
||||||
|
label: t('table.friendList.unfriend')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
// TODO(icon): verify unfollow icon replacement
|
// TODO(icon): verify unfollow icon replacement
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
{
|
{
|
||||||
accessorKey: 'created_at',
|
accessorKey: 'created_at',
|
||||||
size: 120,
|
size: 120,
|
||||||
|
meta: { label: () => t('table.friendLog.date') },
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -64,6 +65,7 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
|
|
||||||
size: 160,
|
size: 160,
|
||||||
header: () => t('table.friendLog.type'),
|
header: () => t('table.friendLog.type'),
|
||||||
|
meta: { label: () => t('table.friendLog.type') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const type = row.getValue('type');
|
const type = row.getValue('type');
|
||||||
return (
|
return (
|
||||||
@@ -79,7 +81,8 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
minSize: 80,
|
minSize: 80,
|
||||||
header: () => t('table.friendLog.user'),
|
header: () => t('table.friendLog.user'),
|
||||||
meta: {
|
meta: {
|
||||||
stretch: true
|
stretch: true,
|
||||||
|
label: () => t('table.friendLog.user')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const original = row.original;
|
const original = row.original;
|
||||||
@@ -113,7 +116,8 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
{
|
{
|
||||||
id: 'action',
|
id: 'action',
|
||||||
meta: {
|
meta: {
|
||||||
class: 'w-[80px] max-w-[80px] text-right'
|
class: 'w-[80px] max-w-[80px] text-right',
|
||||||
|
label: () => t('table.friendLog.action')
|
||||||
},
|
},
|
||||||
size: 80,
|
size: 80,
|
||||||
maxSize: 80,
|
maxSize: 80,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const createColumns = ({ getCreatedAt, onDelete, onDeletePrompt }) => {
|
|||||||
accessorFn: (row) => getCreatedAt(row),
|
accessorFn: (row) => getCreatedAt(row),
|
||||||
id: 'created_at',
|
id: 'created_at',
|
||||||
size: 140,
|
size: 140,
|
||||||
|
meta: { label: () => t('table.gameLog.date') },
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -70,6 +71,7 @@ export const createColumns = ({ getCreatedAt, onDelete, onDeletePrompt }) => {
|
|||||||
accessorKey: 'type',
|
accessorKey: 'type',
|
||||||
size: 150,
|
size: 150,
|
||||||
header: () => t('table.gameLog.type'),
|
header: () => t('table.gameLog.type'),
|
||||||
|
meta: { label: () => t('table.gameLog.type') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const original = row.original;
|
const original = row.original;
|
||||||
const label = t(`view.game_log.filters.${original.type}`);
|
const label = t(`view.game_log.filters.${original.type}`);
|
||||||
@@ -94,6 +96,7 @@ export const createColumns = ({ getCreatedAt, onDelete, onDeletePrompt }) => {
|
|||||||
accessorKey: 'displayName',
|
accessorKey: 'displayName',
|
||||||
size: 200,
|
size: 200,
|
||||||
header: () => t('table.gameLog.user'),
|
header: () => t('table.gameLog.user'),
|
||||||
|
meta: { label: () => t('table.gameLog.user') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const original = row.original;
|
const original = row.original;
|
||||||
const isFriend = original.isFriend;
|
const isFriend = original.isFriend;
|
||||||
@@ -121,7 +124,8 @@ export const createColumns = ({ getCreatedAt, onDelete, onDeletePrompt }) => {
|
|||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
minSize: 150,
|
minSize: 150,
|
||||||
meta: {
|
meta: {
|
||||||
stretch: true
|
stretch: true,
|
||||||
|
label: () => t('table.gameLog.detail')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const original = row.original;
|
const original = row.original;
|
||||||
@@ -248,7 +252,8 @@ export const createColumns = ({ getCreatedAt, onDelete, onDeletePrompt }) => {
|
|||||||
{
|
{
|
||||||
id: 'action',
|
id: 'action',
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-right'
|
class: 'text-right',
|
||||||
|
label: () => t('table.gameLog.action')
|
||||||
},
|
},
|
||||||
size: 90,
|
size: 90,
|
||||||
minSize: 90,
|
minSize: 90,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
{
|
{
|
||||||
accessorKey: 'created',
|
accessorKey: 'created',
|
||||||
size: 120,
|
size: 120,
|
||||||
|
meta: { label: () => t('table.moderation.date') },
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -64,6 +65,7 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
accessorKey: 'type',
|
accessorKey: 'type',
|
||||||
size: 140,
|
size: 140,
|
||||||
header: () => t('table.moderation.type'),
|
header: () => t('table.moderation.type'),
|
||||||
|
meta: { label: () => t('table.moderation.type') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const type = row.getValue('type');
|
const type = row.getValue('type');
|
||||||
const typeKey = `view.moderation.filters.${type}`;
|
const typeKey = `view.moderation.filters.${type}`;
|
||||||
@@ -79,7 +81,8 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
{
|
{
|
||||||
accessorKey: 'sourceDisplayName',
|
accessorKey: 'sourceDisplayName',
|
||||||
meta: {
|
meta: {
|
||||||
class: 'overflow-hidden'
|
class: 'overflow-hidden',
|
||||||
|
label: () => t('table.moderation.source')
|
||||||
},
|
},
|
||||||
size: 120,
|
size: 120,
|
||||||
header: () => t('table.moderation.source'),
|
header: () => t('table.moderation.source'),
|
||||||
@@ -100,7 +103,8 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
size: 200,
|
size: 200,
|
||||||
minSize: 80,
|
minSize: 80,
|
||||||
meta: {
|
meta: {
|
||||||
stretch: true
|
stretch: true,
|
||||||
|
label: () => t('table.moderation.target')
|
||||||
},
|
},
|
||||||
header: () => t('table.moderation.target'),
|
header: () => t('table.moderation.target'),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
@@ -118,7 +122,8 @@ export const createColumns = ({ onDelete, onDeletePrompt }) => {
|
|||||||
{
|
{
|
||||||
id: 'action',
|
id: 'action',
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-right'
|
class: 'text-right',
|
||||||
|
label: () => t('table.moderation.action')
|
||||||
},
|
},
|
||||||
size: 80,
|
size: 80,
|
||||||
minSize: 80,
|
minSize: 80,
|
||||||
|
|||||||
@@ -31,24 +31,27 @@ import { i18n } from '../../plugin';
|
|||||||
|
|
||||||
const { t } = i18n.global;
|
const { t } = i18n.global;
|
||||||
|
|
||||||
const sortButton = ({ column, label, descFirst = false }) => (
|
const sortButton = ({ column, label, descFirst = false }) => {
|
||||||
<Button
|
const resolvedLabel = typeof label === 'function' ? label() : label;
|
||||||
variant="ghost"
|
return (
|
||||||
size="sm"
|
<Button
|
||||||
class="-ml-2 h-8 px-2"
|
variant="ghost"
|
||||||
onClick={() => {
|
size="sm"
|
||||||
const sorted = column.getIsSorted();
|
class="-ml-2 h-8 px-2"
|
||||||
if (!sorted && descFirst) {
|
onClick={() => {
|
||||||
column.toggleSorting(true);
|
const sorted = column.getIsSorted();
|
||||||
return;
|
if (!sorted && descFirst) {
|
||||||
}
|
column.toggleSorting(true);
|
||||||
column.toggleSorting(sorted === 'asc');
|
return;
|
||||||
}}
|
}
|
||||||
>
|
column.toggleSorting(sorted === 'asc');
|
||||||
{label}
|
}}
|
||||||
<ArrowUpDown class="ml-1 h-4 w-4" />
|
>
|
||||||
</Button>
|
{resolvedLabel}
|
||||||
);
|
<ArrowUpDown class="ml-1 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export function getColumns({
|
export function getColumns({
|
||||||
onShowAvatarDialog,
|
onShowAvatarDialog,
|
||||||
@@ -102,8 +105,12 @@ export function getColumns({
|
|||||||
id: 'name',
|
id: 'name',
|
||||||
accessorKey: 'Name',
|
accessorKey: 'Name',
|
||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({ column, label: t('dialog.avatar.info.name') }),
|
sortButton({
|
||||||
|
column,
|
||||||
|
label: () => t('dialog.avatar.info.name')
|
||||||
|
}),
|
||||||
size: 200,
|
size: 200,
|
||||||
|
meta: { label: () => t('dialog.avatar.info.name') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const ref = row.original;
|
const ref = row.original;
|
||||||
return (
|
return (
|
||||||
@@ -124,6 +131,7 @@ export function getColumns({
|
|||||||
header: () => t('dialog.avatar.info.platform'),
|
header: () => t('dialog.avatar.info.platform'),
|
||||||
size: 120,
|
size: 120,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
meta: { label: () => t('dialog.avatar.info.platform') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const ref = row.original;
|
const ref = row.original;
|
||||||
const platforms = getAvailablePlatforms(ref.unityPackages);
|
const platforms = getAvailablePlatforms(ref.unityPackages);
|
||||||
@@ -160,6 +168,7 @@ export function getColumns({
|
|||||||
header: () => t('dialog.avatar.info.tags'),
|
header: () => t('dialog.avatar.info.tags'),
|
||||||
size: 150,
|
size: 150,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
meta: { label: () => t('dialog.avatar.info.tags') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const tags = row.original.$tags || [];
|
const tags = row.original.$tags || [];
|
||||||
if (!tags.length) return null;
|
if (!tags.length) return null;
|
||||||
@@ -198,6 +207,7 @@ export function getColumns({
|
|||||||
accessorKey: 'releaseStatus',
|
accessorKey: 'releaseStatus',
|
||||||
header: () => t('dialog.avatar.tags.public'),
|
header: () => t('dialog.avatar.tags.public'),
|
||||||
size: 120,
|
size: 120,
|
||||||
|
meta: { label: () => t('dialog.avatar.tags.public') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const ref = row.original;
|
const ref = row.original;
|
||||||
return (
|
return (
|
||||||
@@ -215,12 +225,13 @@ export function getColumns({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('dialog.avatar.info.time_spent'),
|
label: () => t('dialog.avatar.info.time_spent'),
|
||||||
descFirst: true
|
descFirst: true
|
||||||
}),
|
}),
|
||||||
size: 140,
|
size: 140,
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-right'
|
class: 'text-right',
|
||||||
|
label: () => t('dialog.avatar.info.time_spent')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const time = row.original?.$timeSpent;
|
const time = row.original?.$timeSpent;
|
||||||
@@ -237,12 +248,13 @@ export function getColumns({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('dialog.avatar.info.version'),
|
label: () => t('dialog.avatar.info.version'),
|
||||||
descFirst: true
|
descFirst: true
|
||||||
}),
|
}),
|
||||||
size: 90,
|
size: 90,
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-right'
|
class: 'text-right',
|
||||||
|
label: () => t('dialog.avatar.info.version')
|
||||||
},
|
},
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<span class=" text-sm">{row.original.version ?? '-'}</span>
|
<span class=" text-sm">{row.original.version ?? '-'}</span>
|
||||||
@@ -255,9 +267,10 @@ export function getColumns({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('dialog.avatar.info.pc_performance')
|
label: () => t('dialog.avatar.info.pc_performance')
|
||||||
}),
|
}),
|
||||||
size: 140,
|
size: 140,
|
||||||
|
meta: { label: () => t('dialog.avatar.info.pc_performance') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const perf = getPlatformInfo(row.original.unityPackages)?.pc
|
const perf = getPlatformInfo(row.original.unityPackages)?.pc
|
||||||
?.performanceRating;
|
?.performanceRating;
|
||||||
@@ -276,9 +289,10 @@ export function getColumns({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('dialog.avatar.info.android_performance')
|
label: () => t('dialog.avatar.info.android_performance')
|
||||||
}),
|
}),
|
||||||
size: 140,
|
size: 140,
|
||||||
|
meta: { label: () => t('dialog.avatar.info.android_performance') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const perf = getPlatformInfo(row.original.unityPackages)
|
const perf = getPlatformInfo(row.original.unityPackages)
|
||||||
?.android?.performanceRating;
|
?.android?.performanceRating;
|
||||||
@@ -297,9 +311,10 @@ export function getColumns({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('dialog.avatar.info.ios_performance')
|
label: () => t('dialog.avatar.info.ios_performance')
|
||||||
}),
|
}),
|
||||||
size: 140,
|
size: 140,
|
||||||
|
meta: { label: () => t('dialog.avatar.info.ios_performance') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const perf = getPlatformInfo(row.original.unityPackages)?.ios
|
const perf = getPlatformInfo(row.original.unityPackages)?.ios
|
||||||
?.performanceRating;
|
?.performanceRating;
|
||||||
@@ -316,10 +331,11 @@ export function getColumns({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('dialog.avatar.info.last_updated'),
|
label: () => t('dialog.avatar.info.last_updated'),
|
||||||
descFirst: true
|
descFirst: true
|
||||||
}),
|
}),
|
||||||
size: 160,
|
size: 160,
|
||||||
|
meta: { label: () => t('dialog.avatar.info.last_updated') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const ref = row.original;
|
const ref = row.original;
|
||||||
return (
|
return (
|
||||||
@@ -335,10 +351,11 @@ export function getColumns({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('dialog.avatar.info.created_at'),
|
label: () => t('dialog.avatar.info.created_at'),
|
||||||
descFirst: true
|
descFirst: true
|
||||||
}),
|
}),
|
||||||
size: 160,
|
size: 160,
|
||||||
|
meta: { label: () => t('dialog.avatar.info.created_at') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const ref = row.original;
|
const ref = row.original;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ export const createColumns = ({
|
|||||||
accessorFn: (row) => getNotificationCreatedAtTs(row),
|
accessorFn: (row) => getNotificationCreatedAtTs(row),
|
||||||
id: 'created_at',
|
id: 'created_at',
|
||||||
size: 120,
|
size: 120,
|
||||||
|
meta: { label: () => t('table.notification.date') },
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -151,6 +152,7 @@ export const createColumns = ({
|
|||||||
accessorKey: 'type',
|
accessorKey: 'type',
|
||||||
size: 180,
|
size: 180,
|
||||||
header: () => t('table.notification.type'),
|
header: () => t('table.notification.type'),
|
||||||
|
meta: { label: () => t('table.notification.type') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const original = row.original;
|
const original = row.original;
|
||||||
const typeKey = `view.notification.filters.${original.type}`;
|
const typeKey = `view.notification.filters.${original.type}`;
|
||||||
@@ -228,7 +230,8 @@ export const createColumns = ({
|
|||||||
{
|
{
|
||||||
accessorKey: 'senderUsername',
|
accessorKey: 'senderUsername',
|
||||||
meta: {
|
meta: {
|
||||||
class: 'overflow-hidden'
|
class: 'overflow-hidden',
|
||||||
|
label: () => t('table.notification.user')
|
||||||
},
|
},
|
||||||
size: 150,
|
size: 150,
|
||||||
header: () => t('table.notification.user'),
|
header: () => t('table.notification.user'),
|
||||||
@@ -284,7 +287,8 @@ export const createColumns = ({
|
|||||||
{
|
{
|
||||||
accessorKey: 'groupName',
|
accessorKey: 'groupName',
|
||||||
meta: {
|
meta: {
|
||||||
class: 'overflow-hidden'
|
class: 'overflow-hidden',
|
||||||
|
label: () => t('table.notification.group')
|
||||||
},
|
},
|
||||||
size: 150,
|
size: 150,
|
||||||
header: () => t('table.notification.group'),
|
header: () => t('table.notification.group'),
|
||||||
@@ -390,6 +394,7 @@ export const createColumns = ({
|
|||||||
accessorKey: 'photo',
|
accessorKey: 'photo',
|
||||||
size: 80,
|
size: 80,
|
||||||
header: () => t('table.notification.photo'),
|
header: () => t('table.notification.photo'),
|
||||||
|
meta: { label: () => t('table.notification.photo') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const original = row.original;
|
const original = row.original;
|
||||||
if (original.type === 'boop') {
|
if (original.type === 'boop') {
|
||||||
@@ -458,7 +463,8 @@ export const createColumns = ({
|
|||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
meta: {
|
meta: {
|
||||||
class: 'min-w-0 overflow-hidden',
|
class: 'min-w-0 overflow-hidden',
|
||||||
stretch: true
|
stretch: true,
|
||||||
|
label: () => t('table.notification.message')
|
||||||
},
|
},
|
||||||
minSize: 100,
|
minSize: 100,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
@@ -548,7 +554,8 @@ export const createColumns = ({
|
|||||||
{
|
{
|
||||||
id: 'action',
|
id: 'action',
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-right'
|
class: 'text-right',
|
||||||
|
label: () => t('table.notification.action')
|
||||||
},
|
},
|
||||||
size: 120,
|
size: 120,
|
||||||
minSize: 120,
|
minSize: 120,
|
||||||
|
|||||||
@@ -20,24 +20,27 @@ import { i18n } from '../../plugin';
|
|||||||
|
|
||||||
const { t } = i18n.global;
|
const { t } = i18n.global;
|
||||||
|
|
||||||
const sortButton = ({ column, label, descFirst = false }) => (
|
const sortButton = ({ column, label, descFirst = false }) => {
|
||||||
<Button
|
const resolvedLabel = typeof label === 'function' ? label() : label;
|
||||||
variant="ghost"
|
return (
|
||||||
size="sm"
|
<Button
|
||||||
class="-ml-2 h-8 px-2"
|
variant="ghost"
|
||||||
onClick={() => {
|
size="sm"
|
||||||
const sorted = column.getIsSorted();
|
class="-ml-2 h-8 px-2"
|
||||||
if (!sorted && descFirst) {
|
onClick={() => {
|
||||||
column.toggleSorting(true);
|
const sorted = column.getIsSorted();
|
||||||
return;
|
if (!sorted && descFirst) {
|
||||||
}
|
column.toggleSorting(true);
|
||||||
column.toggleSorting(sorted === 'asc');
|
return;
|
||||||
}}
|
}
|
||||||
>
|
column.toggleSorting(sorted === 'asc');
|
||||||
{label}
|
}}
|
||||||
<ArrowUpDown class="ml-1 h-4 w-4" />
|
>
|
||||||
</Button>
|
{resolvedLabel}
|
||||||
);
|
<ArrowUpDown class="ml-1 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getInstanceIconWeight = (item) => {
|
const getInstanceIconWeight = (item) => {
|
||||||
if (!item) return 0;
|
if (!item) return 0;
|
||||||
@@ -70,6 +73,7 @@ export const createColumns = ({
|
|||||||
header: () => t('table.playerList.avatar'),
|
header: () => t('table.playerList.avatar'),
|
||||||
size: 70,
|
size: 70,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
meta: { label: () => t('table.playerList.avatar') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const userRef = row.original?.ref;
|
const userRef = row.original?.ref;
|
||||||
const src = userImage(userRef);
|
const src = userImage(userRef);
|
||||||
@@ -89,8 +93,12 @@ export const createColumns = ({
|
|||||||
id: 'timer',
|
id: 'timer',
|
||||||
accessorFn: (row) => row?.timer,
|
accessorFn: (row) => row?.timer,
|
||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({ column, label: t('table.playerList.timer') }),
|
sortButton({
|
||||||
|
column,
|
||||||
|
label: () => t('table.playerList.timer')
|
||||||
|
}),
|
||||||
size: 90,
|
size: 90,
|
||||||
|
meta: { label: () => t('table.playerList.timer') },
|
||||||
sortingFn: (rowA, rowB) =>
|
sortingFn: (rowA, rowB) =>
|
||||||
(rowA.original?.timer ?? 0) - (rowB.original?.timer ?? 0),
|
(rowA.original?.timer ?? 0) - (rowB.original?.timer ?? 0),
|
||||||
cell: ({ row }) => <Timer epoch={row.original?.timer} />
|
cell: ({ row }) => <Timer epoch={row.original?.timer} />
|
||||||
@@ -101,9 +109,10 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.playerList.displayName')
|
label: () => t('table.playerList.displayName')
|
||||||
}),
|
}),
|
||||||
size: 200,
|
size: 200,
|
||||||
|
meta: { label: () => t('table.playerList.displayName') },
|
||||||
sortingFn: (rowA, rowB) =>
|
sortingFn: (rowA, rowB) =>
|
||||||
sortAlphabetically(rowA.original, rowB.original, 'displayName'),
|
sortAlphabetically(rowA.original, rowB.original, 'displayName'),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
@@ -118,8 +127,9 @@ export const createColumns = ({
|
|||||||
id: 'rank',
|
id: 'rank',
|
||||||
accessorFn: (row) => row?.ref?.$trustSortNum,
|
accessorFn: (row) => row?.ref?.$trustSortNum,
|
||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({ column, label: t('table.playerList.rank') }),
|
sortButton({ column, label: () => t('table.playerList.rank') }),
|
||||||
size: 110,
|
size: 110,
|
||||||
|
meta: { label: () => t('table.playerList.rank') },
|
||||||
sortingFn: (rowA, rowB) =>
|
sortingFn: (rowA, rowB) =>
|
||||||
(rowA.original?.ref?.$trustSortNum ?? 0) -
|
(rowA.original?.ref?.$trustSortNum ?? 0) -
|
||||||
(rowB.original?.ref?.$trustSortNum ?? 0),
|
(rowB.original?.ref?.$trustSortNum ?? 0),
|
||||||
@@ -143,7 +153,8 @@ export const createColumns = ({
|
|||||||
size: 200,
|
size: 200,
|
||||||
minSize: 100,
|
minSize: 100,
|
||||||
meta: {
|
meta: {
|
||||||
stretch: true
|
stretch: true,
|
||||||
|
label: () => t('table.playerList.status')
|
||||||
},
|
},
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
@@ -170,9 +181,13 @@ export const createColumns = ({
|
|||||||
id: 'photonId',
|
id: 'photonId',
|
||||||
accessorFn: (row) => row?.photonId,
|
accessorFn: (row) => row?.photonId,
|
||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({ column, label: t('table.playerList.photonId') }),
|
sortButton({
|
||||||
|
column,
|
||||||
|
label: () => t('table.playerList.photonId')
|
||||||
|
}),
|
||||||
size: 110,
|
size: 110,
|
||||||
enableHiding: true,
|
enableHiding: true,
|
||||||
|
meta: { label: () => t('table.playerList.photonId') },
|
||||||
sortingFn: (rowA, rowB) =>
|
sortingFn: (rowA, rowB) =>
|
||||||
(rowA.original?.photonId ?? 0) - (rowB.original?.photonId ?? 0),
|
(rowA.original?.photonId ?? 0) - (rowB.original?.photonId ?? 0),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
@@ -212,13 +227,14 @@ export const createColumns = ({
|
|||||||
header: ({ column }) =>
|
header: ({ column }) =>
|
||||||
sortButton({
|
sortButton({
|
||||||
column,
|
column,
|
||||||
label: t('table.playerList.icon'),
|
label: () => t('table.playerList.icon'),
|
||||||
descFirst: true
|
descFirst: true
|
||||||
}),
|
}),
|
||||||
size: 90,
|
size: 90,
|
||||||
accessorFn: (row) => getInstanceIconWeight(row),
|
accessorFn: (row) => getInstanceIconWeight(row),
|
||||||
meta: {
|
meta: {
|
||||||
class: 'text-center'
|
class: 'text-center',
|
||||||
|
label: () => t('table.playerList.icon')
|
||||||
},
|
},
|
||||||
sortingFn: (rowA, rowB, columnId) => {
|
sortingFn: (rowA, rowB, columnId) => {
|
||||||
const a = rowA.original;
|
const a = rowA.original;
|
||||||
@@ -291,6 +307,7 @@ export const createColumns = ({
|
|||||||
header: () => t('table.playerList.platform'),
|
header: () => t('table.playerList.platform'),
|
||||||
size: 90,
|
size: 90,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
meta: { label: () => t('table.playerList.platform') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const userRef = row.original?.ref;
|
const userRef = row.original?.ref;
|
||||||
const platform = userRef?.$platform;
|
const platform = userRef?.$platform;
|
||||||
@@ -330,6 +347,7 @@ export const createColumns = ({
|
|||||||
header: () => t('table.playerList.language'),
|
header: () => t('table.playerList.language'),
|
||||||
size: 100,
|
size: 100,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
meta: { label: () => t('table.playerList.language') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const userRef = row.original?.ref;
|
const userRef = row.original?.ref;
|
||||||
const langs = userRef?.$languages ?? [];
|
const langs = userRef?.$languages ?? [];
|
||||||
@@ -366,6 +384,7 @@ export const createColumns = ({
|
|||||||
header: () => t('table.playerList.bioLink'),
|
header: () => t('table.playerList.bioLink'),
|
||||||
size: 100,
|
size: 100,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
|
meta: { label: () => t('table.playerList.bioLink') },
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const links =
|
const links =
|
||||||
row.original?.ref?.bioLinks?.filter(Boolean) ?? [];
|
row.original?.ref?.bioLinks?.filter(Boolean) ?? [];
|
||||||
@@ -402,7 +421,8 @@ export const createColumns = ({
|
|||||||
size: 150,
|
size: 150,
|
||||||
minSize: 20,
|
minSize: 20,
|
||||||
meta: {
|
meta: {
|
||||||
stretch: true
|
stretch: true,
|
||||||
|
label: () => t('table.playerList.note')
|
||||||
},
|
},
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user