mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-22 16:23:50 +02:00
495 lines
11 KiB
JavaScript
495 lines
11 KiB
JavaScript
import {
|
|
getCoreRowModel,
|
|
getExpandedRowModel,
|
|
getFilteredRowModel,
|
|
getPaginationRowModel,
|
|
getSortedRowModel,
|
|
isFunction,
|
|
useVueTable
|
|
} from '@tanstack/vue-table';
|
|
import { computed, ref, unref, watch } from 'vue';
|
|
|
|
/**
|
|
*
|
|
* @param str
|
|
*/
|
|
function safeJsonParse(str) {
|
|
if (!str) {
|
|
return null;
|
|
}
|
|
try {
|
|
return JSON.parse(str);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param fn
|
|
* @param wait
|
|
*/
|
|
function debounce(fn, wait) {
|
|
let t = 0;
|
|
return (...args) => {
|
|
if (t) {
|
|
clearTimeout(t);
|
|
}
|
|
t = setTimeout(() => fn(...args), wait);
|
|
};
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param sizing
|
|
* @param columns
|
|
*/
|
|
function filterSizingByColumns(sizing, columns) {
|
|
if (!sizing || typeof sizing !== 'object') {
|
|
return {};
|
|
}
|
|
const ids = new Set((columns ?? []).map((c) => c?.id).filter(Boolean));
|
|
const out = {};
|
|
for (const [key, value] of Object.entries(sizing)) {
|
|
if (ids.has(key)) {
|
|
out[key] = value;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param sorting
|
|
* @param columns
|
|
*/
|
|
function filterSortingByColumns(sorting, columns) {
|
|
if (!Array.isArray(sorting)) {
|
|
return [];
|
|
}
|
|
const ids = new Set((columns ?? []).map((c) => c?.id).filter(Boolean));
|
|
return sorting.filter((s) => s && ids.has(s.id));
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param order
|
|
* @param columns
|
|
*/
|
|
function filterOrderByColumns(order, columns) {
|
|
if (!Array.isArray(order)) {
|
|
return [];
|
|
}
|
|
const ids = new Set((columns ?? []).map((c) => c?.id).filter(Boolean));
|
|
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
|
|
*/
|
|
function getColumnId(col) {
|
|
return col?.id ?? col?.accessorKey ?? null;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param columns
|
|
*/
|
|
function findStretchColumnId(columns) {
|
|
if (!Array.isArray(columns)) {
|
|
return null;
|
|
}
|
|
for (const col of columns) {
|
|
if (col?.meta?.['stretch']) {
|
|
return getColumnId(col);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param updaterOrValue
|
|
* @param targetRef
|
|
*/
|
|
function setRef(updaterOrValue, targetRef) {
|
|
targetRef.value = isFunction(updaterOrValue)
|
|
? updaterOrValue(targetRef.value)
|
|
: updaterOrValue;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param func
|
|
*/
|
|
function resolveMaybeGetter(func) {
|
|
return typeof func === 'function' ? func() : unref(func);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param columns
|
|
* @param enabled
|
|
* @param spacerId
|
|
* @param stretchAfterId
|
|
*/
|
|
function withSpacerColumn(columns, enabled, spacerId, stretchAfterId) {
|
|
if (!enabled) {
|
|
return columns;
|
|
}
|
|
if (!Array.isArray(columns)) {
|
|
return columns;
|
|
}
|
|
|
|
const id = spacerId ?? '__spacer';
|
|
|
|
if (columns.some((c) => getColumnId(c) === id)) {
|
|
return columns;
|
|
}
|
|
|
|
const spacerColumn = {
|
|
id,
|
|
header: () => null,
|
|
cell: () => null,
|
|
enableSorting: false,
|
|
size: 0,
|
|
minSize: 0,
|
|
meta: { thClass: 'p-0', tdClass: 'p-0' }
|
|
};
|
|
|
|
if (stretchAfterId) {
|
|
const idx = columns.findIndex((c) => getColumnId(c) === stretchAfterId);
|
|
if (idx !== -1) {
|
|
return [
|
|
...columns.slice(0, idx + 1),
|
|
spacerColumn,
|
|
...columns.slice(idx + 1)
|
|
];
|
|
}
|
|
}
|
|
|
|
return [...columns, spacerColumn];
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param options
|
|
*/
|
|
export function useVrcxVueTable(options) {
|
|
const {
|
|
getRowId,
|
|
getRowCanExpand,
|
|
|
|
enablePagination = true,
|
|
initialPagination,
|
|
enableSorting = true,
|
|
initialSorting,
|
|
enableFiltering = true,
|
|
|
|
enableExpanded = false,
|
|
initialExpanded,
|
|
|
|
enablePinning = false,
|
|
initialColumnPinning,
|
|
|
|
enableColumnResizing = true,
|
|
columnResizeMode = 'onChange',
|
|
initialColumnSizing,
|
|
|
|
enableColumnReorder = true,
|
|
initialColumnOrder,
|
|
|
|
enableColumnVisibility = true,
|
|
initialColumnVisibility,
|
|
|
|
fillRemainingSpace = true,
|
|
spacerColumnId = '__spacer',
|
|
|
|
persistKey,
|
|
persistColumnSizing = true,
|
|
persistSorting = true,
|
|
persistColumnOrder = true,
|
|
persistColumnVisibility = true,
|
|
persistDebounceMs = 200,
|
|
|
|
tableOptions = {}
|
|
} = options ?? {};
|
|
|
|
const hasData = options && 'data' in options;
|
|
const hasColumns = options && 'columns' in options;
|
|
if (!hasData) console.warn('useVrcxVueTable: `data` is required');
|
|
if (!hasColumns) console.warn('useVrcxVueTable: `columns` is required');
|
|
|
|
const expanded = ref(initialExpanded ?? {});
|
|
const pagination = ref(initialPagination ?? { pageIndex: 0, pageSize: 50 });
|
|
const columnPinning = ref(initialColumnPinning ?? { left: [], right: [] });
|
|
const columnSizing = ref(initialColumnSizing ?? {});
|
|
const columnOrder = ref(initialColumnOrder ?? []);
|
|
const columnVisibility = ref(initialColumnVisibility ?? {});
|
|
|
|
const storageKey = persistKey ? `vrcx:table:${persistKey}` : null;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function readPersisted() {
|
|
if (!storageKey) {
|
|
return null;
|
|
}
|
|
return safeJsonParse(localStorage.getItem(storageKey));
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param patch
|
|
*/
|
|
function writePersisted(patch) {
|
|
if (!storageKey) {
|
|
return;
|
|
}
|
|
const cur = safeJsonParse(localStorage.getItem(storageKey)) ?? {};
|
|
const next = { ...cur, ...patch, updatedAt: Date.now() };
|
|
localStorage.setItem(storageKey, JSON.stringify(next));
|
|
}
|
|
|
|
const persisted = readPersisted();
|
|
|
|
let resolvedSorting = initialSorting ?? [];
|
|
if (persisted && persistSorting && Array.isArray(persisted.sorting)) {
|
|
resolvedSorting = persisted.sorting;
|
|
}
|
|
const sorting = ref(resolvedSorting);
|
|
|
|
if (persisted && persistColumnSizing && persisted.columnSizing) {
|
|
columnSizing.value = persisted.columnSizing;
|
|
}
|
|
|
|
if (
|
|
persisted &&
|
|
persistColumnOrder &&
|
|
Array.isArray(persisted.columnOrder)
|
|
) {
|
|
columnOrder.value = persisted.columnOrder;
|
|
}
|
|
|
|
if (persisted && persistColumnVisibility && persisted.columnVisibility) {
|
|
columnVisibility.value = persisted.columnVisibility;
|
|
}
|
|
|
|
const state = {};
|
|
const handlers = {};
|
|
const rowModels = {};
|
|
const extra = {};
|
|
|
|
/**
|
|
*
|
|
* @param enabled
|
|
* @param key
|
|
* @param r
|
|
* @param onChangeKey
|
|
* @param rowModelPart
|
|
* @param extraPart
|
|
*/
|
|
function register(enabled, key, r, onChangeKey, rowModelPart, extraPart) {
|
|
if (!enabled) {
|
|
return;
|
|
}
|
|
|
|
Object.defineProperty(state, key, {
|
|
enumerable: true,
|
|
get: () => r.value
|
|
});
|
|
|
|
if (onChangeKey) handlers[onChangeKey] = (val) => setRef(val, r);
|
|
if (rowModelPart) Object.assign(rowModels, rowModelPart);
|
|
if (extraPart) Object.assign(extra, extraPart);
|
|
}
|
|
|
|
register(enableSorting, 'sorting', sorting, 'onSortingChange', {
|
|
getSortedRowModel: getSortedRowModel()
|
|
});
|
|
|
|
register(enablePagination, 'pagination', pagination, 'onPaginationChange', {
|
|
getPaginationRowModel: getPaginationRowModel()
|
|
});
|
|
|
|
register(
|
|
enableExpanded,
|
|
'expanded',
|
|
expanded,
|
|
'onExpandedChange',
|
|
{ getExpandedRowModel: getExpandedRowModel() },
|
|
{ getRowCanExpand }
|
|
);
|
|
|
|
register(
|
|
enablePinning,
|
|
'columnPinning',
|
|
columnPinning,
|
|
'onColumnPinningChange'
|
|
);
|
|
|
|
register(
|
|
enableColumnResizing,
|
|
'columnSizing',
|
|
columnSizing,
|
|
'onColumnSizingChange',
|
|
null,
|
|
{ enableColumnResizing: true, columnResizeMode }
|
|
);
|
|
|
|
register(
|
|
enableColumnReorder,
|
|
'columnOrder',
|
|
columnOrder,
|
|
'onColumnOrderChange'
|
|
);
|
|
|
|
register(
|
|
enableColumnVisibility,
|
|
'columnVisibility',
|
|
columnVisibility,
|
|
'onColumnVisibilityChange'
|
|
);
|
|
|
|
if (enableFiltering) {
|
|
Object.assign(rowModels, {
|
|
getFilteredRowModel: getFilteredRowModel()
|
|
});
|
|
}
|
|
|
|
const dataSource = computed(() => options.data);
|
|
const columnsSource = computed(() => resolveMaybeGetter(options.columns));
|
|
|
|
const table = useVueTable({
|
|
data: dataSource,
|
|
get columns() {
|
|
const cols = columnsSource.value;
|
|
|
|
const stretchAfterId = findStretchColumnId(cols);
|
|
|
|
return withSpacerColumn(
|
|
cols,
|
|
fillRemainingSpace,
|
|
spacerColumnId,
|
|
stretchAfterId
|
|
);
|
|
},
|
|
getRowId,
|
|
|
|
getCoreRowModel: getCoreRowModel(),
|
|
...rowModels,
|
|
...handlers,
|
|
...extra,
|
|
|
|
state,
|
|
|
|
...tableOptions
|
|
});
|
|
|
|
watch(
|
|
columnsSource,
|
|
(next) => {
|
|
table.setOptions((prev) => ({
|
|
...prev,
|
|
columns: withSpacerColumn(
|
|
next,
|
|
fillRemainingSpace,
|
|
spacerColumnId,
|
|
findStretchColumnId(next)
|
|
)
|
|
}));
|
|
table.setState((prev) => ({ ...prev }));
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
const persistWrite = debounce(
|
|
(payload) => writePersisted(payload),
|
|
persistDebounceMs
|
|
);
|
|
|
|
if (storageKey && persistColumnSizing) {
|
|
watch(
|
|
columnSizing,
|
|
(val) => {
|
|
const cols = table.getAllLeafColumns?.() ?? [];
|
|
persistWrite({
|
|
columnSizing: filterSizingByColumns(val, cols)
|
|
});
|
|
},
|
|
{ deep: true }
|
|
);
|
|
}
|
|
|
|
if (storageKey && persistSorting) {
|
|
watch(
|
|
sorting,
|
|
(val) => {
|
|
const cols = table.getAllLeafColumns?.() ?? [];
|
|
persistWrite({
|
|
sorting: filterSortingByColumns(val, cols)
|
|
});
|
|
},
|
|
{ deep: true }
|
|
);
|
|
}
|
|
|
|
if (storageKey && persistColumnOrder) {
|
|
watch(
|
|
columnOrder,
|
|
(val) => {
|
|
const cols = table.getAllLeafColumns?.() ?? [];
|
|
persistWrite({
|
|
columnOrder: filterOrderByColumns(val, cols)
|
|
});
|
|
},
|
|
{ deep: true }
|
|
);
|
|
}
|
|
|
|
if (storageKey && persistColumnVisibility) {
|
|
watch(
|
|
columnVisibility,
|
|
(val) => {
|
|
const cols = table.getAllLeafColumns?.() ?? [];
|
|
persistWrite({
|
|
columnVisibility: filterVisibilityByColumns(val, cols)
|
|
});
|
|
},
|
|
{ deep: true }
|
|
);
|
|
}
|
|
|
|
return {
|
|
table,
|
|
sorting,
|
|
pagination,
|
|
expanded,
|
|
columnPinning,
|
|
columnSizing,
|
|
columnOrder,
|
|
columnVisibility
|
|
};
|
|
}
|