Files
Psychotoxical-psysonic/src/utils/serverCluster/clusterLibraryScopes.ts
T
Maxim Isaev 558f42dba6 feat(cluster): per-member library scope in sidebar picker and merge reads
Cluster mode lists music folders from every member (cached for instant
picker), applies per-server musicLibraryFilter scopes in merged browse,
and passes libraryScopes through Rust list/advanced-search SQL filters.
Dropdown width sizes to content; single-server picker unchanged.
2026-06-05 19:11:40 +03:00

76 lines
2.8 KiB
TypeScript

import { libraryScopeForServer } from '../../api/subsonicClient';
import { useAuthStore } from '../../store/authStore';
import { resolveServerIdForIndexKey } from '../server/serverLookup';
/** Per-member Navidrome music-folder scopes for cluster index reads (omit = all libraries). */
export function buildClusterLibraryScopes(memberIds: string[]): Record<string, string> | undefined {
const scopes: Record<string, string> = {};
for (const sid of memberIds) {
const scope = libraryScopeForServer(sid);
if (scope) scopes[sid] = scope;
}
return Object.keys(scopes).length > 0 ? scopes : undefined;
}
export function isClusterAllLibrariesSelected(memberIds: string[]): boolean {
const filters = useAuthStore.getState().musicLibraryFilterByServer;
return memberIds.every(sid => {
const resolved = resolveServerIdForIndexKey(sid) || sid;
const f = filters[resolved] ?? filters[sid];
return f === undefined || f === 'all';
});
}
/** Label for the sidebar scope subtitle when one member is narrowed. */
export function clusterLibraryScopeSubtitle(
memberIds: string[],
entries: Array<{ serverId: string; serverLabel: string; folderId: string; folderName: string }>,
): string | null {
if (isClusterAllLibrariesSelected(memberIds)) return null;
const filters = useAuthStore.getState().musicLibraryFilterByServer;
for (const entry of entries) {
const resolved = resolveServerIdForIndexKey(entry.serverId) || entry.serverId;
const f = filters[resolved] ?? filters[entry.serverId];
if (f && f !== 'all' && f === entry.folderId) {
return `${entry.serverLabel}${entry.folderName}`;
}
}
return null;
}
export function isClusterLibraryFolderSelected(
serverId: string,
folderId: string,
): boolean {
const filters = useAuthStore.getState().musicLibraryFilterByServer;
const resolved = resolveServerIdForIndexKey(serverId) || serverId;
const f = filters[resolved] ?? filters[serverId];
return f === folderId;
}
export function clusterLibraryPickerEntryId(serverId: string, folderId: string): string {
return `${serverId}::${folderId}`;
}
/** Sidebar picker `filterId` in cluster mode (`all` or `serverId::folderId`). */
export function clusterPickerFilterId(
memberIds: string[],
entries: Array<{ serverId: string; folderId: string }>,
): string {
if (isClusterAllLibrariesSelected(memberIds)) return 'all';
for (const entry of entries) {
if (isClusterLibraryFolderSelected(entry.serverId, entry.folderId)) {
return clusterLibraryPickerEntryId(entry.serverId, entry.folderId);
}
}
return 'all';
}
export function parseClusterLibraryPickerEntryId(
entryId: string,
): { serverId: string; folderId: string } | null {
const sep = entryId.indexOf('::');
if (sep <= 0) return null;
return { serverId: entryId.slice(0, sep), folderId: entryId.slice(sep + 2) };
}