diff --git a/src/lib/table/useVrcxVueTable.js b/src/lib/table/useVrcxVueTable.js index 2e92e7bd..64330976 100644 --- a/src/lib/table/useVrcxVueTable.js +++ b/src/lib/table/useVrcxVueTable.js @@ -7,7 +7,42 @@ import { isFunction, useVueTable } from '@tanstack/vue-table'; -import { ref, unref } from 'vue'; +import { ref, unref, watch } from 'vue'; + +function safeJsonParse(str) { + if (!str) { + return null; + } + try { + return JSON.parse(str); + } catch { + return null; + } +} + +function debounce(fn, wait) { + let t = 0; + return (...args) => { + if (t) { + clearTimeout(t); + } + t = setTimeout(() => fn(...args), wait); + }; +} + +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; +} function getColumnId(col) { return col?.id ?? col?.accessorKey ?? null; @@ -98,6 +133,10 @@ export function useVrcxVueTable(options) { fillRemainingSpace = true, spacerColumnId = '__spacer', + persistKey, + persistColumnSizing = true, + persistDebounceMs = 200, + tableOptions = {} } = options ?? {}; @@ -112,6 +151,29 @@ export function useVrcxVueTable(options) { const columnPinning = ref(initialColumnPinning ?? { left: [], right: [] }); const columnSizing = ref(initialColumnSizing ?? {}); + const storageKey = persistKey ? `vrcx:table:${persistKey}` : null; + + function readPersisted() { + if (!storageKey) { + return null; + } + return safeJsonParse(localStorage.getItem(storageKey)); + } + + 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(); + if (persisted && persistColumnSizing && persisted.columnSizing) { + columnSizing.value = persisted.columnSizing; + } + const state = {}; const handlers = {}; const rowModels = {}; @@ -199,6 +261,24 @@ export function useVrcxVueTable(options) { ...tableOptions }); + const persistWrite = debounce( + (payload) => writePersisted(payload), + persistDebounceMs + ); + + if (storageKey && persistColumnSizing) { + watch( + columnSizing, + (val) => { + const cols = table.getAllLeafColumns?.() ?? []; + persistWrite({ + columnSizing: filterSizingByColumns(val, cols) + }); + }, + { deep: true } + ); + } + return { table, sorting, diff --git a/src/views/Feed/Feed.vue b/src/views/Feed/Feed.vue index bbc8e796..6594809b 100644 --- a/src/views/Feed/Feed.vue +++ b/src/views/Feed/Feed.vue @@ -76,6 +76,7 @@ ); const { table, pagination } = useVrcxVueTable({ + persistKey: 'feed', data: feedDisplayData, columns: baseColumns, getRowId: (row) => `${row.type}:${row.rowId}:${row.created_at ?? ''}`, diff --git a/src/views/FriendLog/FriendLog.vue b/src/views/FriendLog/FriendLog.vue index 0e9aa753..56b7ef1b 100644 --- a/src/views/FriendLog/FriendLog.vue +++ b/src/views/FriendLog/FriendLog.vue @@ -157,6 +157,7 @@ ); const { table, pagination } = useVrcxVueTable({ + persistKey: 'friendLog', data: friendLogDisplayData, columns, getRowId: (row) => `${row.type}:${row.rowId ?? row.userId ?? row.created_at ?? ''}`, diff --git a/src/views/GameLog/GameLog.vue b/src/views/GameLog/GameLog.vue index 2a063b3f..b6121285 100644 --- a/src/views/GameLog/GameLog.vue +++ b/src/views/GameLog/GameLog.vue @@ -156,6 +156,7 @@ ); const { table, pagination } = useVrcxVueTable({ + persistKey: 'gameLog', data: gameLogDisplayData, columns, getRowId: (row) => `${row.type}:${row.rowId ?? row.displayName + row.location + row.time}`, diff --git a/src/views/Moderation/Moderation.vue b/src/views/Moderation/Moderation.vue index 52d8e5cf..210f8f26 100644 --- a/src/views/Moderation/Moderation.vue +++ b/src/views/Moderation/Moderation.vue @@ -145,6 +145,7 @@ ); const { table, pagination } = useVrcxVueTable({ + persistKey: 'moderation', data: moderationDisplayData, columns, getRowId: (row) => row.id ?? `${row.type}:${row.sourceUserId}:${row.targetUserId}:${row.created ?? ''}`, diff --git a/src/views/Notifications/Notification.vue b/src/views/Notifications/Notification.vue index f25547d4..4e937fb8 100644 --- a/src/views/Notifications/Notification.vue +++ b/src/views/Notifications/Notification.vue @@ -213,6 +213,7 @@ ); const { table, pagination } = useVrcxVueTable({ + persistKey: 'notifications', data: notificationDisplayData, columns, getRowId: (row) => row.id ?? `${row.type}:${row.senderUserId ?? ''}:${row.created_at ?? ''}`, diff --git a/src/views/PlayerList/PlayerList.vue b/src/views/PlayerList/PlayerList.vue index e7ded06d..6e6d5706 100644 --- a/src/views/PlayerList/PlayerList.vue +++ b/src/views/PlayerList/PlayerList.vue @@ -278,6 +278,7 @@ const playerListDisplayData = computed(() => currentInstanceUsersData.value ?? []); const { table: playerListTable } = useVrcxVueTable({ + persistKey: 'playerList', data: playerListDisplayData, columns: playerListColumns.value, getRowId: (row) => `${row?.ref?.id ?? ''}:${row?.displayName ?? ''}:${row?.photonId ?? ''}`,