feat(viewPref): keep record of user view preferences per section (groupBy, order, list or grid)

This commit is contained in:
Edouard Vanbelle
2026-05-28 11:12:36 +02:00
parent 7f4b329718
commit bd474aa67e
7 changed files with 204 additions and 7 deletions
+55
View File
@@ -0,0 +1,55 @@
// @ts-check
/**
* groupBySync — apply groupBy + sort-direction state to the group-by menu UI.
*
* Pure DOM helper with no module imports so it can be safely imported by any
* view without creating circular dependencies.
*
* Call after `syncGroupByMenu()` has built the option list, e.g. when
* restoring saved preferences on section entry.
*
* Usage:
* import { applyGroupByMenuState } from '../core/groupBySync.js';
* applyGroupByMenuState('type', true);
*/
/**
* Reflect `groupBy` and `reversed` in the group-by menu DOM:
* - marks the matching `.group-by-option` as active
* - updates the group-by button label and active class
* - toggles the sort-direction button active class
*
* No-op when the menu elements are not in the DOM (e.g. before initApp).
*
* @param {string} groupBy Active group-by key, or `''` for "None".
* @param {boolean} reversed Whether sort direction is reversed.
*/
function applyGroupByMenuState(groupBy, reversed) {
// Mark the matching option as active; clear all others.
for (const b of document.querySelectorAll('.group-by-option')) {
const btn = /** @type {HTMLElement} */ (b);
btn.classList.toggle('active', (btn.dataset.groupBy ?? '') === groupBy);
}
// Group-by button: active class + label text.
const groupByBtn = document.getElementById('group-by-btn');
groupByBtn?.classList.toggle('active', groupBy !== '');
const lbl = groupByBtn?.querySelector('.group-by-label');
if (lbl) {
if (groupBy === '') {
lbl.textContent = '';
} else {
const activeOpt = /** @type {HTMLElement|null} */ (
document.querySelector(`.group-by-option[data-group-by="${CSS.escape(groupBy)}"]`)
);
lbl.textContent = activeOpt?.textContent ?? '';
}
}
// Sort-direction button: active = reversed.
document.getElementById('sort-dir-btn')?.classList.toggle('active', reversed);
}
export { applyGroupByMenuState };
+100
View File
@@ -0,0 +1,100 @@
// @ts-check
/**
* View preferences — persist groupBy, sort-direction, and grid/list view
* per section in localStorage.
*
* Each section has its own key (`oxicloud.view.<section>`) so that, for
* example, Favorites can be in list view with "Type" grouping while Files
* is in grid view with no grouping.
*
* Section keys match `app.currentSection` values:
* 'files' | 'favorites' | 'sharedwithme' | 'recent' | 'trash' |
* 'photos' | 'music' | 'shared'
*
* All errors (quota, private browsing, JSON parse) are silently swallowed.
*
* Usage:
* import * as viewPrefs from '../core/viewPrefs.js';
*
* // Read
* const { groupBy, reversed, view } = viewPrefs.load('files');
*
* // Write all fields at once
* viewPrefs.save('files', 'type', true, 'list');
*
* // Write only the view (grid/list) toggle, keeping stored groupBy/reversed
* viewPrefs.saveView('favorites', 'grid');
*
* // Resolve which view (grid/list) to apply on section entry
* const v = viewPrefs.resolveView('sharedwithme'); // 'grid' | 'list'
*/
const _PREFIX = 'oxicloud.view.';
/**
* @typedef {'grid'|'list'|''} ViewMode
* @typedef {{ groupBy: string, reversed: boolean, view: ViewMode }} ViewPrefs
*/
/**
* Load saved preferences for a section.
* Returns safe defaults when nothing is stored or storage is unavailable.
* @param {string} section
* @returns {ViewPrefs}
*/
function load(section) {
try {
const raw = localStorage.getItem(_PREFIX + section);
if (!raw) return { groupBy: '', reversed: false, view: '' };
const p = JSON.parse(raw);
return {
groupBy: typeof p.groupBy === 'string' ? p.groupBy : '',
reversed: Boolean(p.reversed),
view: p.view === 'grid' || p.view === 'list' ? p.view : ''
};
} catch {
return { groupBy: '', reversed: false, view: '' };
}
}
/**
* Persist all preferences for a section.
* @param {string} section
* @param {string} groupBy
* @param {boolean} reversed
* @param {ViewMode} view
*/
function save(section, groupBy, reversed, view) {
try {
localStorage.setItem(_PREFIX + section, JSON.stringify({ groupBy, reversed, view }));
} catch {
// Silently ignore quota errors or restricted environments.
}
}
/**
* Update only the grid/list view for a section, preserving groupBy and reversed.
* @param {string} section
* @param {ViewMode} view
*/
function saveView(section, view) {
const current = load(section);
save(section, current.groupBy, current.reversed, view);
}
/**
* Resolve the view mode to apply when entering a section.
* Priority: section-specific pref → legacy global `oxicloud-view` key → `'grid'`.
* @param {string} section
* @returns {'grid'|'list'}
*/
function resolveView(section) {
const prefs = load(section);
if (prefs.view) return prefs.view;
// Fall back to the pre-existing global key (backward compatibility).
const global = localStorage.getItem('oxicloud-view');
return global === 'list' ? 'list' : 'grid';
}
export { load, resolveView, save, saveView };