feat: Add folder view to view folder media (#2077)

- Closes: #2068

Note: Renames existing `folder` view to `folders` [experimental
functionality]
This commit is contained in:
Dermot Duffy
2025-05-31 17:13:39 -07:00
committed by GitHub
parent 1901475065
commit 956a2e523c
30 changed files with 246 additions and 132 deletions
@@ -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<FolderActionConfig> {
export class FoldersViewAction extends AdvancedCameraCardAction<FoldersViewActionConfig> {
public async execute(api: CardActionsAPI): Promise<void> {
await super.execute(api);
@@ -19,7 +19,8 @@ export class FolderAction extends AdvancedCameraCardAction<FolderActionConfig> {
await api.getViewManager().setViewByParametersWithExistingQuery({
params: {
view: 'folder',
// Supports both 'folder' and 'folders' views.
view: this._action.advanced_camera_card_action,
query: new FolderViewQuery(query),
},
});
+3 -2
View File
@@ -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 */
@@ -120,6 +120,7 @@ export class ViewQueryExecutor {
viewModifiers.push(...(await executeMediaQuery(mediaType)));
break;
case 'folder':
case 'folders':
viewModifiers.push(...(await executeFolderQuery()));
break;
}
+6 -5
View File
@@ -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() : {},
};
}
+4 -4
View File
@@ -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 {
>
</advanced-camera-card-image>`
: ``}
${!this.hide && view?.isMediaGalleryView() && !view.is('folder')
${!this.hide && view?.isMediaGalleryView() && !view.is('folders')
? html` <advanced-camera-card-media-gallery
.hass=${this.hass}
.viewManagerEpoch=${this.viewManagerEpoch}
@@ -200,7 +200,7 @@ export class AdvancedCameraCardViews extends LitElement {
>
</advanced-camera-card-diagnostics>`
: ``}
${!this.hide && view?.is('folder')
${!this.hide && view?.is('folders')
? html` <advanced-camera-card-folder-gallery
.hass=${this.hass}
.viewManagerEpoch=${this.viewManagerEpoch}
@@ -1,10 +0,0 @@
import { z } from 'zod';
import { advancedCameraCardCustomActionsBaseSchema } from './base';
export const folderActionConfigSchema = advancedCameraCardCustomActionsBaseSchema.extend(
{
advanced_camera_card_action: z.literal('folder'),
folder: z.string().optional(),
},
);
export type FolderActionConfig = z.infer<typeof folderActionConfigSchema>;
@@ -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<typeof foldersViewActionConfigSchema>;
+3 -3
View File
@@ -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[],
],
+2 -2
View File
@@ -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,
+1
View File
@@ -17,6 +17,7 @@ export const VIEWS_USER_SPECIFIED = [
'clip',
'clips',
'folder',
'folders',
'snapshot',
'snapshots',
'recording',
+1
View File
@@ -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') },
+1
View File
@@ -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",
+2 -1
View File
@@ -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",
+1
View File
@@ -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",
+1
View File
@@ -589,6 +589,7 @@
"clips": "Galleria delle clip",
"current": "Vista corrente",
"folder": "",
"folders": "",
"image": "Immagine statica",
"live": "Dal vivo",
"recording": "",
+1
View File
@@ -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",
+1
View File
@@ -589,6 +589,7 @@
"clips": "Galeria de clipes",
"current": "Visualização atual",
"folder": "",
"folders": "",
"image": "Imagem estática",
"live": "Visualização ao vivo",
"recording": "",
+10 -7
View File
@@ -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<AdvancedCameraCardUserSpecifiedView, 'folder'>,
action: Exclude<AdvancedCameraCardUserSpecifiedView, 'folder' | 'folders'>,
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 }),
};
+1
View File
@@ -19,6 +19,7 @@ export const getCameraIDsForViewName = (
case 'diagnostics':
case 'image':
case 'folder':
case 'folders':
case 'media':
return cameraManager.getStore().getCameraIDs();
+6 -2
View File
@@ -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 {