diff --git a/docs/configuration/folders.md b/docs/configuration/folders.md index 182678fe..0082c4e1 100644 --- a/docs/configuration/folders.md +++ b/docs/configuration/folders.md @@ -313,7 +313,7 @@ folders: id: my-folder ha: url: >- - https://ha.ondu.org/media-browser/browser/app%2Cmedia-source%3A%2F%2Fmedia_source + /media-browser/browser/app%2Cmedia-source%3A%2F%2Fmedia_source path: # Match everything, parse nothing. - {} @@ -331,7 +331,7 @@ folders: id: my-folder ha: url: >- - https://ha.ondu.org/media-browser/browser/app%2Cmedia-source%3A%2F%2Fmedia_source + /media-browser/browser/app%2Cmedia-source%3A%2F%2Fmedia_source path: - {} # At the final level, match everything, parse the date and time. @@ -350,7 +350,7 @@ folders: id: my-folder ha: url: >- - https://ha.ondu.org/media-browser/browser/app%2Cmedia-source%3A%2F%2Fmedia_source + /media-browser/browser/app%2Cmedia-source%3A%2F%2Fmedia_source path: - {} # At the final level, match everything, parse the date and time. diff --git a/docs/configuration/performance.md b/docs/configuration/performance.md index 1f26a77b..54fe6474 100644 --- a/docs/configuration/performance.md +++ b/docs/configuration/performance.md @@ -22,13 +22,13 @@ performance: # [...] ``` -| Option | Default | Description | -| ---------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `animated_progress_indicator` | `true` | Will show the animated progress indicator 'spinners' when `true`. | -| `card_loading_effects` | `true` | Will occasionally show (silly) visual effects during card loading (e.g. fireworks on New Year's Day). | -| `card_loading_indicator` | `true` | Will show the card loading indicator (spinner & version number) and effects (see `card_loading_effects` below) when `true`. | -| `media_chunk_size` | `50` | How many media items to fetch and render at a time (e.g. thumbnails under a live view, or number of snapshots to load in the media viewer). This may only make partial sense in some contexts (e.g. the 'infinite gallery' is still infinite, it just loads thumbnails this many items at a time) or not at all (e.g. the timeline will show the number of events dictated by the time span the user navigates to). | -| `max_simultaneous_engine_requests` | _Infinity_ | How many camera engine requests to allow occur in parallel. Setting lower values will slow the card down since more requests will run in sequence, but it will increase the chances of positive cache hit rates and reduce the chances of overwhelming the backend. | +| Option | Default | Description | +| ---------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `animated_progress_indicator` | `true` | Will show the animated progress indicator 'spinners' when `true`. | +| `card_loading_effects` | `true` | Will occasionally show (silly) visual effects during card loading (e.g. fireworks on New Year's Day). | +| `card_loading_indicator` | `true` | Will show the card loading indicator (spinner & version number) and effects (see `card_loading_effects` below) when `true`. | +| `media_chunk_size` | `50` | How many media items to fetch and render at a time (e.g. thumbnails under a live view, or number of snapshots to load in the media viewer). This may only make partial sense in some contexts (e.g. the 'infinite gallery' is still infinite, it just loads thumbnails this many items at a time) or not at all (e.g. the timeline will show the number of events dictated by the time span the user navigates to, and [`folders`](./folders.md) always show every matching item). | +| `max_simultaneous_engine_requests` | _Infinity_ | How many camera engine requests to allow occur in parallel. Setting lower values will slow the card down since more requests will run in sequence, but it will increase the chances of positive cache hit rates and reduce the chances of overwhelming the backend. | ### `style` diff --git a/src/card-controller/folders/ha/engine.ts b/src/card-controller/folders/ha/engine.ts index 8553a939..efd96587 100644 --- a/src/card-controller/folders/ha/engine.ts +++ b/src/card-controller/folders/ha/engine.ts @@ -166,7 +166,6 @@ export class HAFoldersEngine implements FoldersEngine { targets: BrowseMediaTarget[], ): BrowseMediaStep[] => { const nextComponent = pathComponents.shift(); - const limit = query.limit ?? null; return [ { @@ -174,11 +173,6 @@ export class HAFoldersEngine implements FoldersEngine { metadataGenerator: (media, parent) => this._metadataGenerator.generate(media, parent, nextComponent?.ha?.parsers), - // At the final step (no nextComponent), apply limit via earlyExit. - ...(limit && { - earlyExit: (media) => media.length >= limit, - }), - ...(nextComponent && { matcher: (media) => this._mediaMatcher.match(hass, media, { @@ -202,11 +196,10 @@ export class HAFoldersEngine implements FoldersEngine { }, ); - const results = getViewItemsFromBrowseMediaArray(browseMedia, { + return getViewItemsFromBrowseMediaArray(browseMedia, { folder: query.folder, path: query.path, }); - return query.limit ? results.slice(0, query.limit) : results; } public generateChildFolderQuery( diff --git a/src/card-controller/folders/types.ts b/src/card-controller/folders/types.ts index 0c48d59d..2b5d8592 100644 --- a/src/card-controller/folders/types.ts +++ b/src/card-controller/folders/types.ts @@ -39,7 +39,6 @@ export interface FolderQuery extends BaseQuery, QueryFilters { // A trail of paths to navigate back to the "root", with the last path being // the path that this query directly refers to. path: NonEmptyTuple; - limit?: number; } // =============== diff --git a/src/card-controller/view/view-query-executor.ts b/src/card-controller/view/view-query-executor.ts index 694b4926..9487b351 100644 --- a/src/card-controller/view/view-query-executor.ts +++ b/src/card-controller/view/view-query-executor.ts @@ -205,9 +205,7 @@ export class ViewQueryExecutor { case 'folders': viewModifiers.push( ...(await executeQuery( - builder.buildDefaultFolderQuery(queryExecutorOptions?.folder, { - limit: this._getLimit(), - }), + builder.buildDefaultFolderQuery(queryExecutorOptions?.folder), )), ); break; diff --git a/src/components-lib/navigation.ts b/src/components-lib/navigation.ts index 5e9ee85d..c7a1cd57 100644 --- a/src/components-lib/navigation.ts +++ b/src/components-lib/navigation.ts @@ -10,8 +10,6 @@ import type { UnifiedQueryBuilder } from '../view/unified-query-builder'; export interface FolderNavigationParamaters { viewManagerEpoch: ViewManagerEpoch; builder: UnifiedQueryBuilder; - - limit?: number; } export interface MediaNavigationParamaters { @@ -51,9 +49,6 @@ export const navigateUp = (options?: FolderNavigationParamaters | null): void => const query = options?.builder.buildFolderQueryWithPath( folderQuery.folder, nonEmptyPath, - { - limit: options?.limit, - }, ); void options?.viewManagerEpoch.manager.setViewByParametersWithExistingQuery({ @@ -73,9 +68,6 @@ export const navigateToFolder = ( const query = options?.builder.buildFolderQueryWithPath( item.getFolder(), nonEmptyPath, - { - limit: options?.limit, - }, ); void options?.viewManagerEpoch?.manager.setViewByParametersWithExistingQuery({ diff --git a/src/components/gallery/gallery.ts b/src/components/gallery/gallery.ts index 8a128436..493700a6 100644 --- a/src/components/gallery/gallery.ts +++ b/src/components/gallery/gallery.ts @@ -26,7 +26,6 @@ import { import type { ConditionStateManagerReadonlyInterface } from '../../condition-trigger/conditions/types.js'; import type { MediaGalleryConfig } from '../../config/schema/media-gallery.js'; import type { CardWideConfig } from '../../config/schema/types.js'; -import { MEDIA_CHUNK_SIZE_DEFAULT } from '../../const.js'; import type { HomeAssistant } from '../../ha/types.js'; import galleryStyle from '../../scss/gallery.scss?inline'; import { stopEventFromActivatingCardWideActions } from '../../utils/action.js'; @@ -101,19 +100,11 @@ export class AdvancedCameraCardGallery extends LitElement { } } - private _getLimit(): number { - return ( - this.cardWideConfig?.performance?.features?.media_chunk_size ?? - MEDIA_CHUNK_SIZE_DEFAULT - ); - } - private _getFolderNavigationParameters(): FolderNavigationParamaters | null { return this._builder && this.viewManagerEpoch ? { builder: this._builder, viewManagerEpoch: this.viewManagerEpoch, - limit: this._getLimit(), } : null; } diff --git a/src/components/thumbnail-carousel.ts b/src/components/thumbnail-carousel.ts index 65908eea..59157533 100644 --- a/src/components/thumbnail-carousel.ts +++ b/src/components/thumbnail-carousel.ts @@ -23,7 +23,6 @@ import { } from '../components-lib/navigation.js'; import type { ThumbnailsControlConfig } from '../config/schema/common/controls/thumbnails.js'; import type { CardWideConfig } from '../config/schema/types.js'; -import { MEDIA_CHUNK_SIZE_DEFAULT } from '../const.js'; import type { HomeAssistant } from '../ha/types.js'; import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss?inline'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; @@ -73,19 +72,11 @@ export class AdvancedCameraCardThumbnailCarousel extends LitElement { private _thumbnails: TemplateResult[] = []; private _builder: UnifiedQueryBuilder | null = null; - private _getLimit(): number { - return ( - this.cardWideConfig?.performance?.features?.media_chunk_size ?? - MEDIA_CHUNK_SIZE_DEFAULT - ); - } - private _getFolderNavOptions(): FolderNavigationParamaters | undefined { return this._builder && this.viewManagerEpoch ? { builder: this._builder, viewManagerEpoch: this.viewManagerEpoch, - limit: this._getLimit(), } : undefined; } diff --git a/src/view/unified-query-builder.ts b/src/view/unified-query-builder.ts index 1bf444fe..361c3b0e 100644 --- a/src/view/unified-query-builder.ts +++ b/src/view/unified-query-builder.ts @@ -267,14 +267,12 @@ export class UnifiedQueryBuilder { public buildFolderQueryWithPath( folder: FolderConfig, path: NonEmptyTuple, - options?: QueryFiltersOptions, ): UnifiedQuery { const query = new UnifiedQuery(); const folderQuery: FolderQuery = { source: QuerySource.Folder, folder, path, - ...(options?.limit !== undefined && { limit: options.limit }), }; query.addNode(folderQuery); return query; @@ -282,7 +280,7 @@ export class UnifiedQueryBuilder { public buildDefaultFolderQuery( folderID?: string, - options?: QueryFiltersOptions, + options?: QueryFilters, ): UnifiedQuery | null { const query = new UnifiedQuery(); this._addNode(query, this._buildFolderQueryNode(folderID, options)); @@ -317,7 +315,7 @@ export class UnifiedQueryBuilder { return params ? { ...params, - ...options, + ...this._extractFilterOptions(options), } : null; } diff --git a/tests/card-controller/folders/ha/engine.test.ts b/tests/card-controller/folders/ha/engine.test.ts index 231bada0..ec0e3d82 100644 --- a/tests/card-controller/folders/ha/engine.test.ts +++ b/tests/card-controller/folders/ha/engine.test.ts @@ -238,12 +238,11 @@ describe('HAFoldersEngine', () => { expect(await engine.expandFolder(createHASS(), query)).toBeNull(); }); - it('should early exit when limit is reached', async () => { + it('should return every matching media item without a cap', async () => { const query: FolderQuery = { source: QuerySource.Folder, folder: { type: 'ha', id: 'test' }, path: [{ ha: { id: 'media-source://id' } }], - limit: 1, }; vi.mocked(homeAssistantWSRequest).mockResolvedValueOnce( @@ -266,8 +265,7 @@ describe('HAFoldersEngine', () => { const engine = new HAFoldersEngine(templateManager); const results = await engine.expandFolder(createHASS(), query); - // Even though there are 2 children, the limit of 1 should trigger earlyExit. - expect(results?.length).toBe(1); + expect(results?.length).toBe(2); }); // See additional matcher testing in media-matcher.test.ts . diff --git a/tests/components-lib/navigation.test.ts b/tests/components-lib/navigation.test.ts index af7b4761..db1a9b63 100644 --- a/tests/components-lib/navigation.test.ts +++ b/tests/components-lib/navigation.test.ts @@ -157,35 +157,6 @@ describe('navigateUp', () => { path: [{ ha: { id: 'one' } }, { ha: { id: 'two' } }], }); }); - - it('should go up in the folder hierarchy with limit', () => { - const api = createCardAPI(); - const folder = createFolder(); - const view = createView({ - query: createFolderQuery(folder, [{ ha: { id: 'one' } }, { ha: { id: 'two' } }]), - }); - vi.mocked(api.getViewManager().getView).mockReturnValue(view); - - const epoch: ViewManagerEpoch = { - manager: api.getViewManager(), - }; - - const builder = new UnifiedQueryBuilder( - mock(), - mock(), - ); - const options: FolderNavigationParamaters = { - builder, - viewManagerEpoch: epoch, - limit: 50, - }; - - navigateUp(options); - - const query = vi.mocked(api.getViewManager().setViewByParametersWithExistingQuery) - .mock.calls[0][0]?.params?.query as UnifiedQuery; - expect(query.getNodes()[0].limit).toBe(50); - }); }); describe('navigateToFolder', () => { @@ -241,31 +212,6 @@ describe('navigateToFolder', () => { expect(nodes?.[0]).toHaveProperty('path'); expect((nodes?.[0] as { path: readonly unknown[] }).path).toHaveLength(2); }); - - it('should navigate into folder with limit', () => { - const api = createCardAPI(); - const folder = createFolder(); - const epoch: ViewManagerEpoch = { - manager: api.getViewManager(), - }; - - const builder = new UnifiedQueryBuilder( - mock(), - mock(), - ); - const options: FolderNavigationParamaters = { - builder, - viewManagerEpoch: epoch, - limit: 100, - }; - - const item = new ViewFolder(folder, [{ ha: { id: 'root' } }]); - navigateToFolder(item, options); - - const query = vi.mocked(api.getViewManager().setViewByParametersWithExistingQuery) - .mock.calls[0][0]?.params?.query; - expect(query?.getNodes()[0].limit).toBe(100); - }); }); describe('getUpFolderItem', () => { diff --git a/tests/view/unified-query-builder.test.ts b/tests/view/unified-query-builder.test.ts index 40370dbb..f7a320cf 100644 --- a/tests/view/unified-query-builder.test.ts +++ b/tests/view/unified-query-builder.test.ts @@ -499,22 +499,7 @@ describe('UnifiedQueryBuilder', () => { }); }); - it('should apply limit option', () => { - const { cameraManager, foldersManager } = createMocks(); - const builder = new UnifiedQueryBuilder(cameraManager, foldersManager); - const folder = createFolder({ id: 'folder1', title: 'Test' }); - const path: [FolderPathComponent] = [{ ha: { id: 'Root' } }]; - const query = builder.buildFolderQueryWithPath(folder, path, { - limit: 10, - }); - - expect(query.getNodes()[0]).toMatchObject({ - source: QuerySource.Folder, - limit: 10, - }); - }); - - it('should not include limit when not provided', () => { + it('should not limit the number of items', () => { const { cameraManager, foldersManager } = createMocks(); const builder = new UnifiedQueryBuilder(cameraManager, foldersManager); const folder = createFolder({ id: 'folder1', title: 'Test' }); @@ -568,26 +553,6 @@ describe('UnifiedQueryBuilder', () => { expect(query).toBeNull(); }); - it('should apply limit option', () => { - const { cameraManager, foldersManager } = createMocks(); - const folder = createFolder({ id: 'folder1', title: 'Test' }); - const path: [FolderPathComponent] = [{ ha: { id: 'Root' } }]; - - foldersManager.getFolder.mockReturnValue(folder); - foldersManager.getDefaultQueryParameters.mockReturnValue( - createFolderQueryParams(folder, path), - ); - - const builder = new UnifiedQueryBuilder(cameraManager, foldersManager); - const query = builder.buildDefaultFolderQuery('folder1', { limit: 20 }); - - assert(query); - expect(query.getNodes()[0]).toMatchObject({ - source: QuerySource.Folder, - limit: 20, - }); - }); - it('should use default folder when no ID provided', () => { const { cameraManager, foldersManager } = createMocks(); foldersManager.getFolder.mockReturnValue(null); @@ -1010,6 +975,30 @@ describe('UnifiedQueryBuilder', () => { }); }); + it('should not limit a folder query built for folder media type', () => { + const { cameraManager, foldersManager, store } = createMocks(); + store.getCameraIDs.mockReturnValue(new Set(['camera.office'])); + store.getCameraConfig.mockReturnValue( + createCameraConfig({ media: { type: 'folder', folders: ['folder1'] } }), + ); + + cameraManager.getCameraCapabilities.mockReturnValue(createCapabilities()); + + const folder = createFolder({ id: 'folder1', title: 'Test Folder' }); + const path: [FolderPathComponent] = [{ ha: { id: 'Root' } }]; + + foldersManager.getFolder.mockReturnValue(folder); + foldersManager.getDefaultQueryParameters.mockReturnValue( + createFolderQueryParams(folder, path), + ); + + const builder = new UnifiedQueryBuilder(cameraManager, foldersManager); + const query = builder.buildDefaultCameraQuery(undefined, { limit: 50 }); + + assert(query); + expect(query.getNodes()[0]).not.toHaveProperty('limit'); + }); + it('should build default folder query for folder media type without specific folders', () => { const { cameraManager, foldersManager, store } = createMocks(); store.getCameraIDs.mockReturnValue(new Set(['camera.office']));