Add drawer component and support side thumbnails.
This commit is contained in:
+3
-2
@@ -24,16 +24,17 @@
|
|||||||
"@types/bluebird": "^3.5.36",
|
"@types/bluebird": "^3.5.36",
|
||||||
"component-emitter": "^1.3.0",
|
"component-emitter": "^1.3.0",
|
||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
"custom-card-helpers": "^1.8.0",
|
"custom-card-helpers": "^1.9.0",
|
||||||
"embla-carousel": "^6.1.1",
|
"embla-carousel": "^6.1.1",
|
||||||
"home-assistant-js-websocket": "^6.1.1",
|
"home-assistant-js-websocket": "^6.1.1",
|
||||||
"lit": "^2.2.1",
|
|
||||||
"keycharm": "^0.4.0",
|
"keycharm": "^0.4.0",
|
||||||
|
"lit": "^2.2.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"propagating-hammerjs": "^2.0.1",
|
"propagating-hammerjs": "^2.0.1",
|
||||||
"quick-lru": "^6.1.0",
|
"quick-lru": "^6.1.0",
|
||||||
"screenfull": "^6.0.1",
|
"screenfull": "^6.0.1",
|
||||||
|
"side-drawer": "^3.0.0",
|
||||||
"ts-toolbelt": "^9.6.0",
|
"ts-toolbelt": "^9.6.0",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"vis-data": "^7.1.3",
|
"vis-data": "^7.1.3",
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { CSSResultGroup, LitElement, unsafeCSS, PropertyValues } from 'lit';
|
import { CSSResultGroup, LitElement, unsafeCSS, PropertyValues } from 'lit';
|
||||||
|
import { property } from 'lit/decorators.js';
|
||||||
|
|
||||||
import EmblaCarousel, {
|
import EmblaCarousel, {
|
||||||
EmblaCarouselType,
|
EmblaCarouselType,
|
||||||
EmblaOptionsType,
|
EmblaOptionsType,
|
||||||
@@ -15,6 +17,9 @@ export interface CarouselSelect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FrigateCardCarousel extends LitElement {
|
export class FrigateCardCarousel extends LitElement {
|
||||||
|
@property({ attribute: true, reflect: true })
|
||||||
|
public direction: 'vertical' | 'horizontal' = 'horizontal';
|
||||||
|
|
||||||
protected _carousel?: EmblaCarouselType;
|
protected _carousel?: EmblaCarouselType;
|
||||||
protected _plugins: Record<string, EmblaPluginType> = {};
|
protected _plugins: Record<string, EmblaPluginType> = {};
|
||||||
|
|
||||||
@@ -98,7 +103,13 @@ export class FrigateCardCarousel extends LitElement {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
this._carousel = EmblaCarousel(carouselNode, this._getOptions(), plugins);
|
this._carousel = EmblaCarousel(
|
||||||
|
carouselNode,
|
||||||
|
{
|
||||||
|
axis: this.direction == 'horizontal' ? 'x' : 'y',
|
||||||
|
...this._getOptions()
|
||||||
|
},
|
||||||
|
plugins);
|
||||||
this._carousel.on('init', () => dispatchFrigateCardEvent(this, 'carousel:init'));
|
this._carousel.on('init', () => dispatchFrigateCardEvent(this, 'carousel:init'));
|
||||||
this._carousel.on('select', () => {
|
this._carousel.on('select', () => {
|
||||||
const selected = this.carouselSelected();
|
const selected = this.carouselSelected();
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
unsafeCSS,
|
||||||
|
PropertyValues,
|
||||||
|
} from 'lit';
|
||||||
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
|
import { createRef, ref, Ref } from 'lit/directives/ref.js';
|
||||||
|
import 'side-drawer';
|
||||||
|
|
||||||
|
import drawerStyle from '../scss/drawer.scss';
|
||||||
|
import drawerInjectStyle from '../scss/drawer-inject.scss';
|
||||||
|
|
||||||
|
@customElement('frigate-card-drawer')
|
||||||
|
export class FrigateCardDrawer extends LitElement {
|
||||||
|
@property({ attribute: true, reflect: true })
|
||||||
|
public location: 'left' | 'right' = 'left';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the timeline configuration.
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean, reflect: true, attribute: true })
|
||||||
|
set open(open: boolean) {
|
||||||
|
if (this._drawerRef.value) {
|
||||||
|
const old = this._drawerRef.value.open;
|
||||||
|
this._drawerRef.value.open = open;
|
||||||
|
this.requestUpdate('open', old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get open(): boolean {
|
||||||
|
return this._drawerRef.value?.open ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _drawerRef: Ref<HTMLElement & { open: boolean }> = createRef();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on the first update.
|
||||||
|
* @param changedProps The changed properties.
|
||||||
|
*/
|
||||||
|
protected firstUpdated(changedProps: PropertyValues): void {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
|
// The `side-drawer` component (and the material drawer for that matter)
|
||||||
|
// only do fixed drawers (i.e. a drawer for the whole viewport). Hackily
|
||||||
|
// override the style to customize the drawer to be absolute within the div.
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.innerHTML = drawerInjectStyle;
|
||||||
|
this._drawerRef.value?.shadowRoot?.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html` <side-drawer location="${this.location}" ${ref(this._drawerRef)}>
|
||||||
|
<slot></slot>
|
||||||
|
</side-drawer>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return unsafeCSS(drawerStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import { BrowseMediaUtil } from '../browse-media-util.js';
|
import { BrowseMediaUtil } from '../browse-media-util.js';
|
||||||
import { CSSResultGroup, TemplateResult, html, unsafeCSS } from 'lit';
|
import { CSSResultGroup, TemplateResult, html, unsafeCSS, PropertyValues } from 'lit';
|
||||||
import { EmblaOptionsType } from 'embla-carousel';
|
import { EmblaOptionsType } from 'embla-carousel';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
|
|
||||||
import type { FrigateBrowseMediaSource, ThumbnailsControlConfig } from '../types.js';
|
import type { FrigateBrowseMediaSource, ThumbnailsControlConfig } from '../types.js';
|
||||||
import { FrigateCardCarousel } from './carousel.js';
|
import { FrigateCardCarousel } from './carousel.js';
|
||||||
import { dispatchFrigateCardEvent, stopEventFromActivatingCardWideActions } from '../common.js';
|
import {
|
||||||
|
dispatchFrigateCardEvent,
|
||||||
|
stopEventFromActivatingCardWideActions,
|
||||||
|
} from '../common.js';
|
||||||
|
|
||||||
import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss';
|
import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss';
|
||||||
|
|
||||||
@@ -18,7 +21,7 @@ export interface ThumbnailCarouselTap {
|
|||||||
@customElement('frigate-card-thumbnail-carousel')
|
@customElement('frigate-card-thumbnail-carousel')
|
||||||
export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
|
export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
protected target?: FrigateBrowseMediaSource;
|
public target?: FrigateBrowseMediaSource;
|
||||||
|
|
||||||
protected _tapSelected?;
|
protected _tapSelected?;
|
||||||
|
|
||||||
@@ -90,6 +93,17 @@ export class FrigateCardThumbnailCarousel extends FrigateCardCarousel {
|
|||||||
return slides;
|
return slides;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The updated lifecycle callback for this element.
|
||||||
|
* @param changedProperties The properties that were changed in this render.
|
||||||
|
*/
|
||||||
|
updated(changedProperties: PropertyValues): void {
|
||||||
|
if (this._carousel && changedProperties.has('target')) {
|
||||||
|
this._destroyCarousel();
|
||||||
|
}
|
||||||
|
super.updated(changedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a given thumbnail.
|
* Render a given thumbnail.
|
||||||
* @param mediaToRender The media item to render.
|
* @param mediaToRender The media item to render.
|
||||||
|
|||||||
+14
-1
@@ -23,11 +23,19 @@ img,video {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
-khtml-user-select: none;
|
-khtml-user-select: none;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
:host([direction="vertical"]) .embla__container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
:host([direction="horizontal"]) .embla__container {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
.embla__viewport {
|
.embla__viewport {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -50,9 +58,14 @@ img,video {
|
|||||||
.embla__slide {
|
.embla__slide {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-right: 5px;
|
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
:host([direction="vertical"]) .embla__slide {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
:host([direction="horizontal"]) .embla__slide {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
.embla__slide img,video {
|
.embla__slide img,video {
|
||||||
// Letterbox media. <frigate-card-ha-hls-player> has similar added directly in
|
// Letterbox media. <frigate-card-ha-hls-player> has similar added directly in
|
||||||
// its element.
|
// its element.
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
:host {
|
||||||
|
// Drawer width sizes to contents.
|
||||||
|
width: unset;
|
||||||
|
};
|
||||||
|
|
||||||
|
#d, #fs {
|
||||||
|
// Override width/height to be 100% instead of 100v[wh].
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
// Position absolutely.
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([location=right]) #d {
|
||||||
|
// Position to the right.
|
||||||
|
left: unset;
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
};
|
||||||
|
|
||||||
|
:host([location=right][open]) #d {
|
||||||
|
transform: none;
|
||||||
|
box-shadow: 0px 0px 25px 0px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#fs {
|
||||||
|
width: 100%;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifs {
|
||||||
|
// Override width/height to be 100% instead of 100v[wh].
|
||||||
|
height: 100%;
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
side-drawer {
|
||||||
|
background-color: var(--card-background-color);
|
||||||
|
}
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
display: block;
|
display: block;
|
||||||
background-color: var(--card-background-color);
|
background-color: var(--card-background-color);
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
|
|
||||||
|
// So that absolute sidedrawer is relative to this host.
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.timeline {
|
div.timeline {
|
||||||
|
|||||||
Reference in New Issue
Block a user