feat(resourceList): keep consistence in groupby options order accross sectons + add userVignette on owner's swimlane
This commit is contained in:
@@ -574,6 +574,20 @@
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* When the header contains a rich DOM node (e.g. a user vignette for the
|
||||||
|
"owner" group-by), reset the typographic overrides that only make sense
|
||||||
|
for plain-text labels, and lay the node out inline. */
|
||||||
|
.resource-list__swimlane-header--node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 12px;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: normal;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Swimlane group card (list view only) ────────────────── */
|
/* ── Swimlane group card (list view only) ────────────────── */
|
||||||
|
|
||||||
/* When swimlane groups are present, dissolve the outer container into the
|
/* When swimlane groups are present, dissolve the outer container into the
|
||||||
|
|||||||
+13
-13
@@ -76,6 +76,19 @@ const GROUP_BY_DEFS = [
|
|||||||
return labels[key] ?? key;
|
return labels[key] ?? key;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'size',
|
||||||
|
get label() {
|
||||||
|
return i18n.t('groupby.size', 'Size');
|
||||||
|
},
|
||||||
|
orderBy: 'size',
|
||||||
|
// sizeBucket(-1) → "Folders" sentinel; no labelFn needed.
|
||||||
|
keyFn: (item) => {
|
||||||
|
if (!('mime_type' in item)) return sizeBucket(-1);
|
||||||
|
const r = /** @type {Record<string, number>} */ (/** @type {unknown} */ (item));
|
||||||
|
return sizeBucket(r.size ?? 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'modifiedAt',
|
key: 'modifiedAt',
|
||||||
get label() {
|
get label() {
|
||||||
@@ -98,19 +111,6 @@ const GROUP_BY_DEFS = [
|
|||||||
const r = /** @type {Record<string, number>} */ (/** @type {unknown} */ (item));
|
const r = /** @type {Record<string, number>} */ (/** @type {unknown} */ (item));
|
||||||
return r.created_at ? normalizeDateBucket(r.created_at) : null;
|
return r.created_at ? normalizeDateBucket(r.created_at) : null;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'size',
|
|
||||||
get label() {
|
|
||||||
return i18n.t('groupby.size', 'Size');
|
|
||||||
},
|
|
||||||
orderBy: 'size',
|
|
||||||
// sizeBucket(-1) → "Folders" sentinel; no labelFn needed.
|
|
||||||
keyFn: (item) => {
|
|
||||||
if (!('mime_type' in item)) return sizeBucket(-1);
|
|
||||||
const r = /** @type {Record<string, number>} */ (/** @type {unknown} */ (item));
|
|
||||||
return sizeBucket(r.size ?? 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,14 @@ export class ResourceListComponent {
|
|||||||
*/
|
*/
|
||||||
this._groupLabelFn = undefined;
|
this._groupLabelFn = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional node-builder stored between `render()` and `append()` calls.
|
||||||
|
* When set, the swimlane header renders a DOM node instead of plain text
|
||||||
|
* (e.g. a user vignette for the "owner" group-by dimension).
|
||||||
|
* @type {((key: string) => HTMLElement) | undefined}
|
||||||
|
*/
|
||||||
|
this._headerNodeFn = undefined;
|
||||||
|
|
||||||
this._ownerVisible = this._cfg.showOwner;
|
this._ownerVisible = this._cfg.showOwner;
|
||||||
|
|
||||||
this._initDelegation();
|
this._initDelegation();
|
||||||
@@ -138,8 +146,12 @@ export class ResourceListComponent {
|
|||||||
* @param {((key: string) => string)=} groupLabelFn
|
* @param {((key: string) => string)=} groupLabelFn
|
||||||
* Optional: converts the raw grouping key to a human-readable header
|
* Optional: converts the raw grouping key to a human-readable header
|
||||||
* label. When omitted the key itself is used.
|
* label. When omitted the key itself is used.
|
||||||
|
* @param {((key: string) => HTMLElement)=} headerNodeFn
|
||||||
|
* Optional: builds a rich DOM node for the swimlane header (e.g. a user
|
||||||
|
* vignette for the "owner" group-by). When provided, `groupLabelFn` is
|
||||||
|
* ignored for the header and the returned node is appended instead.
|
||||||
*/
|
*/
|
||||||
render(items, groupFn, groupLabelFn) {
|
render(items, groupFn, groupLabelFn, headerNodeFn) {
|
||||||
const header = this._container.querySelector('.list-header');
|
const header = this._container.querySelector('.list-header');
|
||||||
this._container.innerHTML = '';
|
this._container.innerHTML = '';
|
||||||
if (header) this._container.appendChild(header);
|
if (header) this._container.appendChild(header);
|
||||||
@@ -151,11 +163,12 @@ export class ResourceListComponent {
|
|||||||
this._lastGroupKey = undefined;
|
this._lastGroupKey = undefined;
|
||||||
this._lastGroupEl = null;
|
this._lastGroupEl = null;
|
||||||
this._groupLabelFn = groupLabelFn;
|
this._groupLabelFn = groupLabelFn;
|
||||||
|
this._headerNodeFn = headerNodeFn;
|
||||||
|
|
||||||
// Prevent ui.js global delegation from firing on this container
|
// Prevent ui.js global delegation from firing on this container
|
||||||
this._container.dataset.managedBy = 'resource-list';
|
this._container.dataset.managedBy = 'resource-list';
|
||||||
|
|
||||||
this._appendItems(items, groupFn, groupLabelFn);
|
this._appendItems(items, groupFn, groupLabelFn, headerNodeFn);
|
||||||
this._wireSelectAll();
|
this._wireSelectAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,9 +180,10 @@ export class ResourceListComponent {
|
|||||||
* @param {Array<FileItem|FolderItem>} items
|
* @param {Array<FileItem|FolderItem>} items
|
||||||
* @param {((item: FileItem|FolderItem) => string|null)=} groupFn
|
* @param {((item: FileItem|FolderItem) => string|null)=} groupFn
|
||||||
* @param {((key: string) => string)=} groupLabelFn
|
* @param {((key: string) => string)=} groupLabelFn
|
||||||
|
* @param {((key: string) => HTMLElement)=} headerNodeFn
|
||||||
*/
|
*/
|
||||||
append(items, groupFn, groupLabelFn) {
|
append(items, groupFn, groupLabelFn, headerNodeFn) {
|
||||||
this._appendItems(items, groupFn, groupLabelFn ?? this._groupLabelFn);
|
this._appendItems(items, groupFn, groupLabelFn ?? this._groupLabelFn, headerNodeFn ?? this._headerNodeFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Remove all items (but keep `.list-header` if present). */
|
/** Remove all items (but keep `.list-header` if present). */
|
||||||
@@ -328,8 +342,9 @@ export class ResourceListComponent {
|
|||||||
* @param {Array<FileItem|FolderItem>} items
|
* @param {Array<FileItem|FolderItem>} items
|
||||||
* @param {((item: FileItem|FolderItem) => string|null)=} groupFn
|
* @param {((item: FileItem|FolderItem) => string|null)=} groupFn
|
||||||
* @param {((key: string) => string)=} groupLabelFn
|
* @param {((key: string) => string)=} groupLabelFn
|
||||||
|
* @param {((key: string) => HTMLElement)=} headerNodeFn
|
||||||
*/
|
*/
|
||||||
_appendItems(items, groupFn, groupLabelFn) {
|
_appendItems(items, groupFn, groupLabelFn, headerNodeFn) {
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
// Start from the persisted key so load-more pages continue seamlessly.
|
// Start from the persisted key so load-more pages continue seamlessly.
|
||||||
@@ -354,7 +369,7 @@ export class ResourceListComponent {
|
|||||||
if (key !== null) {
|
if (key !== null) {
|
||||||
fragmentGroup = document.createElement('div');
|
fragmentGroup = document.createElement('div');
|
||||||
fragmentGroup.className = 'resource-list__swimlane-group';
|
fragmentGroup.className = 'resource-list__swimlane-group';
|
||||||
fragmentGroup.appendChild(this._createGroupHeader(key, groupLabelFn));
|
fragmentGroup.appendChild(this._createGroupHeader(key, groupLabelFn, headerNodeFn));
|
||||||
fragment.appendChild(fragmentGroup);
|
fragment.appendChild(fragmentGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,14 +397,25 @@ export class ResourceListComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a swimlane divider element.
|
* Create a swimlane divider element.
|
||||||
* @param {string} key - Raw grouping key (e.g. UUID or bucket name).
|
*
|
||||||
* @param {((key: string) => string)=} labelFn - Optional human-readable resolver.
|
* When `headerNodeFn` is supplied the header renders a rich DOM node
|
||||||
|
* (e.g. a user vignette) instead of plain text; the `--node` CSS modifier
|
||||||
|
* is added to suppress the small-caps / uppercase text styles.
|
||||||
|
*
|
||||||
|
* @param {string} key - Raw grouping key (e.g. UUID or bucket name).
|
||||||
|
* @param {((key: string) => string)=} labelFn - Optional plain-text resolver.
|
||||||
|
* @param {((key: string) => HTMLElement)=} headerNodeFn - Optional rich-node builder.
|
||||||
*/
|
*/
|
||||||
_createGroupHeader(key, labelFn) {
|
_createGroupHeader(key, labelFn, headerNodeFn) {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.className = 'resource-list__swimlane-header';
|
el.className = 'resource-list__swimlane-header';
|
||||||
el.dataset.swimlaneHeader = 'true';
|
el.dataset.swimlaneHeader = 'true';
|
||||||
el.textContent = labelFn ? labelFn(key) : key;
|
if (headerNodeFn) {
|
||||||
|
el.classList.add('resource-list__swimlane-header--node');
|
||||||
|
el.appendChild(headerNodeFn(key));
|
||||||
|
} else {
|
||||||
|
el.textContent = labelFn ? labelFn(key) : key;
|
||||||
|
}
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import { ui } from '../../app/ui.js';
|
import { ui } from '../../app/ui.js';
|
||||||
import { ResourceListComponent } from '../../components/resourceList.js';
|
import { ResourceListComponent } from '../../components/resourceList.js';
|
||||||
|
import { createUserVignette } from '../../components/userVignette.js';
|
||||||
import { normalizeDateBucket, sizeBucket } from '../../core/formatters.js';
|
import { normalizeDateBucket, sizeBucket } from '../../core/formatters.js';
|
||||||
import { i18n } from '../../core/i18n.js';
|
import { i18n } from '../../core/i18n.js';
|
||||||
import * as viewPrefs from '../../core/viewPrefs.js';
|
import * as viewPrefs from '../../core/viewPrefs.js';
|
||||||
@@ -32,7 +33,8 @@ import { systemUsers } from '../../model/systemUsers.js';
|
|||||||
/**
|
/**
|
||||||
* @typedef {{ key: string, label: string, orderBy: string,
|
* @typedef {{ key: string, label: string, orderBy: string,
|
||||||
* keyFn: (item: FileItem|FolderItem) => string|null,
|
* keyFn: (item: FileItem|FolderItem) => string|null,
|
||||||
* labelFn?: (key: string) => string }} GroupByDef
|
* labelFn?: (key: string) => string,
|
||||||
|
* headerNodeFn?: (key: string) => HTMLElement }} GroupByDef
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,6 +44,20 @@ import { systemUsers } from '../../model/systemUsers.js';
|
|||||||
* @type {GroupByDef[]}
|
* @type {GroupByDef[]}
|
||||||
*/
|
*/
|
||||||
const GROUP_BY_DEFS = [
|
const GROUP_BY_DEFS = [
|
||||||
|
{
|
||||||
|
key: 'owner',
|
||||||
|
get label() {
|
||||||
|
return i18n.t('groupby.owner', 'Owner');
|
||||||
|
},
|
||||||
|
orderBy: 'owner',
|
||||||
|
// keyFn groups by UUID — stable, avoids collisions on identical display names.
|
||||||
|
keyFn: (item) => {
|
||||||
|
const r = /** @type {Record<string,string>} */ (/** @type {unknown} */ (item));
|
||||||
|
return r.owner_id || null;
|
||||||
|
},
|
||||||
|
labelFn: (id) => systemUsers.getDisplayNameSync(id),
|
||||||
|
headerNodeFn: (id) => createUserVignette(id, 'sm')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'type',
|
key: 'type',
|
||||||
get label() {
|
get label() {
|
||||||
@@ -71,6 +87,19 @@ const GROUP_BY_DEFS = [
|
|||||||
return labels[key] ?? key;
|
return labels[key] ?? key;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'size',
|
||||||
|
get label() {
|
||||||
|
return i18n.t('groupby.size', 'Size');
|
||||||
|
},
|
||||||
|
orderBy: 'size',
|
||||||
|
// Folders have no size — sizeBucket(-1) returns the "Folders" label.
|
||||||
|
keyFn: (item) => {
|
||||||
|
if (!('mime_type' in item)) return sizeBucket(-1);
|
||||||
|
const r = /** @type {Record<string,number>} */ (/** @type {unknown} */ (item));
|
||||||
|
return sizeBucket(r.size ?? 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'favoriteDate',
|
key: 'favoriteDate',
|
||||||
get label() {
|
get label() {
|
||||||
@@ -94,32 +123,6 @@ const GROUP_BY_DEFS = [
|
|||||||
const r = /** @type {Record<string,number>} */ (/** @type {unknown} */ (item));
|
const r = /** @type {Record<string,number>} */ (/** @type {unknown} */ (item));
|
||||||
return r.modified_at ? normalizeDateBucket(r.modified_at) : null;
|
return r.modified_at ? normalizeDateBucket(r.modified_at) : null;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'size',
|
|
||||||
get label() {
|
|
||||||
return i18n.t('groupby.size', 'Size');
|
|
||||||
},
|
|
||||||
orderBy: 'size',
|
|
||||||
// Folders have no size — sizeBucket(-1) returns the "Folders" label.
|
|
||||||
keyFn: (item) => {
|
|
||||||
if (!('mime_type' in item)) return sizeBucket(-1);
|
|
||||||
const r = /** @type {Record<string,number>} */ (/** @type {unknown} */ (item));
|
|
||||||
return sizeBucket(r.size ?? 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'owner',
|
|
||||||
get label() {
|
|
||||||
return i18n.t('groupby.owner', 'Owner');
|
|
||||||
},
|
|
||||||
orderBy: 'owner',
|
|
||||||
// keyFn groups by UUID — stable, avoids collisions on identical display names.
|
|
||||||
keyFn: (item) => {
|
|
||||||
const r = /** @type {Record<string,string>} */ (/** @type {unknown} */ (item));
|
|
||||||
return r.owner_id || null;
|
|
||||||
},
|
|
||||||
labelFn: (id) => systemUsers.getDisplayNameSync(id)
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -304,9 +307,9 @@ const favoritesView = {
|
|||||||
const items = this._mapItems(data.items);
|
const items = this._mapItems(data.items);
|
||||||
|
|
||||||
if (isFirstPage) {
|
if (isFirstPage) {
|
||||||
this._component?.render(items, def?.keyFn, def?.labelFn);
|
this._component?.render(items, def?.keyFn, def?.labelFn, def?.headerNodeFn);
|
||||||
} else {
|
} else {
|
||||||
this._component?.append(items, def?.keyFn, def?.labelFn);
|
this._component?.append(items, def?.keyFn, def?.labelFn, def?.headerNodeFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wire unified item tooltip (owner + path) after items are in the DOM
|
// Wire unified item tooltip (owner + path) after items are in the DOM
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import { ui } from '../../app/ui.js';
|
import { ui } from '../../app/ui.js';
|
||||||
import { ResourceListComponent } from '../../components/resourceList.js';
|
import { ResourceListComponent } from '../../components/resourceList.js';
|
||||||
|
import { createUserVignette } from '../../components/userVignette.js';
|
||||||
import { normalizeDateBucket, sizeBucket } from '../../core/formatters.js';
|
import { normalizeDateBucket, sizeBucket } from '../../core/formatters.js';
|
||||||
import { i18n } from '../../core/i18n.js';
|
import { i18n } from '../../core/i18n.js';
|
||||||
import * as viewPrefs from '../../core/viewPrefs.js';
|
import * as viewPrefs from '../../core/viewPrefs.js';
|
||||||
@@ -33,7 +34,8 @@ import { systemUsers } from '../../model/systemUsers.js';
|
|||||||
/**
|
/**
|
||||||
* @typedef {{ key: string, label: string, orderBy: string,
|
* @typedef {{ key: string, label: string, orderBy: string,
|
||||||
* keyFn: (item: FileItem|FolderItem) => string|null,
|
* keyFn: (item: FileItem|FolderItem) => string|null,
|
||||||
* labelFn?: (key: string) => string }} GroupByDef
|
* labelFn?: (key: string) => string,
|
||||||
|
* headerNodeFn?: (key: string) => HTMLElement }} GroupByDef
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,6 +54,19 @@ import { systemUsers } from '../../model/systemUsers.js';
|
|||||||
* @type {GroupByDef[]}
|
* @type {GroupByDef[]}
|
||||||
*/
|
*/
|
||||||
const GROUP_BY_DEFS = [
|
const GROUP_BY_DEFS = [
|
||||||
|
{
|
||||||
|
key: 'owner',
|
||||||
|
get label() {
|
||||||
|
return i18n.t('groupby.owner', 'Owner');
|
||||||
|
},
|
||||||
|
orderBy: 'owner',
|
||||||
|
keyFn: (item) => {
|
||||||
|
const r = /** @type {Record<string,string>} */ (/** @type {unknown} */ (item));
|
||||||
|
return r.owner_id || null;
|
||||||
|
},
|
||||||
|
labelFn: (id) => systemUsers.getDisplayNameSync(id),
|
||||||
|
headerNodeFn: (id) => createUserVignette(id, 'sm')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'type',
|
key: 'type',
|
||||||
get label() {
|
get label() {
|
||||||
@@ -80,6 +95,18 @@ const GROUP_BY_DEFS = [
|
|||||||
return labels[key] ?? key;
|
return labels[key] ?? key;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'size',
|
||||||
|
get label() {
|
||||||
|
return i18n.t('groupby.size', 'Size');
|
||||||
|
},
|
||||||
|
orderBy: 'size',
|
||||||
|
keyFn: (item) => {
|
||||||
|
if (!('mime_type' in item)) return sizeBucket(-1);
|
||||||
|
const r = /** @type {Record<string,number>} */ (/** @type {unknown} */ (item));
|
||||||
|
return sizeBucket(r.size ?? 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'accessedAt',
|
key: 'accessedAt',
|
||||||
get label() {
|
get label() {
|
||||||
@@ -102,30 +129,6 @@ const GROUP_BY_DEFS = [
|
|||||||
const r = /** @type {Record<string,number>} */ (/** @type {unknown} */ (item));
|
const r = /** @type {Record<string,number>} */ (/** @type {unknown} */ (item));
|
||||||
return r.modified_at ? normalizeDateBucket(r.modified_at) : null;
|
return r.modified_at ? normalizeDateBucket(r.modified_at) : null;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'size',
|
|
||||||
get label() {
|
|
||||||
return i18n.t('groupby.size', 'Size');
|
|
||||||
},
|
|
||||||
orderBy: 'size',
|
|
||||||
keyFn: (item) => {
|
|
||||||
if (!('mime_type' in item)) return sizeBucket(-1);
|
|
||||||
const r = /** @type {Record<string,number>} */ (/** @type {unknown} */ (item));
|
|
||||||
return sizeBucket(r.size ?? 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'owner',
|
|
||||||
get label() {
|
|
||||||
return i18n.t('groupby.owner', 'Owner');
|
|
||||||
},
|
|
||||||
orderBy: 'owner',
|
|
||||||
keyFn: (item) => {
|
|
||||||
const r = /** @type {Record<string,string>} */ (/** @type {unknown} */ (item));
|
|
||||||
return r.owner_id || null;
|
|
||||||
},
|
|
||||||
labelFn: (id) => systemUsers.getDisplayNameSync(id)
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -311,9 +314,9 @@ const recentView = {
|
|||||||
const items = this._mapItems(data.items);
|
const items = this._mapItems(data.items);
|
||||||
|
|
||||||
if (isFirstPage) {
|
if (isFirstPage) {
|
||||||
this._component?.render(items, def?.keyFn, def?.labelFn);
|
this._component?.render(items, def?.keyFn, def?.labelFn, def?.headerNodeFn);
|
||||||
} else {
|
} else {
|
||||||
this._component?.append(items, def?.keyFn, def?.labelFn);
|
this._component?.append(items, def?.keyFn, def?.labelFn, def?.headerNodeFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wire unified item tooltip (owner + path) after items are in the DOM.
|
// Wire unified item tooltip (owner + path) after items are in the DOM.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
import { ui } from '../../app/ui.js';
|
import { ui } from '../../app/ui.js';
|
||||||
import { ResourceListComponent } from '../../components/resourceList.js';
|
import { ResourceListComponent } from '../../components/resourceList.js';
|
||||||
|
import { createUserVignette } from '../../components/userVignette.js';
|
||||||
import { normalizeDateBucket, sizeBucket } from '../../core/formatters.js';
|
import { normalizeDateBucket, sizeBucket } from '../../core/formatters.js';
|
||||||
import { i18n } from '../../core/i18n.js';
|
import { i18n } from '../../core/i18n.js';
|
||||||
import * as viewPrefs from '../../core/viewPrefs.js';
|
import * as viewPrefs from '../../core/viewPrefs.js';
|
||||||
@@ -26,7 +27,8 @@ import { systemUsers } from '../../model/systemUsers.js';
|
|||||||
/**
|
/**
|
||||||
* @typedef {{ key: string, label: string, orderBy: string,
|
* @typedef {{ key: string, label: string, orderBy: string,
|
||||||
* keyFn: (item: FileItem|FolderItem) => string|null,
|
* keyFn: (item: FileItem|FolderItem) => string|null,
|
||||||
* labelFn?: (key: string) => string }} GroupByDef
|
* labelFn?: (key: string) => string,
|
||||||
|
* headerNodeFn?: (key: string) => HTMLElement }} GroupByDef
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,6 +44,24 @@ import { systemUsers } from '../../model/systemUsers.js';
|
|||||||
* @type {GroupByDef[]}
|
* @type {GroupByDef[]}
|
||||||
*/
|
*/
|
||||||
const GROUP_BY_DEFS = [
|
const GROUP_BY_DEFS = [
|
||||||
|
{
|
||||||
|
key: 'owner',
|
||||||
|
// label is accessed via syncGroupByMenu → read at section-switch time,
|
||||||
|
// when translations are guaranteed to be loaded.
|
||||||
|
get label() {
|
||||||
|
return i18n.t('groupby.owner', 'Owner');
|
||||||
|
},
|
||||||
|
orderBy: 'granted_by',
|
||||||
|
// keyFn groups by UUID — stable and unique, avoids collisions between
|
||||||
|
// users with the same display name.
|
||||||
|
keyFn: (item) => {
|
||||||
|
const r = /** @type {Record<string,string>} */ (/** @type {unknown} */ (item));
|
||||||
|
return r.owner_id || null;
|
||||||
|
},
|
||||||
|
// labelFn resolves UUID → display name from the pre-fetched cache.
|
||||||
|
labelFn: (id) => systemUsers.getDisplayNameSync(id),
|
||||||
|
headerNodeFn: (id) => createUserVignette(id, 'sm')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'type',
|
key: 'type',
|
||||||
get label() {
|
get label() {
|
||||||
@@ -74,23 +94,6 @@ const GROUP_BY_DEFS = [
|
|||||||
return labels[key] ?? key;
|
return labels[key] ?? key;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'owner',
|
|
||||||
// label is accessed via syncGroupByMenu → read at section-switch time,
|
|
||||||
// when translations are guaranteed to be loaded.
|
|
||||||
get label() {
|
|
||||||
return i18n.t('groupby.owner', 'Owner');
|
|
||||||
},
|
|
||||||
orderBy: 'granted_by',
|
|
||||||
// keyFn groups by UUID — stable and unique, avoids collisions between
|
|
||||||
// users with the same display name.
|
|
||||||
keyFn: (item) => {
|
|
||||||
const r = /** @type {Record<string,string>} */ (/** @type {unknown} */ (item));
|
|
||||||
return r.owner_id || null;
|
|
||||||
},
|
|
||||||
// labelFn resolves UUID → display name from the pre-fetched cache.
|
|
||||||
labelFn: (id) => systemUsers.getDisplayNameSync(id)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'size',
|
key: 'size',
|
||||||
get label() {
|
get label() {
|
||||||
@@ -316,9 +319,9 @@ const sharedWithMeView = {
|
|||||||
const items = this._mapItems(data.items);
|
const items = this._mapItems(data.items);
|
||||||
|
|
||||||
if (isFirstPage) {
|
if (isFirstPage) {
|
||||||
this._component?.render(items, def?.keyFn, def?.labelFn);
|
this._component?.render(items, def?.keyFn, def?.labelFn, def?.headerNodeFn);
|
||||||
} else {
|
} else {
|
||||||
this._component?.append(items, def?.keyFn, def?.labelFn);
|
this._component?.append(items, def?.keyFn, def?.labelFn, def?.headerNodeFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wire owner tooltips after items are in the DOM
|
// Wire owner tooltips after items are in the DOM
|
||||||
|
|||||||
Reference in New Issue
Block a user