fix(dict): default-hide для existing users (v=2 storage) + sticky header/pagination
This commit is contained in:
@@ -24,45 +24,72 @@ export interface DictTableState {
|
||||
hidden: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema version stored в localStorage. Bumpит когда меняется default
|
||||
* visibility logic — старые записи с другим (или missing) _v читаются как
|
||||
* unmigrated, и для них применяется текущий fallback. После первого
|
||||
* write entry обновляется с current version → больше fallback не сработает.
|
||||
*
|
||||
* <p>v1 (MR !282): юзер сам прятал колонки, default hidden=[].
|
||||
* <p>v2 (MR after !287): info-dense schemas (10+ scalar полей) hide tail
|
||||
* by default. Юзеры из v1 имеют `{hidden:[]}` в storage — без version
|
||||
* bump мы бы их default-показ оставили (т.е. fix не сработал бы для них).
|
||||
*/
|
||||
const STATE_VERSION = 2
|
||||
|
||||
interface StoredState extends DictTableState {
|
||||
_v?: number
|
||||
}
|
||||
|
||||
const DEFAULT_STATE: DictTableState = { sort: null, hidden: [] }
|
||||
|
||||
const storageKey = (dictName: string): string => `ord-dict-table:${dictName}`
|
||||
|
||||
const parseSort = (raw: Partial<DictTableState> | null | undefined): SortState => {
|
||||
const s = raw?.sort
|
||||
return s &&
|
||||
typeof s.col === 'string' &&
|
||||
(s.dir === 'asc' || s.dir === 'desc')
|
||||
? { col: s.col, dir: s.dir }
|
||||
: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Прочитать persisted state. Если в storage пусто (юзер ни разу не
|
||||
* взаимодействовал с этим dict) → используем `fallbackHidden` как initial
|
||||
* visibility (для info-dense schemas: hide tail columns по умолчанию,
|
||||
* чтобы таблица помещалась в viewport без H-scroll). Любой toggle/setHidden
|
||||
* запишет state в storage → дальнейшие посещения respect юзерский выбор
|
||||
* вне зависимости от fallback.
|
||||
* Прочитать persisted state. Если в storage пусто, или запись unversioned/
|
||||
* outdated → возвращаем `fallbackHidden` (для info-dense schemas: hide tail
|
||||
* columns по умолчанию). Sort всё равно preserve'ем — он юзерский выбор,
|
||||
* version bump его не должен трогать.
|
||||
*
|
||||
* <p>После любого toggle/setHidden writeState запишет state с current _v →
|
||||
* дальнейшие посещения respect юзерский выбор вне зависимости от fallback.
|
||||
*/
|
||||
const readState = (dictName: string, fallbackHidden: string[]): DictTableState => {
|
||||
const fallback: DictTableState = { sort: null, hidden: fallbackHidden }
|
||||
if (typeof window === 'undefined') return fallback
|
||||
if (typeof window === 'undefined') return { sort: null, hidden: fallbackHidden }
|
||||
try {
|
||||
const raw = window.localStorage.getItem(storageKey(dictName))
|
||||
if (!raw) return fallback
|
||||
const parsed = JSON.parse(raw) as Partial<DictTableState>
|
||||
if (!raw) return { sort: null, hidden: fallbackHidden }
|
||||
const parsed = JSON.parse(raw) as StoredState
|
||||
// Unversioned (v1 → юзер из MR !282) или mismatched _v → reapply
|
||||
// fallback. Sort preserve.
|
||||
if (parsed._v !== STATE_VERSION) {
|
||||
return { sort: parseSort(parsed), hidden: fallbackHidden }
|
||||
}
|
||||
return {
|
||||
sort:
|
||||
parsed.sort &&
|
||||
typeof parsed.sort.col === 'string' &&
|
||||
(parsed.sort.dir === 'asc' || parsed.sort.dir === 'desc')
|
||||
? { col: parsed.sort.col, dir: parsed.sort.dir }
|
||||
: null,
|
||||
sort: parseSort(parsed),
|
||||
hidden: Array.isArray(parsed.hidden)
|
||||
? parsed.hidden.filter((s): s is string => typeof s === 'string')
|
||||
: [],
|
||||
}
|
||||
} catch {
|
||||
return fallback
|
||||
return { sort: null, hidden: fallbackHidden }
|
||||
}
|
||||
}
|
||||
|
||||
const writeState = (dictName: string, state: DictTableState): void => {
|
||||
if (typeof window === 'undefined') return
|
||||
try {
|
||||
window.localStorage.setItem(storageKey(dictName), JSON.stringify(state))
|
||||
const stored: StoredState = { ...state, _v: STATE_VERSION }
|
||||
window.localStorage.setItem(storageKey(dictName), JSON.stringify(stored))
|
||||
} catch {
|
||||
/* QuotaExceeded / private mode — silently ignore, state остаётся
|
||||
только в memory. */
|
||||
|
||||
Reference in New Issue
Block a user