Initial version of media filter components.
This commit is contained in:
+24
-14
@@ -27,13 +27,13 @@ import { View } from '../view/view.js';
|
|||||||
import { renderProgressIndicator } from './message.js';
|
import { renderProgressIndicator } from './message.js';
|
||||||
import './thumbnail.js';
|
import './thumbnail.js';
|
||||||
import { THUMBNAIL_DETAILS_WIDTH_MIN } from './thumbnail.js';
|
import { THUMBNAIL_DETAILS_WIDTH_MIN } from './thumbnail.js';
|
||||||
import './media-filter';
|
|
||||||
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
||||||
import { MediaQueriesClassifier } from '../view/media-queries-classifier';
|
import { MediaQueriesClassifier } from '../view/media-queries-classifier';
|
||||||
import { EventMediaQueries, RecordingMediaQueries } from '../view/media-queries';
|
import { EventMediaQueries, RecordingMediaQueries } from '../view/media-queries';
|
||||||
import { EventQuery, MediaQuery, RecordingQuery } from '../camera/types';
|
import { EventQuery, MediaQuery, RecordingQuery } from '../camera/types';
|
||||||
import { MediaQueriesResults } from '../view/media-queries-results';
|
import { MediaQueriesResults } from '../view/media-queries-results';
|
||||||
import { errorToConsole } from '../utils/basic';
|
import { errorToConsole } from '../utils/basic';
|
||||||
|
import "./media-filter";
|
||||||
|
|
||||||
const GALLERY_MEDIA_CHUNK_SIZE = 100;
|
const GALLERY_MEDIA_CHUNK_SIZE = 100;
|
||||||
|
|
||||||
@@ -101,15 +101,25 @@ export class FrigateCardGallery extends LitElement {
|
|||||||
return renderProgressIndicator({ cardWideConfig: this.cardWideConfig });
|
return renderProgressIndicator({ cardWideConfig: this.cardWideConfig });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Make this slot choice configuration left/right.
|
||||||
return html`
|
return html`
|
||||||
<frigate-card-gallery-core
|
<frigate-card-surround-basic>
|
||||||
.hass=${this.hass}
|
<frigate-card-media-filter
|
||||||
.view=${this.view}
|
.hass=${this.hass}
|
||||||
.galleryConfig=${this.galleryConfig}
|
.cameras=${this.cameras}
|
||||||
.cameras=${this.cameras}
|
.cameraManager=${this.cameraManager}
|
||||||
.cameraManager=${this.cameraManager}
|
slot="right"
|
||||||
>
|
>
|
||||||
</frigate-card-gallery-core>
|
</frigate-card-media-filter>
|
||||||
|
<frigate-card-gallery-core
|
||||||
|
.hass=${this.hass}
|
||||||
|
.view=${this.view}
|
||||||
|
.galleryConfig=${this.galleryConfig}
|
||||||
|
.cameras=${this.cameras}
|
||||||
|
.cameraManager=${this.cameraManager}
|
||||||
|
>
|
||||||
|
</frigate-card-gallery-core>
|
||||||
|
</frigate-card-surround-basic>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +156,7 @@ export class FrigateCardGalleryCore extends LitElement {
|
|||||||
|
|
||||||
protected _intersectionObserver: IntersectionObserver;
|
protected _intersectionObserver: IntersectionObserver;
|
||||||
protected _resizeObserver: ResizeObserver;
|
protected _resizeObserver: ResizeObserver;
|
||||||
protected _refSentinel: Ref<HTMLElement> = createRef();
|
protected _refLoader: Ref<HTMLElement> = createRef();
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
protected _showExtensionLoader = true;
|
protected _showExtensionLoader = true;
|
||||||
@@ -167,7 +177,7 @@ export class FrigateCardGalleryCore extends LitElement {
|
|||||||
this._resizeObserver.observe(this);
|
this._resizeObserver.observe(this);
|
||||||
|
|
||||||
// Request update in order to ensure the intersection observer reconnects
|
// Request update in order to ensure the intersection observer reconnects
|
||||||
// with the sentinel.
|
// with the loader sentinel.
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +336,7 @@ export class FrigateCardGalleryCore extends LitElement {
|
|||||||
</frigate-card-thumbnail>`,
|
</frigate-card-thumbnail>`,
|
||||||
)}
|
)}
|
||||||
${this._showExtensionLoader
|
${this._showExtensionLoader
|
||||||
? html` <ha-card class="sentinel" ${ref(this._refSentinel)}>
|
? html` <ha-card ${ref(this._refLoader)}>
|
||||||
<span class="dotdotdot"></span>
|
<span class="dotdotdot"></span>
|
||||||
</ha-card>`
|
</ha-card>`
|
||||||
: ''}
|
: ''}
|
||||||
@@ -334,9 +344,9 @@ export class FrigateCardGalleryCore extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public updated(): void {
|
public updated(): void {
|
||||||
if (this._refSentinel.value) {
|
if (this._refLoader.value) {
|
||||||
this._intersectionObserver.disconnect();
|
this._intersectionObserver.disconnect();
|
||||||
this._intersectionObserver.observe(this._refSentinel.value);
|
this._intersectionObserver.observe(this._refLoader.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
|
||||||
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
|
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
||||||
|
import { DateRange } from '../camera/range';
|
||||||
|
import { localize } from '../localize/localize';
|
||||||
|
import mediaFilterStyle from '../scss/media-filter.scss';
|
||||||
|
import { ExtendedHomeAssistant } from '../types';
|
||||||
|
import { dispatchFrigateCardEvent } from '../utils/basic';
|
||||||
|
|
||||||
|
export interface ValueLabel<T> {
|
||||||
|
value?: T;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaFilterCoreSelection {
|
||||||
|
camera?: string[];
|
||||||
|
what?: string[];
|
||||||
|
where?: string[];
|
||||||
|
when?: MediaFilterCoreWhenSelection;
|
||||||
|
favorite?: MediaFilterCoreFavoriteSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilterElement<T> = HTMLElement & {
|
||||||
|
selectedItem?: ValueLabel<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum MediaFilterCoreFavoriteSelection {
|
||||||
|
All = 'all',
|
||||||
|
Favorite = 'favorite',
|
||||||
|
NotFavorite = 'not-favorite',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MediaFilterCoreWhen {
|
||||||
|
All = 'all',
|
||||||
|
Today = 'today',
|
||||||
|
Yesterday = 'yesterday',
|
||||||
|
PastWeek = 'past-week',
|
||||||
|
PastMonth = 'past-month',
|
||||||
|
Custom = 'custom',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MediaFilterCoreWhenSelection {
|
||||||
|
selection: MediaFilterCoreWhen;
|
||||||
|
custom?: DateRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('frigate-card-media-filter-core')
|
||||||
|
export class FrigateCardMediaFilterCore extends LitElement {
|
||||||
|
@property({ attribute: false })
|
||||||
|
public hass?: ExtendedHomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public whenOptions?: ValueLabel<MediaFilterCoreWhenSelection>[];
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public cameraOptions?: ValueLabel<string>[];
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public whatOptions?: ValueLabel<string>[];
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public whereOptions?: ValueLabel<string>[];
|
||||||
|
|
||||||
|
protected _refWhen: Ref<FilterElement<MediaFilterCoreWhenSelection>> = createRef();
|
||||||
|
protected _refCamera: Ref<FilterElement<string>> = createRef();
|
||||||
|
protected _refWhat: Ref<FilterElement<string>> = createRef();
|
||||||
|
protected _refWhere: Ref<FilterElement<string>> = createRef();
|
||||||
|
protected _refFavorite: Ref<FilterElement<MediaFilterCoreFavoriteSelection>> =
|
||||||
|
createRef();
|
||||||
|
|
||||||
|
protected _valueChangedHandler(ev: CustomEvent<{ value: unknown }>): void {
|
||||||
|
// Handler is called on initial load -- skip it.
|
||||||
|
if (!ev.detail.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const values: MediaFilterCoreSelection = {
|
||||||
|
...(this._refWhen.value && {
|
||||||
|
when: this._refWhen.value.selectedItem?.value as MediaFilterCoreWhenSelection,
|
||||||
|
}),
|
||||||
|
...(this._refCamera.value && {
|
||||||
|
camera: this._refCamera.value.selectedItem?.value
|
||||||
|
? [this._refCamera.value.selectedItem?.value]
|
||||||
|
: undefined,
|
||||||
|
}),
|
||||||
|
...(this._refWhat.value && {
|
||||||
|
what: this._refWhat.value.selectedItem?.value
|
||||||
|
? [this._refWhat.value.selectedItem?.value]
|
||||||
|
: undefined,
|
||||||
|
}),
|
||||||
|
...(this._refWhere.value && {
|
||||||
|
where: this._refWhere.value.selectedItem?.value
|
||||||
|
? [this._refWhere.value.selectedItem?.value]
|
||||||
|
: undefined,
|
||||||
|
}),
|
||||||
|
...(this._refFavorite.value && {
|
||||||
|
favorite: this._refFavorite.value.selectedItem?.value as
|
||||||
|
| MediaFilterCoreFavoriteSelection
|
||||||
|
| undefined,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
dispatchFrigateCardEvent(this, 'media-filter-core:change', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master render method.
|
||||||
|
* @returns A rendered template.
|
||||||
|
*/
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
const favoriteOptions: ValueLabel<MediaFilterCoreFavoriteSelection>[] = [
|
||||||
|
{
|
||||||
|
value: MediaFilterCoreFavoriteSelection.All,
|
||||||
|
label: localize('media_filter.all'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MediaFilterCoreFavoriteSelection.Favorite,
|
||||||
|
label: localize('media_filter.favorite'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MediaFilterCoreFavoriteSelection.NotFavorite,
|
||||||
|
label: localize('media_filter.not_favorite'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Time based options are not pre-computed here to ensure relative dates
|
||||||
|
// (e.g. 'today') are always calculated when activated not when rendered.
|
||||||
|
const whenOptions = [
|
||||||
|
{
|
||||||
|
value: { selection: MediaFilterCoreWhen.All },
|
||||||
|
label: localize('media_filter.all'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { selection: MediaFilterCoreWhen.Today },
|
||||||
|
label: localize('media_filter.today'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { selection: MediaFilterCoreWhen.Yesterday },
|
||||||
|
label: localize('media_filter.yesterday'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { selection: MediaFilterCoreWhen.PastWeek },
|
||||||
|
label: localize('media_filter.past_week'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { selection: MediaFilterCoreWhen.PastMonth },
|
||||||
|
label: localize('media_filter.past_month'),
|
||||||
|
},
|
||||||
|
...(this.whenOptions ?? []),
|
||||||
|
];
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-combo-box
|
||||||
|
${ref(this._refWhen)}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${localize('media_filter.when')}
|
||||||
|
.items=${whenOptions}
|
||||||
|
.allowCustomValue=${false}
|
||||||
|
@value-changed=${this._valueChangedHandler.bind(this)}
|
||||||
|
></ha-combo-box>
|
||||||
|
${this.cameraOptions
|
||||||
|
? html` <ha-combo-box
|
||||||
|
${ref(this._refCamera)}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${localize('media_filter.camera')}
|
||||||
|
.items=${this.cameraOptions}
|
||||||
|
.allowCustomValue=${false}
|
||||||
|
@value-changed=${this._valueChangedHandler.bind(this)}
|
||||||
|
></ha-combo-box>`
|
||||||
|
: ''}
|
||||||
|
${this.whatOptions
|
||||||
|
? html` <ha-combo-box
|
||||||
|
${ref(this._refWhat)}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${localize('media_filter.what')}
|
||||||
|
.items=${this.whatOptions}
|
||||||
|
.allowCustomValue=${false}
|
||||||
|
@value-changed=${this._valueChangedHandler.bind(this)}
|
||||||
|
></ha-combo-box>`
|
||||||
|
: ''}
|
||||||
|
${this.whereOptions
|
||||||
|
? html`<ha-combo-box
|
||||||
|
${ref(this._refWhere)}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${localize('media_filter.where')}
|
||||||
|
.items=${this.whereOptions}
|
||||||
|
.allowCustomValue=${false}
|
||||||
|
@value-changed=${this._valueChangedHandler.bind(this)}
|
||||||
|
></ha-combo-box>`
|
||||||
|
: ''}
|
||||||
|
<ha-combo-box
|
||||||
|
${ref(this._refFavorite)}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.label=${localize('media_filter.favorite')}
|
||||||
|
.items=${favoriteOptions}
|
||||||
|
.allowCustomValue=${false}
|
||||||
|
@value-changed=${this._valueChangedHandler.bind(this)}
|
||||||
|
></ha-combo-box>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return unsafeCSS(mediaFilterStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'frigate-card-media-filter-core': FrigateCardMediaFilterCore;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { sub } from 'date-fns';
|
||||||
|
import endOfDay from 'date-fns/endOfDay';
|
||||||
|
import endOfYesterday from 'date-fns/endOfYesterday';
|
||||||
|
import endOfToday from 'date-fns/esm/endOfToday';
|
||||||
|
import startOfToday from 'date-fns/esm/startOfToday';
|
||||||
|
import startOfYesterday from 'date-fns/startOfYesterday';
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from 'lit';
|
||||||
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
|
import { CameraManager } from '../camera/manager';
|
||||||
|
import { DateRange } from '../camera/range';
|
||||||
|
import { CameraConfig, ExtendedHomeAssistant } from '../types';
|
||||||
|
import { getCameraTitle } from '../utils/camera';
|
||||||
|
import {
|
||||||
|
MediaFilterCoreFavoriteSelection,
|
||||||
|
MediaFilterCoreSelection,
|
||||||
|
MediaFilterCoreWhen,
|
||||||
|
MediaFilterCoreWhenSelection,
|
||||||
|
ValueLabel,
|
||||||
|
} from './media-filter-core';
|
||||||
|
import './surround.js';
|
||||||
|
import './timeline-core.js';
|
||||||
|
|
||||||
|
@customElement('frigate-card-media-filter')
|
||||||
|
export class FrigateCardMediaFilter extends LitElement {
|
||||||
|
@property({ attribute: false })
|
||||||
|
public hass?: ExtendedHomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public cameras?: Map<string, CameraConfig>;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public cameraManager?: CameraManager;
|
||||||
|
|
||||||
|
protected _cameraOptions: ValueLabel<string>[] = [];
|
||||||
|
|
||||||
|
protected _convertWhenToDateRange(
|
||||||
|
value?: MediaFilterCoreWhenSelection,
|
||||||
|
): DateRange | null {
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value.selection === MediaFilterCoreWhen.Custom && value.custom) {
|
||||||
|
return value.custom;
|
||||||
|
}
|
||||||
|
const now = new Date();
|
||||||
|
switch (value.selection) {
|
||||||
|
case MediaFilterCoreWhen.Today:
|
||||||
|
return { start: startOfToday(), end: endOfToday() };
|
||||||
|
case MediaFilterCoreWhen.Yesterday:
|
||||||
|
return { start: startOfYesterday(), end: endOfYesterday() };
|
||||||
|
case MediaFilterCoreWhen.PastWeek:
|
||||||
|
return { start: sub(now, { days: 7 }), end: endOfDay(now) };
|
||||||
|
case MediaFilterCoreWhen.PastMonth:
|
||||||
|
return { start: sub(now, { months: 1 }), end: endOfDay(now) };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _convertFavoriteToBoolean(
|
||||||
|
value?: MediaFilterCoreFavoriteSelection,
|
||||||
|
): boolean | null {
|
||||||
|
if (!value || value === MediaFilterCoreFavoriteSelection.All) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value === MediaFilterCoreFavoriteSelection.Favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _mediaFilterHandler(ev: CustomEvent<MediaFilterCoreSelection>): void {
|
||||||
|
const convertedTime = this._convertWhenToDateRange(ev.detail.when);
|
||||||
|
const convertedFavorite = this._convertFavoriteToBoolean(ev.detail.favorite);
|
||||||
|
const details = {
|
||||||
|
...ev.detail,
|
||||||
|
...(convertedTime && { when: convertedTime }),
|
||||||
|
...(convertedFavorite && { favorite: convertedFavorite }),
|
||||||
|
};
|
||||||
|
// TODO: remove.
|
||||||
|
console.debug('Received media filter choices:', details);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
|
if (changedProps.has('cameras') && this.cameras) {
|
||||||
|
this._cameraOptions = Array.from(this.cameras.entries()).map(
|
||||||
|
([cameraID, cameraConfig]) => ({
|
||||||
|
value: cameraID,
|
||||||
|
label: getCameraTitle(this.hass, cameraConfig),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
// TODO Replace with real custom when options
|
||||||
|
// TODO Replace with real custom what options
|
||||||
|
// TODO Replace with real custom where options
|
||||||
|
const whereOptions = [{ value: 'steps', label: 'Front Steps' }];
|
||||||
|
|
||||||
|
const whatOptions = [
|
||||||
|
{ value: 'car', label: 'Car' },
|
||||||
|
{ value: 'person', label: 'Person' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const whenOptions = [
|
||||||
|
{
|
||||||
|
value: {
|
||||||
|
selection: MediaFilterCoreWhen.Custom,
|
||||||
|
custom: { start: startOfToday(), end: endOfToday() },
|
||||||
|
},
|
||||||
|
label: 'December 2021',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return html` <frigate-card-media-filter-core
|
||||||
|
.hass=${this.hass}
|
||||||
|
.whenOptions=${whenOptions}
|
||||||
|
.cameraOptions=${this._cameraOptions}
|
||||||
|
.whatOptions=${whatOptions}
|
||||||
|
.whereOptions=${whereOptions}
|
||||||
|
@frigate-card:media-filter-core:change=${this._mediaFilterHandler.bind(this)}
|
||||||
|
>
|
||||||
|
</frigate-card-media-filter-core>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'frigate-card-media-filter': FrigateCardMediaFilter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -364,6 +364,19 @@
|
|||||||
"start": "Start",
|
"start": "Start",
|
||||||
"seek": "Seek"
|
"seek": "Seek"
|
||||||
},
|
},
|
||||||
|
"media_filter": {
|
||||||
|
"all": "All",
|
||||||
|
"camera": "Camera",
|
||||||
|
"favorite": "Favorite",
|
||||||
|
"not_favorite": "Not Favorite",
|
||||||
|
"past_month": "Past Month",
|
||||||
|
"past_week": "Past Week",
|
||||||
|
"today": "Today",
|
||||||
|
"what": "What",
|
||||||
|
"when": "When",
|
||||||
|
"where": "Where",
|
||||||
|
"yesterday": "Yesterday"
|
||||||
|
},
|
||||||
"recording": {
|
"recording": {
|
||||||
"events": "Events",
|
"events": "Events",
|
||||||
"seek": "Seek"
|
"seek": "Seek"
|
||||||
|
|||||||
@@ -335,6 +335,19 @@
|
|||||||
"start": "Avvia",
|
"start": "Avvia",
|
||||||
"seek": "Cercare"
|
"seek": "Cercare"
|
||||||
},
|
},
|
||||||
|
"media_filter": {
|
||||||
|
"all": "",
|
||||||
|
"camera": "",
|
||||||
|
"favorite": "",
|
||||||
|
"not_favorite": "",
|
||||||
|
"past_month": "",
|
||||||
|
"past_week": "",
|
||||||
|
"today": "",
|
||||||
|
"what": "",
|
||||||
|
"when": "",
|
||||||
|
"where": "",
|
||||||
|
"yesterday": ""
|
||||||
|
},
|
||||||
"recording": {
|
"recording": {
|
||||||
"events": "Eventi",
|
"events": "Eventi",
|
||||||
"seek": "Cercare"
|
"seek": "Cercare"
|
||||||
|
|||||||
@@ -335,6 +335,19 @@
|
|||||||
"start": "Início",
|
"start": "Início",
|
||||||
"seek": "Procurar"
|
"seek": "Procurar"
|
||||||
},
|
},
|
||||||
|
"media_filter": {
|
||||||
|
"all": "",
|
||||||
|
"camera": "",
|
||||||
|
"favorite": "",
|
||||||
|
"not_favorite": "",
|
||||||
|
"past_month": "",
|
||||||
|
"past_week": "",
|
||||||
|
"today": "",
|
||||||
|
"what": "",
|
||||||
|
"when": "",
|
||||||
|
"where": "",
|
||||||
|
"yesterday": ""
|
||||||
|
},
|
||||||
"recording": {
|
"recording": {
|
||||||
"events": "Eventos",
|
"events": "Eventos",
|
||||||
"seek": "Procurar"
|
"seek": "Procurar"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(var(--frigate-card-gallery-columns), minmax(0, 1fr));
|
grid-template-columns: repeat(var(--frigate-card-gallery-columns), minmax(0, 1fr));
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: min-content;
|
||||||
gap: var(--frigate-card-gallery-gap);
|
gap: var(--frigate-card-gallery-gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host ha-card {
|
:host ha-card {
|
||||||
|
min-width: 100%;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
@@ -2,7 +2,14 @@
|
|||||||
// - TODO: ts-prune https://camchenry.com/blog/deleting-dead-code-in-typescript
|
// - TODO: ts-prune https://camchenry.com/blog/deleting-dead-code-in-typescript
|
||||||
// - TODO: getRecordingTitle should use getCameraTitle but need hass.
|
// - TODO: getRecordingTitle should use getCameraTitle but need hass.
|
||||||
// - TODO: Take MediaQueries wrappers out of the camera manager.
|
// - TODO: Take MediaQueries wrappers out of the camera manager.
|
||||||
|
|
||||||
|
// Gallery:
|
||||||
// - TODO: Event gallery show_details default does not work.
|
// - TODO: Event gallery show_details default does not work.
|
||||||
|
// - TODO: Use spinner for gallery loading / cardwideconfig not dotdotdot
|
||||||
|
// - TODO: Handle all TODOs in media filter and media filter core.
|
||||||
|
// - TODO: Make media filter slot choice configurable (gallery.ts).
|
||||||
|
// - TODO: Configurable drawer icons (e.g. filter).
|
||||||
|
// - TODO: Filter panel expands from right can occasionally 'stick' open.
|
||||||
|
|
||||||
// Hard:
|
// Hard:
|
||||||
// - TODO: Implement gallery.
|
// - TODO: Implement gallery.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
// imported by a custom card directly.
|
// imported by a custom card directly.
|
||||||
"globalTags": [
|
"globalTags": [
|
||||||
"ha-card",
|
"ha-card",
|
||||||
|
"ha-combo-box",
|
||||||
"ha-icon",
|
"ha-icon",
|
||||||
"ha-icon-button",
|
"ha-icon-button",
|
||||||
"ha-button-menu",
|
"ha-button-menu",
|
||||||
|
|||||||
Reference in New Issue
Block a user