feat: Allow control of where selected item is in grid (#1979)

- Closes: #1946
This commit is contained in:
Dermot Duffy
2025-03-23 14:49:47 -07:00
committed by GitHub
parent 8512df1720
commit f140f6a653
14 changed files with 226 additions and 32 deletions
+43 -5
View File
@@ -34,13 +34,21 @@ export interface MediaGridConstructorOptions {
displayConfig?: ViewDisplayConfig;
}
export interface ExtendedMasonry extends Masonry {
// Expose the items array to allow for custom ordering (used for the
// `grid_selected_position` parameter).
items: {
element: MediaGridChild;
}[];
}
export class MediaGridController {
protected _host: HTMLElement;
protected _selected: GridID | null;
protected _mediaLoadedInfoMap: Map<GridID, MediaLoadedInfo> = new Map();
protected _gridContents: MediaGridContents = new Map();
protected _masonry: Masonry | null = null;
protected _masonry: ExtendedMasonry | null = null;
protected _displayConfig: ViewDisplayConfig | null = null;
protected _hostWidth: number;
protected _idAttribute: string;
@@ -128,6 +136,34 @@ export class MediaGridController {
return this._selected;
}
protected _sortItemsInGrid(): void {
const existingItems = this._masonry?.items;
const selectedItem = existingItems?.find(
(item) => item.element.getAttribute(this._idAttribute) === this._selected,
);
// If `grid_selected_position` is set to 'first' or 'last', move the
// selected item to the start or end of the list respectively.
if (
!!this._displayConfig?.grid_selected_position &&
['first', 'last'].includes(this._displayConfig.grid_selected_position) &&
existingItems &&
selectedItem &&
this._masonry
) {
// Implementation note: With the latest version of the Masonry library
// (4.2.2) using the prepended() and appended() methods in quick sucession
// causes the layout to not show the newly added items. Instead, access
// the items in place and swap them around.
const otherItems = existingItems?.filter((item) => item !== selectedItem);
const newItems =
this._displayConfig.grid_selected_position === 'first'
? [selectedItem, ...otherItems]
: [...otherItems, selectedItem];
this._masonry.items = newItems;
}
}
public selectCell(id: GridID) {
if (this._selected === id) {
return;
@@ -141,11 +177,12 @@ export class MediaGridController {
dispatchExistingMediaLoadedInfoAsEvent(this._host, mediaLoadedInfo);
}
this._sortItemsInGrid();
this._updateSelectedStylesOnElements();
// Sizes may change when an element is selected, so re-do the layout (must
// come after the call to _updateStylesOnElements in order to ensure the
// right styles are applied first).
// Sizes and positions may change when an element is selected, so re-do the
// layout (must come after the call to _updateStylesOnElements in order to
// ensure the right styles are applied first).
this._throttledLayout();
}
@@ -203,6 +240,7 @@ export class MediaGridController {
this._cellResizeObserver.observe(child);
}
this._sortItemsInGrid();
this._updateSelectedStylesOnElements();
this._setColumnSizeStyles();
}
@@ -275,7 +313,7 @@ export class MediaGridController {
percentPosition: true,
transitionDuration: '0.2s',
gutter: MEDIA_GRID_HORIZONTAL_GUTTER_WIDTH,
});
}) as ExtendedMasonry;
this._masonry.addItems?.([...this._gridContents.values()]);
this._throttledLayout();
}
+3
View File
@@ -3,9 +3,12 @@ import { z } from 'zod';
export const viewDisplayModeSchema = z.enum(['single', 'grid']);
export type ViewDisplayMode = z.infer<typeof viewDisplayModeSchema>;
const gridSelectedPositionSchema = z.enum(['default', 'first', 'last']);
export const viewDisplaySchema = z
.object({
mode: viewDisplayModeSchema.optional(),
grid_selected_position: gridSelectedPositionSchema.optional(),
grid_selected_width_factor: z.number().min(0).optional(),
grid_max_columns: z.number().min(0).optional(),
grid_columns: z.number().min(0).optional(),
+4
View File
@@ -192,6 +192,8 @@ export const CONF_MEDIA_VIEWER_DISPLAY_GRID_COLUMNS =
`${CONF_MEDIA_VIEWER}.display.grid_columns` as const;
export const CONF_MEDIA_VIEWER_DISPLAY_GRID_MAX_COLUMNS =
`${CONF_MEDIA_VIEWER}.display.grid_max_columns` as const;
export const CONF_MEDIA_VIEWER_DISPLAY_GRID_SELECTED_POSITION =
`${CONF_MEDIA_VIEWER}.display.grid_selected_position` as const;
export const CONF_MEDIA_VIEWER_DISPLAY_GRID_SELECTED_WIDTH_FACTOR =
`${CONF_MEDIA_VIEWER}.display.grid_selected_width_factor` as const;
export const CONF_MEDIA_VIEWER_DRAGGABLE = `${CONF_MEDIA_VIEWER}.draggable` as const;
@@ -294,6 +296,8 @@ export const CONF_LIVE_DISPLAY_GRID_COLUMNS =
`${CONF_LIVE}.display.grid_columns` as const;
export const CONF_LIVE_DISPLAY_GRID_MAX_COLUMNS =
`${CONF_LIVE}.display.grid_max_columns` as const;
export const CONF_LIVE_DISPLAY_GRID_SELECTED_POSITION =
`${CONF_LIVE}.display.grid_selected_position` as const;
export const CONF_LIVE_DISPLAY_GRID_SELECTED_WIDTH_FACTOR =
`${CONF_LIVE}.display.grid_selected_width_factor` as const;
export const CONF_LIVE_DRAGGABLE = `${CONF_LIVE}.draggable` as const;
+28 -4
View File
@@ -126,6 +126,7 @@ import {
CONF_LIVE_CONTROLS_TIMELINE_WINDOW_SECONDS,
CONF_LIVE_DISPLAY_GRID_COLUMNS,
CONF_LIVE_DISPLAY_GRID_MAX_COLUMNS,
CONF_LIVE_DISPLAY_GRID_SELECTED_POSITION,
CONF_LIVE_DISPLAY_GRID_SELECTED_WIDTH_FACTOR,
CONF_LIVE_DISPLAY_MODE,
CONF_LIVE_DRAGGABLE,
@@ -167,6 +168,7 @@ import {
CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_WINDOW_SECONDS,
CONF_MEDIA_VIEWER_DISPLAY_GRID_COLUMNS,
CONF_MEDIA_VIEWER_DISPLAY_GRID_MAX_COLUMNS,
CONF_MEDIA_VIEWER_DISPLAY_GRID_SELECTED_POSITION,
CONF_MEDIA_VIEWER_DISPLAY_GRID_SELECTED_WIDTH_FACTOR,
CONF_MEDIA_VIEWER_DISPLAY_MODE,
CONF_MEDIA_VIEWER_DRAGGABLE,
@@ -649,6 +651,22 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
{ value: 'grid', label: localize('display_modes.grid') },
];
protected _gridSelectPositions: EditorSelectOption[] = [
{ value: '', label: '' },
{
value: 'default',
label: localize('config.common.display.grid_selected_positions.default'),
},
{
value: 'first',
label: localize('config.common.display.grid_selected_positions.first'),
},
{
value: 'last',
label: localize('config.common.display.grid_selected_positions.last'),
},
];
protected _castMethods: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'standard', label: localize('config.cameras.cast.methods.standard') },
@@ -1651,14 +1669,11 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
protected _renderViewDisplay(
domain: string,
configPathMode: string,
configPathSelectedPosition: string,
configPathSelectedWidthFactor: string,
configPathColumns: string,
configPathMaxColumns: string,
): TemplateResult | void {
// grid_select_width_factor: z.number().min(0).optional(),
// grid_max_columns: z.number().min(0).optional(),
// grid_columns: z.number().min(0).optional(),
return this._putInSubmenu(
domain,
true,
@@ -1668,6 +1683,13 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
${this._renderOptionSelector(configPathMode, this._displayModes, {
label: localize('config.common.display.mode'),
})}
${this._renderOptionSelector(
configPathSelectedPosition,
this._gridSelectPositions,
{
label: localize('config.common.display.grid_selected_position'),
},
)}
${this._renderNumberInput(configPathSelectedWidthFactor, {
min: 0,
label: localize('config.common.display.grid_selected_width_factor'),
@@ -2711,6 +2733,7 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
${this._renderViewDisplay(
MENU_LIVE_DISPLAY,
CONF_LIVE_DISPLAY_MODE,
CONF_LIVE_DISPLAY_GRID_SELECTED_POSITION,
CONF_LIVE_DISPLAY_GRID_SELECTED_WIDTH_FACTOR,
CONF_LIVE_DISPLAY_GRID_COLUMNS,
CONF_LIVE_DISPLAY_GRID_MAX_COLUMNS,
@@ -2898,6 +2921,7 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
${this._renderViewDisplay(
MENU_MEDIA_VIEWER_DISPLAY,
CONF_MEDIA_VIEWER_DISPLAY_MODE,
CONF_MEDIA_VIEWER_DISPLAY_GRID_SELECTED_POSITION,
CONF_MEDIA_VIEWER_DISPLAY_GRID_SELECTED_WIDTH_FACTOR,
CONF_MEDIA_VIEWER_DISPLAY_GRID_COLUMNS,
CONF_MEDIA_VIEWER_DISPLAY_GRID_MAX_COLUMNS,
+6
View File
@@ -259,6 +259,12 @@
"editor_label": "Visualització",
"grid_columns": "Nombre exacte de columnes de la graella",
"grid_max_columns": "Nombre màxim de columnes de la graella",
"grid_selected_position": "",
"grid_selected_positions": {
"default": "",
"first": "",
"last": ""
},
"grid_selected_width_factor": "Augmenta l'amplada del suport multimèdia seleccionat en aquest factor",
"mode": "Mode"
},
+11 -5
View File
@@ -237,8 +237,8 @@
"timeline": {
"editor_label": "Mini Timeline",
"format": {
"editor_label": "Time & date format",
"24h": "Use 24-hour clock"
"24h": "Use 24-hour clock",
"editor_label": "Time & date format"
},
"mode": "Mode",
"modes": {
@@ -259,6 +259,12 @@
"editor_label": "Display",
"grid_columns": "Exact number of grid columns",
"grid_max_columns": "Maximum number of grid columns",
"grid_selected_position": "Position of selected media in grid",
"grid_selected_positions": {
"default": "Selected item in default position",
"first": "Selected item is first in grid",
"last": "Selected item is last in grid"
},
"grid_selected_width_factor": "Increase selected media width by this factor",
"mode": "Mode"
},
@@ -465,8 +471,8 @@
},
"remote_control": {
"entities": {
"editor_label": "Remote Control Entities",
"camera": "Input Select entity to control camera"
"camera": "Input Select entity to control camera",
"editor_label": "Remote Control Entities"
}
},
"status_bar": {
@@ -528,8 +534,8 @@
"theme": {
"themes": {
"dark": "Dark",
"ha": "Home Assistant",
"editor_label": "Themes",
"ha": "Home Assistant",
"light": "Light",
"traditional": "Traditional"
}
+6
View File
@@ -259,6 +259,12 @@
"editor_label": "Affichage",
"grid_columns": "Nombre de colonnes de la grille",
"grid_max_columns": "Nombre maximum de colonnes de la grille",
"grid_selected_position": "",
"grid_selected_positions": {
"default": "",
"first": "",
"last": ""
},
"grid_selected_width_factor": "Augmenter la largeur du média sélectionnée par ce facteur",
"mode": "Mode"
},
+6
View File
@@ -259,6 +259,12 @@
"editor_label": "",
"grid_columns": "",
"grid_max_columns": "",
"grid_selected_position": "",
"grid_selected_positions": {
"default": "",
"first": "",
"last": ""
},
"grid_selected_width_factor": "",
"mode": ""
},
+6
View File
@@ -259,6 +259,12 @@
"editor_label": "",
"grid_columns": "",
"grid_max_columns": "",
"grid_selected_position": "",
"grid_selected_positions": {
"default": "",
"first": "",
"last": ""
},
"grid_selected_width_factor": "",
"mode": ""
},
+6
View File
@@ -259,6 +259,12 @@
"editor_label": "",
"grid_columns": "",
"grid_max_columns": "",
"grid_selected_position": "",
"grid_selected_positions": {
"default": "",
"first": "",
"last": ""
},
"grid_selected_width_factor": "",
"mode": ""
},