diff --git a/docs/configuration/actions/custom/README.md b/docs/configuration/actions/custom/README.md index 29425009..8ce8caab 100644 --- a/docs/configuration/actions/custom/README.md +++ b/docs/configuration/actions/custom/README.md @@ -114,7 +114,7 @@ advanced_camera_card_action: expand ## `folder` -Show a given folder in the folder gallery. +Show media from a given folder in the media viewer. ```yaml action: custom:advanced-camera-card-action @@ -126,6 +126,20 @@ advanced_camera_card_action: folder | --------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | | `folder` | The first configured folder (under [`folders`](../../folders.md)). | An optional id of the folder to show, see the `id` parameter under [`folders` configuration](../../folders.md). | +## `folders` + +Show a given folder in the folders gallery. + +```yaml +action: custom:advanced-camera-card-action +advanced_camera_card_action: folders +# [...] +``` + +| Parameter | Default | Description | +| --------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | +| `folder` | The first configured folder (under [`folders`](../../folders.md)). | An optional id of the folder to show, see the `id` parameter under [`folders` configuration](../../folders.md). | + ## `fullscreen` Toggle fullscreen. @@ -821,4 +835,18 @@ elements: - action: custom:advanced-camera-card-action advanced_camera_card_action: status_bar status_bar_action: reset + - type: custom:advanced-camera-card-menu-icon + icon: mdi:alpha-m-circle-outline + title: View Folder Media + tap_action: + - action: custom:advanced-camera-card-action + advanced_camera_card_action: folder + folder: my-folder + - type: custom:advanced-camera-card-menu-icon + icon: mdi:alpha-n-circle-outline + title: View Folders Gallery + tap_action: + - action: custom:advanced-camera-card-action + advanced_camera_card_action: folders + folder: my-folder ``` diff --git a/docs/configuration/menu.md b/docs/configuration/menu.md index 96d84d5a..958ecb45 100644 --- a/docs/configuration/menu.md +++ b/docs/configuration/menu.md @@ -36,7 +36,7 @@ menu: | `display_mode` | The `display_mode` button allows changing between single and grid views. | | `download` | The `download` menu button: allow direct download of the media being displayed. | | `expand` | The `expand` menu button: expand the card into a popup/dialog. | -| `folders` | The `folders` menu button to select a folder of media to view in the [`media_gallery`](./media-gallery.md). Will only appear if [`folders`](./folders.md) are configured. | +| `folders` | The `folders` menu button: brings the user to a [gallery](./media-gallery.md) of folders on tap and to the media viewer with media from the folder on hold. Will only appear if [`folders`](./folders.md) are configured. | | `fullscreen` | The `fullscreen` menu button: expand the card to consume the fullscreen. Please note that fullscreen behavior on iPhone is limited, see [troubleshooting](../troubleshooting.md?id=fullscreen-doesn39t-work-on-iphone). | | `image` | The `image` view menu button: brings the user to the static `image` view. | | `iris` | The main Advanced Camera Card `iris` menu button: brings the user to the default configured view (`view.default`), or collapses/expands the menu if the `menu.style` is `hidden` . | diff --git a/docs/configuration/view.md b/docs/configuration/view.md index 7cb2c89e..ff757273 100644 --- a/docs/configuration/view.md +++ b/docs/configuration/view.md @@ -142,7 +142,8 @@ This card supports several different views. | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `clip` | Shows a viewer for the most recent clip for this camera. Can also be accessed by holding down the `clips` menu icon. | | `clips` | Shows a gallery of clips for this camera. | -| `folder` | Shows a gallery of media from a [`folder`](./folders.md). | +| `folder` | Shows a viewer for the media from the default [`folder`](./folders.md). | +| `folders` | Shows a gallery of media and subfolders from the default [`folder`](./folders.md). | | `image` | Shows a static image specified by the `image` parameter, can be used as a discrete default view or a screensaver (via `view.interaction_seconds`). | | `live` | Shows the live camera view with the configured [live provider](./cameras/live-provider.md). | | `recording` | Shows a viewer for the most recent recording for this camera. Can also be accessed by holding down the `recordings` menu icon. | diff --git a/src/card-controller/actions/actions/folder.ts b/src/card-controller/actions/actions/folders-view.ts similarity index 68% rename from src/card-controller/actions/actions/folder.ts rename to src/card-controller/actions/actions/folders-view.ts index f4245f7a..2da8310f 100644 --- a/src/card-controller/actions/actions/folder.ts +++ b/src/card-controller/actions/actions/folders-view.ts @@ -1,9 +1,9 @@ -import { FolderActionConfig } from '../../../config/schema/actions/custom/folder'; +import { FoldersViewActionConfig } from '../../../config/schema/actions/custom/folders-view'; import { FolderViewQuery } from '../../../view/query'; import { CardActionsAPI } from '../../types'; import { AdvancedCameraCardAction } from './base'; -export class FolderAction extends AdvancedCameraCardAction { +export class FoldersViewAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await super.execute(api); @@ -19,7 +19,8 @@ export class FolderAction extends AdvancedCameraCardAction { await api.getViewManager().setViewByParametersWithExistingQuery({ params: { - view: 'folder', + // Supports both 'folder' and 'folders' views. + view: this._action.advanced_camera_card_action, query: new FolderViewQuery(query), }, }); diff --git a/src/card-controller/actions/factory.ts b/src/card-controller/actions/factory.ts index ddb7bdda..64ef9d71 100644 --- a/src/card-controller/actions/factory.ts +++ b/src/card-controller/actions/factory.ts @@ -10,7 +10,7 @@ import { DefaultAction } from './actions/default'; import { DisplayModeSelectAction } from './actions/display-mode-select'; import { DownloadAction } from './actions/download'; import { ExpandAction } from './actions/expand'; -import { FolderAction } from './actions/folder'; +import { FoldersViewAction } from './actions/folders-view'; import { FullscreenAction } from './actions/fullscreen'; import { InternalCallbackAction } from './actions/internal-callback'; import { LogAction } from './actions/log'; @@ -152,7 +152,8 @@ export class ActionFactory { case INTERNAL_CALLBACK_ACTION: return new InternalCallbackAction(context, action, options?.config); case 'folder': - return new FolderAction(context, action, options?.config); + case 'folders': + return new FoldersViewAction(context, action, options?.config); } /* istanbul ignore next: this path cannot be reached -- @preserve */ diff --git a/src/card-controller/view/view-query-executor.ts b/src/card-controller/view/view-query-executor.ts index df5a0a16..3c77ff7a 100644 --- a/src/card-controller/view/view-query-executor.ts +++ b/src/card-controller/view/view-query-executor.ts @@ -120,6 +120,7 @@ export class ViewQueryExecutor { viewModifiers.push(...(await executeMediaQuery(mediaType))); break; case 'folder': + case 'folders': viewModifiers.push(...(await executeFolderQuery())); break; } diff --git a/src/components-lib/menu-button-controller.ts b/src/components-lib/menu-button-controller.ts index b14837c2..d626c50c 100644 --- a/src/components-lib/menu-button-controller.ts +++ b/src/components-lib/menu-button-controller.ts @@ -15,7 +15,7 @@ import { MediaLoadedInfo } from '../types'; import { createCameraAction, createDisplayModeAction, - createFolderAction, + createFoldersViewAction, createGeneralAction, createMediaPlayerAction, createPTZControlsAction, @@ -658,7 +658,8 @@ export class MenuButtonController { type: 'custom:advanced-camera-card-menu-icon', title: folder.title ?? localize('config.menu.buttons.folders'), style: isSelected ? this._getEmphasizedStyle() : {}, - tap_action: createFolderAction(), + tap_action: createFoldersViewAction('folders'), + hold_action: createFoldersViewAction('folder'), }; } @@ -667,14 +668,14 @@ export class MenuButtonController { QueryClassifier.isFolderQuery(view?.query) && view.query.getQuery()?.folder.id === id; - const action = createFolderAction({ folderID: id }); return { enabled: true, title: folder.title ?? folder.id, icon: folder.icon ?? 'mdi:folder', selected: isSelected, style: isSelected ? this._getEmphasizedStyle() : {}, - ...(action && { tap_action: action }), + tap_action: createFoldersViewAction('folders', { folderID: id }), + hold_action: createFoldersViewAction('folder', { folderID: id }), }; }); @@ -684,7 +685,7 @@ export class MenuButtonController { type: 'custom:advanced-camera-card-menu-submenu', title: localize('config.menu.buttons.folders'), items: submenuItems, - style: view?.is('folder') ? this._getEmphasizedStyle() : {}, + style: view?.isAnyFolderView() ? this._getEmphasizedStyle() : {}, }; } diff --git a/src/components/views.ts b/src/components/views.ts index c41ef100..59c77ea5 100644 --- a/src/components/views.ts +++ b/src/components/views.ts @@ -68,7 +68,7 @@ export class AdvancedCameraCardViews extends LitElement { if (view?.is('live') || this._shouldLivePreload()) { import('./live/index.js'); } - if (view?.isMediaGalleryView() && !view.is('folder')) { + if (view?.isMediaGalleryView() && !view.is('folders')) { import('./gallery/media-gallery.js'); } else if (view?.isViewerView()) { import('./viewer/index.js'); @@ -76,7 +76,7 @@ export class AdvancedCameraCardViews extends LitElement { import('./image.js'); } else if (view?.is('timeline')) { import('./timeline.js'); - } else if (view?.is('folder')) { + } else if (view?.is('folders')) { import('./gallery/folder-gallery.js'); } } @@ -157,7 +157,7 @@ export class AdvancedCameraCardViews extends LitElement { > ` : ``} - ${!this.hide && view?.isMediaGalleryView() && !view.is('folder') + ${!this.hide && view?.isMediaGalleryView() && !view.is('folders') ? html` ` : ``} - ${!this.hide && view?.is('folder') + ${!this.hide && view?.is('folders') ? html` ; diff --git a/src/config/schema/actions/custom/folders-view.ts b/src/config/schema/actions/custom/folders-view.ts new file mode 100644 index 00000000..79ed0fb5 --- /dev/null +++ b/src/config/schema/actions/custom/folders-view.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; +import { advancedCameraCardCustomActionsBaseSchema } from './base'; + +export const foldersViewActionConfigSchema = + advancedCameraCardCustomActionsBaseSchema.extend({ + advanced_camera_card_action: z.literal('folders').or(z.literal('folder')), + folder: z.string().optional(), + }); +export type FoldersViewActionConfig = z.infer; diff --git a/src/config/schema/actions/custom/view.ts b/src/config/schema/actions/custom/view.ts index c36617dd..8ca57ce1 100644 --- a/src/config/schema/actions/custom/view.ts +++ b/src/config/schema/actions/custom/view.ts @@ -7,14 +7,14 @@ import { advancedCameraCardCustomActionsBaseSchema } from './base'; type AdvancedCameraCardUserSpecifiedViewWithoutFolder = Exclude< AdvancedCameraCardUserSpecifiedView, - 'folder' + 'folder' | 'folders' >; export const viewActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend({ advanced_camera_card_action: z.enum( - // The folder view is handled by the `folder` action since it accepts an + // The folder/folders views are handled separately as they accept an // optional folder ID. - VIEWS_USER_SPECIFIED.filter((view) => view !== 'folder') as [ + VIEWS_USER_SPECIFIED.filter((view) => view !== 'folder' && view !== 'folders') as [ AdvancedCameraCardUserSpecifiedViewWithoutFolder, ...AdvancedCameraCardUserSpecifiedViewWithoutFolder[], ], diff --git a/src/config/schema/actions/types.ts b/src/config/schema/actions/types.ts index 2bf0590b..dfb266a8 100644 --- a/src/config/schema/actions/types.ts +++ b/src/config/schema/actions/types.ts @@ -3,7 +3,7 @@ import { statusBarItemBaseSchema } from '../common/status-bar'; import { advancedCameraCardCustomActionsBaseSchema } from './custom/base'; import { cameraSelectActionConfigSchema } from './custom/camera-select'; import { viewDisplayModeActionConfigSchema } from './custom/display-mode'; -import { folderActionConfigSchema } from './custom/folder'; +import { foldersViewActionConfigSchema } from './custom/folders-view'; import { generalActionConfigSchema } from './custom/general'; import { internalCallbackActionConfigSchema } from './custom/internal'; import { logActionConfigSchema } from './custom/log'; @@ -42,7 +42,7 @@ export const statusBarActionConfigSchema: z.ZodSchema< const advancedCameraCardCustomActionSchema = z.union([ cameraSelectActionConfigSchema, - folderActionConfigSchema, + foldersViewActionConfigSchema, generalActionConfigSchema, internalCallbackActionConfigSchema, logActionConfigSchema, diff --git a/src/config/schema/common/const.ts b/src/config/schema/common/const.ts index 55857f8e..19de9e24 100644 --- a/src/config/schema/common/const.ts +++ b/src/config/schema/common/const.ts @@ -17,6 +17,7 @@ export const VIEWS_USER_SPECIFIED = [ 'clip', 'clips', 'folder', + 'folders', 'snapshot', 'snapshots', 'recording', diff --git a/src/editor.ts b/src/editor.ts index a2371bd5..c851a60a 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -414,6 +414,7 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard { value: 'clip', label: localize('config.view.views.clip') }, { value: 'clips', label: localize('config.view.views.clips') }, { value: 'folder', label: localize('config.view.views.folder') }, + { value: 'folders', label: localize('config.view.views.folders') }, { value: 'image', label: localize('config.view.views.image') }, { value: 'live', label: localize('config.view.views.live') }, { value: 'recording', label: localize('config.view.views.recording') }, diff --git a/src/localize/languages/ca.json b/src/localize/languages/ca.json index 340ab281..bd9ce3cc 100644 --- a/src/localize/languages/ca.json +++ b/src/localize/languages/ca.json @@ -589,6 +589,7 @@ "clips": "Galeria de clips", "current": "Vista actual", "folder": "", + "folders": "", "image": "Imatge estàtica", "live": "Visualització en directe", "recording": "Enregistrament més recent", diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index e117ff1c..51781bc0 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -588,7 +588,8 @@ "clip": "Most recent clip", "clips": "Clips gallery", "current": "Current view", - "folder": "Folder view", + "folder": "Folder media", + "folders": "Folders gallery", "image": "Static image", "live": "Live view", "recording": "Most recent recording", diff --git a/src/localize/languages/fr.json b/src/localize/languages/fr.json index 10a0ac63..b504cfdf 100644 --- a/src/localize/languages/fr.json +++ b/src/localize/languages/fr.json @@ -589,6 +589,7 @@ "clips": "Galerie de clips", "current": "Vue actuelle", "folder": "", + "folders": "", "image": "Image statique", "live": "Vue en direct", "recording": "Enregistrement le plus récent", diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json index d484cb53..9f46a079 100644 --- a/src/localize/languages/it.json +++ b/src/localize/languages/it.json @@ -589,6 +589,7 @@ "clips": "Galleria delle clip", "current": "Vista corrente", "folder": "", + "folders": "", "image": "Immagine statica", "live": "Dal vivo", "recording": "", diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json index afa36e7c..842e6929 100644 --- a/src/localize/languages/pt-BR.json +++ b/src/localize/languages/pt-BR.json @@ -589,6 +589,7 @@ "clips": "Galeria de clipes", "current": "Visualização atual", "folder": "", + "folders": "", "image": "Imagem estática", "live": "Visualização ao vivo", "recording": "Gravação mais recente", diff --git a/src/localize/languages/pt-PT.json b/src/localize/languages/pt-PT.json index 40bd7635..077e944c 100644 --- a/src/localize/languages/pt-PT.json +++ b/src/localize/languages/pt-PT.json @@ -589,6 +589,7 @@ "clips": "Galeria de clipes", "current": "Visualização atual", "folder": "", + "folders": "", "image": "Imagem estática", "live": "Visualização ao vivo", "recording": "", diff --git a/src/utils/action.ts b/src/utils/action.ts index adc84d49..97e2ab65 100644 --- a/src/utils/action.ts +++ b/src/utils/action.ts @@ -2,7 +2,7 @@ import { CardActionsAPI } from '../card-controller/types.js'; import { ZoomSettingsBase } from '../components-lib/zoom/types.js'; import { CameraSelectActionConfig } from '../config/schema/actions/custom/camera-select.js'; import { DisplayModeActionConfig } from '../config/schema/actions/custom/display-mode.js'; -import { FolderActionConfig } from '../config/schema/actions/custom/folder.js'; +import { FoldersViewActionConfig } from '../config/schema/actions/custom/folders-view.js'; import { AdvancedCameraCardGeneralAction, GeneralActionConfig, @@ -47,7 +47,7 @@ export function createGeneralAction( } export function createViewAction( - action: Exclude, + action: Exclude, options?: { cardID?: string; }, @@ -74,13 +74,16 @@ export function createCameraAction( }; } -export function createFolderAction(options?: { - cardID?: string; - folderID?: string; -}): FolderActionConfig { +export function createFoldersViewAction( + view: 'folder' | 'folders', + options?: { + cardID?: string; + folderID?: string; + }, +): FoldersViewActionConfig { return { action: 'fire-dom-event', - advanced_camera_card_action: 'folder', + advanced_camera_card_action: view, ...(options?.folderID && { folder: options.folderID }), ...(options?.cardID && { card_id: options.cardID }), }; diff --git a/src/view/view-to-cameras.ts b/src/view/view-to-cameras.ts index 19d4e6c6..f8bdf3d2 100644 --- a/src/view/view-to-cameras.ts +++ b/src/view/view-to-cameras.ts @@ -19,6 +19,7 @@ export const getCameraIDsForViewName = ( case 'diagnostics': case 'image': case 'folder': + case 'folders': case 'media': return cameraManager.getStore().getCameraIDs(); diff --git a/src/view/view.ts b/src/view/view.ts index 21dc29c9..376a4e0f 100644 --- a/src/view/view.ts +++ b/src/view/view.ts @@ -126,7 +126,7 @@ export class View { * Determine if a view is a media gallery. */ public isMediaGalleryView(): boolean { - return ['clips', 'folder', 'snapshots', 'recordings'].includes(this.view); + return ['clips', 'folders', 'snapshots', 'recordings'].includes(this.view); } /** @@ -137,11 +137,15 @@ export class View { return this.isViewerView() || this.is('live') || this.is('image'); } + public isAnyFolderView(): boolean { + return ['folder', 'folders'].includes(this.view); + } + /** * Determine if a view is for the media viewer. */ public isViewerView(): boolean { - return ['media', 'clip', 'snapshot', 'recording'].includes(this.view); + return ['folder', 'media', 'clip', 'snapshot', 'recording'].includes(this.view); } public supportsMultipleDisplayModes(): boolean { diff --git a/tests/card-controller/actions/actions-manager.test.ts b/tests/card-controller/actions/actions-manager.test.ts index 88794eb3..2e25fbce 100644 --- a/tests/card-controller/actions/actions-manager.test.ts +++ b/tests/card-controller/actions/actions-manager.test.ts @@ -105,7 +105,17 @@ describe('ActionsManager', () => { { tap_action: { action: 'navigate', - // Folder also uses the media gallery. + // Folders also uses the media viewer. + navigation_path: '4', + }, + }, + ], + [ + 'folders' as const, + { + tap_action: { + action: 'navigate', + // Folders also uses the media gallery. navigation_path: '3', }, }, diff --git a/tests/card-controller/actions/actions/folder.test.ts b/tests/card-controller/actions/actions/folder.test.ts index 18d00f5a..22c90a1d 100644 --- a/tests/card-controller/actions/actions/folder.test.ts +++ b/tests/card-controller/actions/actions/folder.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { FolderAction } from '../../../../src/card-controller/actions/actions/folder'; +import { FoldersViewAction } from '../../../../src/card-controller/actions/actions/folders-view'; import { FolderQuery } from '../../../../src/card-controller/folders/types'; import { FolderViewQuery } from '../../../../src/view/query'; import { createCardAPI, createFolder } from '../../../test-utils'; @@ -7,7 +7,7 @@ import { createCardAPI, createFolder } from '../../../test-utils'; describe('should handle folder action', async () => { it('should handle folder action successfully', async () => { const api = createCardAPI(); - const action = new FolderAction( + const action = new FoldersViewAction( {}, { action: 'fire-dom-event', @@ -20,7 +20,7 @@ describe('should handle folder action', async () => { const query: FolderQuery = { folder, - path: ['path'], + path: [{ ha: { id: 'path' } }], }; vi.mocked(api.getFoldersManager().generateDefaultFolderQuery).mockReturnValue(query); @@ -44,7 +44,7 @@ describe('should handle folder action', async () => { it('should do nothing with non-existent folder', async () => { const api = createCardAPI(); - const action = new FolderAction( + const action = new FoldersViewAction( {}, { action: 'fire-dom-event', @@ -64,7 +64,7 @@ describe('should handle folder action', async () => { it('should do nothing with non-existent default query', async () => { const api = createCardAPI(); - const action = new FolderAction( + const action = new FoldersViewAction( {}, { action: 'fire-dom-event', diff --git a/tests/card-controller/actions/factory.test.ts b/tests/card-controller/actions/factory.test.ts index 35e60ab0..869203d2 100644 --- a/tests/card-controller/actions/factory.test.ts +++ b/tests/card-controller/actions/factory.test.ts @@ -7,7 +7,7 @@ import { DefaultAction } from '../../../src/card-controller/actions/actions/defa import { DisplayModeSelectAction } from '../../../src/card-controller/actions/actions/display-mode-select'; import { DownloadAction } from '../../../src/card-controller/actions/actions/download'; import { ExpandAction } from '../../../src/card-controller/actions/actions/expand'; -import { FolderAction } from '../../../src/card-controller/actions/actions/folder'; +import { FoldersViewAction } from '../../../src/card-controller/actions/actions/folders-view'; import { FullscreenAction } from '../../../src/card-controller/actions/actions/fullscreen'; import { InternalCallbackAction } from '../../../src/card-controller/actions/actions/internal-callback'; import { LogAction } from '../../../src/card-controller/actions/actions/log'; @@ -185,7 +185,8 @@ describe('ActionFactory', () => { }, InternalCallbackAction, ], - [{ advanced_camera_card_action: 'folder' as const }, FolderAction], + [{ advanced_camera_card_action: 'folder' as const }, FoldersViewAction], + [{ advanced_camera_card_action: 'folders' as const }, FoldersViewAction], ])( 'advanced_camera_card_action: $advanced_camera_card_action', (action: Partial, classObject: object) => { diff --git a/tests/components-lib/menu-button-controller.test.ts b/tests/components-lib/menu-button-controller.test.ts index 4b4e3247..70ebf1c1 100644 --- a/tests/components-lib/menu-button-controller.test.ts +++ b/tests/components-lib/menu-button-controller.test.ts @@ -1792,7 +1792,8 @@ describe('MenuButtonController', () => { style: {}, type: 'custom:advanced-camera-card-menu-icon', title: 'Folders', - tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'folder' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'folders' }, + hold_action: { action: 'fire-dom-event', advanced_camera_card_action: 'folder' }, }); }); @@ -1807,7 +1808,7 @@ describe('MenuButtonController', () => { foldersManager, view: createView({ view: 'folder', - query: new FolderViewQuery({ folder, path: [{ id: 'one' }] }), + query: new FolderViewQuery({ folder, path: [{ ha: { id: 'one' } }] }), }), }); @@ -1820,7 +1821,8 @@ describe('MenuButtonController', () => { }, type: 'custom:advanced-camera-card-menu-icon', title: 'Folders', - tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'folder' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'folders' }, + hold_action: { action: 'fire-dom-event', advanced_camera_card_action: 'folder' }, }); }); @@ -1837,7 +1839,7 @@ describe('MenuButtonController', () => { view: 'folder', query: new FolderViewQuery({ folder: selectedFolder, - path: [{ id: 'id' }], + path: [{ ha: { id: 'id' } }], }), }); @@ -1858,18 +1860,28 @@ describe('MenuButtonController', () => { items: [ { enabled: true, + hold_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'folder', + folder: 'folder-0', + }, icon: 'mdi:folder', selected: false, style: {}, tap_action: { action: 'fire-dom-event', - advanced_camera_card_action: 'folder', + advanced_camera_card_action: 'folders', folder: 'folder-0', }, title: 'folder-0', }, { enabled: true, + hold_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'folder', + folder: 'folder-selected', + }, icon: 'mdi:folder', selected: true, style: { @@ -1877,7 +1889,7 @@ describe('MenuButtonController', () => { }, tap_action: { action: 'fire-dom-event', - advanced_camera_card_action: 'folder', + advanced_camera_card_action: 'folders', folder: 'folder-selected', }, title: 'folder-selected', @@ -1909,23 +1921,33 @@ describe('MenuButtonController', () => { { enabled: true, icon: 'mdi:folder', + hold_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'folder', + folder: 'folder-0', + }, selected: false, style: {}, tap_action: { action: 'fire-dom-event', - advanced_camera_card_action: 'folder', + advanced_camera_card_action: 'folders', folder: 'folder-0', }, title: 'folder-0', }, { enabled: true, + hold_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'folder', + folder: 'folder-1', + }, icon: 'mdi:folder', selected: false, style: {}, tap_action: { action: 'fire-dom-event', - advanced_camera_card_action: 'folder', + advanced_camera_card_action: 'folders', folder: 'folder-1', }, title: 'folder-1', diff --git a/tests/utils/action.test.ts b/tests/utils/action.test.ts index 6e4d01bc..17f868a8 100644 --- a/tests/utils/action.test.ts +++ b/tests/utils/action.test.ts @@ -5,7 +5,7 @@ import { ActionConfig } from '../../src/config/schema/actions/types.js'; import { createCameraAction, createDisplayModeAction, - createFolderAction, + createFoldersViewAction, createGeneralAction, createInternalCallbackAction, createLogAction, @@ -64,14 +64,19 @@ describe('createCameraAction', () => { }); describe('createFolderAction', () => { - it('should create folder action', () => { - expect(createFolderAction({ folderID: 'folderID', cardID: 'card_id' })).toEqual({ - action: 'fire-dom-event', - advanced_camera_card_action: 'folder', - card_id: 'card_id', - folder: 'folderID', - }); - }); + it.each([['folder' as const], ['folders' as const]])( + '%s', + (viewName: 'folder' | 'folders') => { + expect( + createFoldersViewAction(viewName, { folderID: 'folderID', cardID: 'card_id' }), + ).toEqual({ + action: 'fire-dom-event', + advanced_camera_card_action: viewName, + card_id: 'card_id', + folder: 'folderID', + }); + }, + ); }); describe('createMediaPlayerAction', () => { diff --git a/tests/view/view-to-cameras.test.ts b/tests/view/view-to-cameras.test.ts index b66aaa02..e4af49b4 100644 --- a/tests/view/view-to-cameras.test.ts +++ b/tests/view/view-to-cameras.test.ts @@ -11,31 +11,34 @@ import { describe('getCameraIDsForViewName', () => { describe('views that are always supported', () => { - it.each([['diagnostics' as const], ['image' as const], ['media' as const]])( - '%s', - (viewName: AdvancedCameraCardView) => { - const cameraManager = createCameraManager(); - vi.mocked(cameraManager.getStore).mockReturnValue( - createStore([ - { - cameraID: 'camera-1', - config: createCameraConfig({ dependencies: { cameras: ['camera-2'] } }), - }, - { cameraID: 'camera-2' }, - ]), - ); + it.each([ + ['diagnostics' as const], + ['folder' as const], + ['folders' as const], + ['image' as const], + ['media' as const], + ])('%s', (viewName: AdvancedCameraCardView) => { + const cameraManager = createCameraManager(); + vi.mocked(cameraManager.getStore).mockReturnValue( + createStore([ + { + cameraID: 'camera-1', + config: createCameraConfig({ dependencies: { cameras: ['camera-2'] } }), + }, + { cameraID: 'camera-2' }, + ]), + ); - expect(getCameraIDsForViewName(cameraManager, viewName)).toEqual( - new Set(['camera-1', 'camera-2']), - ); - expect(getCameraIDsForViewName(cameraManager, viewName, 'camera-1')).toEqual( - new Set(['camera-1', 'camera-2']), - ); - expect(getCameraIDsForViewName(cameraManager, viewName, 'camera-2')).toEqual( - new Set(['camera-1', 'camera-2']), - ); - }, - ); + expect(getCameraIDsForViewName(cameraManager, viewName)).toEqual( + new Set(['camera-1', 'camera-2']), + ); + expect(getCameraIDsForViewName(cameraManager, viewName, 'camera-1')).toEqual( + new Set(['camera-1', 'camera-2']), + ); + expect(getCameraIDsForViewName(cameraManager, viewName, 'camera-2')).toEqual( + new Set(['camera-1', 'camera-2']), + ); + }); }); describe('views that respect dependencies and need a capability', () => { diff --git a/tests/view/view.test.ts b/tests/view/view.test.ts index 4739a74b..bdcbdd74 100644 --- a/tests/view/view.test.ts +++ b/tests/view/view.test.ts @@ -153,56 +153,81 @@ describe('View Basics', () => { expect(view.context).toEqual({}); }); - it('should detect gallery views', () => { - expect(createView({ view: 'clips' }).isMediaGalleryView()).toBeTruthy(); - expect(createView({ view: 'folder' }).isMediaGalleryView()).toBeTruthy(); - expect(createView({ view: 'snapshots' }).isMediaGalleryView()).toBeTruthy(); - expect(createView({ view: 'recordings' }).isMediaGalleryView()).toBeTruthy(); + describe('should detect folder views', () => { + it('should detect folder views', () => { + expect(createView({ view: 'folder' }).isAnyFolderView()).toBeTruthy(); + expect(createView({ view: 'folders' }).isAnyFolderView()).toBeTruthy(); + }); + + it('should not detect folder views', () => { + expect(createView({ view: 'live' }).isAnyFolderView()).toBeFalsy(); + expect(createView({ view: 'clip' }).isAnyFolderView()).toBeFalsy(); + expect(createView({ view: 'clips' }).isAnyFolderView()).toBeFalsy(); + }); }); - it('should not detect gallery view', () => { - expect(createView({ view: 'live' }).isMediaGalleryView()).toBeFalsy(); - expect(createView({ view: 'timeline' }).isMediaGalleryView()).toBeFalsy(); + describe('should detect media views', () => { + it('should detect any media views', () => { + expect(createView({ view: 'clip' }).isAnyMediaView()).toBeTruthy(); + expect(createView({ view: 'snapshot' }).isAnyMediaView()).toBeTruthy(); + expect(createView({ view: 'media' }).isAnyMediaView()).toBeTruthy(); + expect(createView({ view: 'recording' }).isAnyMediaView()).toBeTruthy(); + expect(createView({ view: 'live' }).isAnyMediaView()).toBeTruthy(); + expect(createView({ view: 'image' }).isAnyMediaView()).toBeTruthy(); + expect(createView({ view: 'folder' }).isAnyMediaView()).toBeTruthy(); + }); + + it('should not detect any media view', () => { + expect(createView({ view: 'timeline' }).isAnyMediaView()).toBeFalsy(); + }); }); - it('should detect any media views', () => { - expect(createView({ view: 'clip' }).isAnyMediaView()).toBeTruthy(); - expect(createView({ view: 'snapshot' }).isAnyMediaView()).toBeTruthy(); - expect(createView({ view: 'media' }).isAnyMediaView()).toBeTruthy(); - expect(createView({ view: 'recording' }).isAnyMediaView()).toBeTruthy(); - expect(createView({ view: 'live' }).isAnyMediaView()).toBeTruthy(); - expect(createView({ view: 'image' }).isAnyMediaView()).toBeTruthy(); + describe('should detect gallery views', () => { + it('should detect gallery views', () => { + expect(createView({ view: 'clips' }).isMediaGalleryView()).toBeTruthy(); + expect(createView({ view: 'folders' }).isMediaGalleryView()).toBeTruthy(); + expect(createView({ view: 'snapshots' }).isMediaGalleryView()).toBeTruthy(); + expect(createView({ view: 'recordings' }).isMediaGalleryView()).toBeTruthy(); + }); + + it('should not detect gallery view', () => { + expect(createView({ view: 'live' }).isMediaGalleryView()).toBeFalsy(); + expect(createView({ view: 'timeline' }).isMediaGalleryView()).toBeFalsy(); + }); }); - it('should not detect any media view', () => { - expect(createView({ view: 'timeline' }).isAnyMediaView()).toBeFalsy(); + describe('should detect viewer views', () => { + it('should detect viewer views', () => { + expect(createView({ view: 'clip' }).isViewerView()).toBeTruthy(); + expect(createView({ view: 'snapshot' }).isViewerView()).toBeTruthy(); + expect(createView({ view: 'media' }).isViewerView()).toBeTruthy(); + expect(createView({ view: 'recording' }).isViewerView()).toBeTruthy(); + expect(createView({ view: 'folder' }).isAnyMediaView()).toBeTruthy(); + }); + + it('should not detect viewer views', () => { + expect(createView({ view: 'live' }).isViewerView()).toBeFalsy(); + expect(createView({ view: 'live' }).isViewerView()).toBeFalsy(); + expect(createView({ view: 'timeline' }).isViewerView()).toBeFalsy(); + }); }); - it('should detect viewer views', () => { - expect(createView({ view: 'clip' }).isViewerView()).toBeTruthy(); - expect(createView({ view: 'snapshot' }).isViewerView()).toBeTruthy(); - expect(createView({ view: 'media' }).isViewerView()).toBeTruthy(); - expect(createView({ view: 'recording' }).isViewerView()).toBeTruthy(); - }); + describe('should get default media type', () => { + it('should get default media type', () => { + expect(createView({ view: 'clip' }).getDefaultMediaType()).toBe('clips'); + expect(createView({ view: 'clips' }).getDefaultMediaType()).toBe('clips'); + expect(createView({ view: 'snapshot' }).getDefaultMediaType()).toBe('snapshots'); + expect(createView({ view: 'snapshots' }).getDefaultMediaType()).toBe('snapshots'); + expect(createView({ view: 'recording' }).getDefaultMediaType()).toBe('recordings'); + expect(createView({ view: 'recordings' }).getDefaultMediaType()).toBe( + 'recordings', + ); + }); - it('should not detect viewer views', () => { - expect(createView({ view: 'live' }).isViewerView()).toBeFalsy(); - expect(createView({ view: 'live' }).isViewerView()).toBeFalsy(); - expect(createView({ view: 'timeline' }).isViewerView()).toBeFalsy(); - }); - - it('should get default media type', () => { - expect(createView({ view: 'clip' }).getDefaultMediaType()).toBe('clips'); - expect(createView({ view: 'clips' }).getDefaultMediaType()).toBe('clips'); - expect(createView({ view: 'snapshot' }).getDefaultMediaType()).toBe('snapshots'); - expect(createView({ view: 'snapshots' }).getDefaultMediaType()).toBe('snapshots'); - expect(createView({ view: 'recording' }).getDefaultMediaType()).toBe('recordings'); - expect(createView({ view: 'recordings' }).getDefaultMediaType()).toBe('recordings'); - }); - - it('should not get default media type', () => { - expect(createView({ view: 'live' }).getDefaultMediaType()).toBeNull(); - expect(createView({ view: 'timeline' }).getDefaultMediaType()).toBeNull(); + it('should not get default media type', () => { + expect(createView({ view: 'live' }).getDefaultMediaType()).toBeNull(); + expect(createView({ view: 'timeline' }).getDefaultMediaType()).toBeNull(); + }); }); it('should determine if display mode is grid', () => {