From 0d7c20b55052858a40c85ef8938d6e3f4539cd9a Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sat, 16 Oct 2021 21:02:07 -0700 Subject: [PATCH] Add 'image' view. --- package.json | 3 ++- rollup.config.js | 2 ++ src/card.ts | 27 +++++++++++++++++++++++++-- src/components/image.ts | 26 ++++++++++++++++++++++++++ src/declarations.d.ts | 1 + src/editor.ts | 13 +++++++++++++ src/images/README.md | 18 ++++++++++++++++++ src/images/frigate-bird-in-sky.jpg | Bin 0 -> 8434 bytes src/localize/languages/en.json | 3 ++- src/scss/image.scss | 5 +++++ src/types.ts | 3 +++ 11 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 src/components/image.ts create mode 100644 src/images/README.md create mode 100644 src/images/frigate-bird-in-sky.jpg create mode 100644 src/scss/image.scss diff --git a/package.json b/package.json index 0610e5ae..b8eb4e09 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,13 @@ "home-assistant-js-websocket": "^5.11.1", "lit": "^2.0.2", "screenfull": "^5.1.0", - "zod": "^3.9.8" + "zod": "^3.10.0-beta.1" }, "devDependencies": { "@babel/core": "^7.15.8", "@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/plugin-proposal-decorators": "^7.15.8", + "@rollup/plugin-image": "^2.1.1", "@rollup/plugin-json": "^4.1.0", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", diff --git a/rollup.config.js b/rollup.config.js index 30276be4..fcc97cab 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,6 +6,7 @@ import { terser } from 'rollup-plugin-terser'; import serve from 'rollup-plugin-serve'; import json from '@rollup/plugin-json'; import styles from 'rollup-plugin-styles'; +import image from '@rollup/plugin-image'; const dev = process.env.ROLLUP_WATCH; @@ -29,6 +30,7 @@ const plugins = [ includePaths: ['./node_modules/'], }, }), + image(), nodeResolve({}), commonjs(), typescript(), diff --git a/src/card.ts b/src/card.ts index 73e4d38d..27cc6ce1 100644 --- a/src/card.ts +++ b/src/card.ts @@ -31,6 +31,7 @@ import type { } from './types'; import { CARD_VERSION, REPO_URL } from './const'; +import { FrigateCardElements } from './components/elements'; import { FrigateCardMenu, MENU_HEIGHT } from './components/menu'; import { View } from './view'; import { homeAssistantWSRequest, shouldUpdateBasedOnHass } from './common'; @@ -40,6 +41,7 @@ import { renderMessage, renderProgressIndicator } from './components/message'; import './editor'; import './components/elements'; import './components/gallery'; +import './components/image'; import './components/live'; import './components/menu'; import './components/message'; @@ -48,7 +50,6 @@ import './patches/ha-camera-stream'; import './patches/ha-hls-player'; import cardStyle from './scss/card.scss'; -import { FrigateCardElements } from './components/elements'; const MEDIA_HEIGHT_CUTOFF = 50; const MEDIA_WIDTH_CUTOFF = MEDIA_HEIGHT_CUTOFF; @@ -112,7 +113,7 @@ export class FrigateCard extends LitElement { // A small cache to avoid needing to create a new list of entities every time // a hass update arrives. - protected _entitiesToMonitor: string[] | null = null; + protected _entitiesToMonitor: string[] = []; // Information about the most recently loaded media item. protected _mediaInfo: MediaLoadInfo | null = null; @@ -197,6 +198,15 @@ export class FrigateCard extends LitElement { emphasize: this._view.is('snapshots'), }); } + if (this.config.menu_buttons?.image ?? false) { + buttons.push({ + type: 'internal-menu-icon', + card_action: 'image', + title: localize('menu.image'), + icon: 'mdi:image', + emphasize: this._view.is('image'), + }); + } if ((this.config.menu_buttons?.frigate_ui ?? true) && this.config.frigate_url) { buttons.push({ type: 'internal-menu-icon', @@ -357,6 +367,7 @@ export class FrigateCard extends LitElement { case 'frigate': this._changeView(); break; + case 'image': case 'live': case 'clips': case 'snapshots': @@ -625,10 +636,22 @@ export class FrigateCard extends LitElement { const liveClasses = { hidden: this.config.live_preload && this._view.view != 'live', }; + const imageClasses = { + hidden: this.config.live_preload && this._view.view != 'image', + }; return html`
${this._message ? renderMessage(this._message) : ``} + ${!this._message && this._view.is('image') + ? html` + ` + : ``} ${!this._message && this._view.isGalleryView() ? html` { + dispatchMediaLoadEvent(this, e); + }} + >`; + } + + static get styles(): CSSResultGroup { + return unsafeCSS(imageStyle); + } +} diff --git a/src/declarations.d.ts b/src/declarations.d.ts index d5cf927a..59c79d0c 100644 --- a/src/declarations.d.ts +++ b/src/declarations.d.ts @@ -1 +1,2 @@ declare module '*.scss'; +declare module '*.jpg'; diff --git a/src/editor.ts b/src/editor.ts index b2a2b615..76e6fd9d 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -102,6 +102,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor snapshots: localize('menu.snapshots'), clip: localize('menu.clip'), snapshot: localize('menu.snapshot'), + image: localize('menu.image'), }; const menuModes = { @@ -382,6 +383,18 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor >
+ + + +
Z@34Z;KR8IOh8ajOhimWLPEpGT;JU1|9kv-4g}yZ5!fO( zOaO2Rz_Ucn<;W`;te@-$Ig+o`lH%T=R*qP8kV3xivK_N1e`zeLQ4t)kZHJ~7mSCC8?p~Hpd|s$ z9eGgZSXpl&N8*h?Q-BXzO4gf(!G1%MaL zMMYBqusCO)*d>RQ;fKC+B?1{s1P_{O$p!3q9b~v}fTDZ27mrAzvX2F^|0K%*Ic~o4 zZlr>Z_B}Z`32d(zjeM@TI1T?tjHp`Pjk$Fien&*Y!i^fUcN+P)WcJKk=R&8EPa2D% z&DY*kvna-HTh3v|PIeEc=~}h+zpRNFqDD*G^P2spsndko*JpL|?Gdr1Wzri#hnpwV z+i3HBC#p(wW2~%n?aB-7;?V$%0Qd+Nf$BPfg`QRzK>}0@*$N9_0X-=U4G=Ow859*^ zi}7dfh_t2IC4A;Nciwu`prL`}OXY#ZseC=9nC)8BuPAiZCo#>8YCu;gu{xHF3Ge|p z0N*{=T7n!?grC5i41R;7ywx`FVe3!mQEG)^FP)xfD-6XLwN+Spn-+d(_6SmWjmo*M zri<7}-7$`^jJXqXH^tp&SzV01zIpLiNr@_ErQp^d;1OX(J3O-y%&=ekdF8UQ>#c2F zIsL8DYdN;dbcV^(^rCnmqni!{viRY2nZJ02U_g%lFKd1-5&%dZ2P&EizySgPKs9rc zdE;vtbkuo380>P#NFcT2692&j!(?>sxxTru+rfj_mAW+)A7rFEW)HmF5W=|g^U-+nDg@Ra|xTvq7og@j*yR_)Sv<-5C@^! z1%MJi&aC)FwSuKTuOl}a-$PTEIYueJsOWmDlNo2`xV~ER$z!k*`dI6)m}&}O7ncGk5r{Xx!Qbhe#{;NP z1OhZCH+c~}d@zJEfIG;P$5E?g_dx&!oOv~QhOxJpNShG|2R%L8YHF!NnI)&k4VY2QHT6a<^UXhd=esX< zRDLKQh{!wKTp=Q1y*r_O3#GE!6?SRC50&X#u+Qiqj`iKYgE>}~bS-UJZhP|Ovy2YO zs_hTi6*0z^)q|3cY)TMG#I?mpJmpPr!?5$K@?t(73^p5QUPM9wI0?-Q02*)sPB3x? z4-YS*e4nh!i8N=_KACC(Cjee1k%gmQN{QBsW3!XUI_}t8%{qf*k!;@vw-i*$KKfwj z1*Q@XoP*EqI{mz3PL%Z`U`jVqMBYU_|JZ6vn3q#;cIEBg#`hO<{(vv+A5Lc<7(DIm zK8rOQDCyk2H$mA_my+_l_GONH%!4!ZiN+c8;zA9K`^6vGw(Ij(hW%N`DrPJ#|9}dS z{WjP4;@;v)YiI29C;v{I^(+%3YB-VgDHVN(PTJ&!5_MIKeEcJa1^VOY z*#Xr;Op}t-LW~W1>%pdJ>eee)_eDms^KnPqJn2Ty|RyG$&2dO-Y_ySxmls;aXd@f)oe^2 zAG?d4&JTVQ8qj1DG%z0tLZwkO4fx z&@9YV`X4_nd_?*Jsl=_}aGvJ^J?*D7XWb!KxnGRDpSPdVQp(MGaL6~gPi4PCYaYI+ zfq^u55+KJbmgb`>VP*td4m*3Wy4QcQP+m`wBHH~^%5Kf8exduyoXQaHZkgi2T>lTH z5kJ0kL?;MsgO2&Bkyng%FoA0w_ zs!kOgSkVR{t3QLMQwgzUPcN>wRedbHmxu*C4iFYN+Nj;@RUhO8d-QuQjRn0?wodwaBi z?Ug^4b)w}2yQBKS+@Y^uN+WCwMYg_Gn5iuHRA5Hb*ku@*3 zC017YYxjr$cxRF}G6+Ptlz5V4kW10fD(&S5S3xP4gjoMF`{)q zmkT<<(!znSfu<&5rA%{d^e7PusrSh&3ebeggO&$DD5TNDHyPuGR0rn}_jY9#%}zME z`8HN+Xn4tPS{2$_H{+O@qwk8htF9Wo4!F;HweItT9pUNK6`VdCmKo9deMsd}8}GH| z1XFHbS9KMQ!JPbs-_l2_Y!B3zcYppob+f)L*@|*n#18opotc!g0>Uy)e^vX;UviNz?=k4 z3DBD#jQTpjcRw<5nooezY8N=CC_vSYE7B243Bio7CJvZO;e7+ZZSTYG!J5b?{`Iah>dUwCK;_HudkO}8&O%cnH$@yn&r|#8~ z=dQY1WegHh%N}`Y1Pv-YN%Q#hlu3l|xJ4ihM=A^*oyhe^S8`R`HU)=jW_ zsu%N52X;U4P^}^C$YqPFQRq8iLq0X(!(l(u-u68I%xM7&p$hnKy@3DM3OxJ>2bmVx z!thj=sIoCmyaFDsbF!$OP&~PgmEl-+sO+RkUx{D;9_+xVZ*TA5KYnfZm)%DC&qrw7 z^_1G3HrrFgsh5=I6gs4%1U6>MM{-xQ5cHZp)SB4ZEmXsSh)KqkZ|dw-$m| zCcPHzOx-CFeNSR;i+}SpQH#EO?eW;9`EeWtHMjTsjmE37mvk?5%^V{90XE2*ifDxM z+*0}69`B=VDUn)?b@5G&w3&51X=dPZ-}PE(xANu{D=x|ww3^sjUXJ+{v`H-6Ol)8b*lfUFydA-tL_T+l!n$8K$ zwXMHbS_of1Pr?^xdp^1g)~7clhyV*=B@z`%0?y6|g+HfHLmYu30QxD_w53u0AGiW_ zJMNpg$z*!j9axs{nIl`rlNAyccF zJ2~X#mE^$TzV(mtyD{qfq5|wq?i*cWP9F-a-9MwY<23h~+_W>%G*-&i$TmKSVuiV0 zc|}{s9jx!al7??MdWirne=%tDQAD(;7lm-!z0u zZNh}$qrNT~vgHsi>w1+8U2$UhmENJ|Czo!WMxtgtX~+Ze3&c-hjGzOK**!Ewnp1|? zM)*d|+~2Nk(t_Vm)w;K6Qnw_}S0=YVyH#N9j$Kt$dznbL5RpyV{f!xBvmJJJtYqm~ zt_ID}yJ2?jp^!$cPwB$M6xlKUiSzeU_61LK7Q1~JEJn7dbnV-l7m1oU$N~ShDp0cm zV3slvI|VQfbwk9XX%o`&>wwe20DOE+h_1MIu3sIU`~>Hggc-@Exg#p>IiMK3m6rmv zymD!Vvuuyik4)vGN8AQV?QYiI{>r-tqpMz``?$GF>-(FP#jdTZ?WdpEON*@vQjXWp ze6Z5oD#5GpUxsic}?I`3O;VqdL(+cB0f)%|FMV=K&PdB_c|nI2``X)~%t z@v(sOuGx3`X6`5IlKaTheUtavPBz$5F=$27u}9}Ca0O`$s^ zq9dYNMn)aov!yFuBj~OtR-a$8K$Psn_SwBT-|}J%KbY&+HuLyeg=0dMTn+(MKq>q< zokw4Zk!siXAOx}5zWF*6-;TC)_P!oy3yw2%EMo80>pE*}TT#DKUG&CoeY$?+$jFQ90AW0+u}lu!&r8TQB#ljalx zP&=-h0aFi!dZvbVTywhDC8HFzex7X#SZ#kMCF#<(`JhU1>96<*-486Os@lL6Chhbm z`>vbWrtD>sG5gW(AsaP`@OOrQ-10I}<<;tvdNfz_&?qXRba`FHC?;Yg zU-6Dc^C_)9V{{8%ih`a9Pc9j2Kce?5XMy?j$7I3fjz1tX5@IbReqop;6f;oVKr>W~ zA-*C7B}qAeLDyg)d6(OTQ3b9eXsBT{Kq{bfkm6jz9>Hcqel*>!Pbq0b&(y{*oEgb- zUpp(fFq0NS_c`u#=(1}XEA05zc!koOcE+6a>*xao-;LTTX9mb0qmpn&Hr2z%k&=GK zE_ctDBs>)^yshCnuJQI)@`cGcO8(ZN&Bf)1TAODGBdOeyIpMcHbjphj>gxBMSXyaK zXCYr!#q1rFotRC>Q7cZGNO>MGi7lgSY)5L&5Wa0}J&n})`t>OW_s$cQ`M|TRz^|%q zbNzGoWrOV>Th5nQLA*(Ic?ImGs90QR_DpweDZ%{8JTgmmG^2~&XBC|n+7%^BQ`b;M zG;@~??ru?NyDy!4HG<(m%-f$A6Oj4V;QU#0fuL>S$UlIvZW(ssj4Op@7NzzZzw>bL z_;I7a{-<8X{jZJpLyb6zBR(WdElBT96pP`oP^I3@5G9U`Q`9S zSSMBO4}s*AJ|d(K8O1y+Zynz;wAZ>hedt>B%woh<{2ayf0H!TUF;?C7$zRXo=ORo@_hOtsH_ zSte=Uwx52A<|E!8vby!|-6)jo4LD6hNJbUW>h*gp;mZH|bZ%a>ori*#>!BJ`X@yO6 z3AQzRl$h>e9;E!NVs3bC^+&r{Owk{p*j0PUbIQK2ME^vGiIn(U^P8ADhLyk~}n=qD#YA>&~|d0d(u46NVguo*PKqDkn) zSC})pJzosqIZD28I*Lvn$P(+9#|J~woESu#HIl#Xr&cY#)wdN1P-Is5y#3u5%E(Ce zP7U(Xc>POTYx8G$$?rTb{HL?wCyqP$xxBL)82^rc6d>vUpAj2)w%6n5UnkW$qAVefv=B@@?5R16!_?uR#^1o> z47N2cAzZnHIVi^1Lz$=_Vl6L)c3UO8)EekP!o zIlbjuH+1m04f}Tc(>FJ1Tfq_`>D}*TPbym~tG-YAaC(oerU%dEp@r)Fo&aWerq}wK zZi=hqt3Id3tZ$1o^Y5lH?TXzC&umvrQp8`D`E+ONNz%Vv%97zlB%z_8BtdN^#HQ4( z#btB2#|zZfx~_2An8fD{Z}E#vj;NOi0LC3FZYHD51700c8B0cnR_G=6;-CHH#MEUk zmNQO|Aw4GTJBO9G)YE>wW{A&G0&85mrNzE{H7R+Ib9wy9H>ZRb5~o6o0}r&-{UM}s#UO&(L?y(&UQgwEt8&% zgg@Zab={Y&S56fYbxt=O$=Oa;9iBt|aYLSZuiy-RC0PVy6Jc-z7l-OGC-IXUWY)_D zqR{~MT#1TC{R63O#O%8&suB?2qu%4={}5LM6BEB!FLiCoHaF{wBU5dw!nw>S;;L5* zYID9S`g%+*lUC?&Lw8LRT{;0D~7Mr;Z z&D1eJW=M*OU&Kbh*TqQPujwIeFEefu)BS5SBOYc(qb&8KNI_%)kB$}A0D zb@XpZZzjORp*X792?_EfB*P@J<%qXV^3{>V@fZoE`|t{YXy!V>6IJbHR}y;3u>@)R z&am|FysH6}C(&0YpA5UQ=&d3$#btXGrP{6-1)b4Zn(gWyQ&IDdKTn)fk{h{hTzR=S zzo$d~w?{$jU*lzj)uc(zUYZKO4QwDT>6?N^WC5|FKNaBw39i0}x%VHCy5mxsjMaF`Vj`1ONZ1JmsTSgIq z4aJ0tO{&YR; z;!55#&y%e?(fv%qh1v3=B1j74w+E7k_=_}UAiy$GZCmybll{-&0^I8I1Wxk8abVQK zxl?$`ka#R54|8|SyQt_~d9nx;y;ggA=2Z?F=u2d!Xuc1u=-1;~f2fC0OaIt-a7Qpg z<4z;|3X5;dqR-~mXju3IE6YwRgQC8`hwo0xez+Iae#Y9ho?QMIap%dTKcRL*|KcRu zw0FjH$kR1{youf#eL2)Hh@E@Z-o(u#rd@0f^zivO|e|6?Vz9L}H}E=1hPY z8}+iP{SJYkGBrQB^z5;-PfzP96T8hXLwLg_LPFp2cG~zYCQ2OP+h*|sI{isR101{@ z9Ps>?-Te>kIq$4&cPt_)4Y}Nu_{frIZwT0TaI@wtXr=KZ#dGrrZ?*6^!`}EVD$?b^O{^M#vEb}iq9~5uvG^DTU)LnP&=}KBO=13L}fGA1ydqQDXnz&7E_)A4uQ zbQo&9Zd4VE&nfbshq~76`SSJWPLJk?>pREgdHE$|XgK2=-<+Qv%Ia0Lsn*pudFigo zmT%ZFIeU1IqjU9UP}8G^qU|ov3vAtx)us8X*JjvTYqg>$JF@dbR`;x8JYy0jFN^k- zh7Ltoy&-mf4xWY7CUOH+m`em4;YYx@cwOsEZ2vD%*@6u?4C%fG){)y^JU~Yn(x)`Z z4MieAOeR^Fz`KB=Ct@Ywb4LTT-JHVd}26Lvn7 zQFd6)dT^j6sntW!H@v0Q?~~oK(Gg}UBevA3t>pI7*xk3MqGtyvTbB8~_}yY^uimCW z+`n&qSn1=X_;l#RIsuL3F{hdVVJ@mwJ@k;I|C$~KNrytfjt3XFY+eo>ck#)o$|;cc zi35dEvW^>rZxq@-8+y)LkJ)@2DeoqaH1~%1y*9rqI-vBKT#1!;c`;gEq*5$Po*GLT w2oAk?fyUA2&oPx1;{~=<8f(-eOf^Djxg7~E;yy|a+^UHp=CVME1pd7KKd-sc*8l(j literal 0 HcmV?d00001 diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index a37348ae..15a6cddf 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -43,7 +43,8 @@ "clip": "Latest Clip", "snapshot": "Latest Snapshot", "frigate_ui": "Frigate User Interface", - "fullscreen": "Fullscreen" + "fullscreen": "Fullscreen", + "image": "Static Image" }, "control": { "nextprev": "Media Next & Previous Controls", diff --git a/src/scss/image.scss b/src/scss/image.scss new file mode 100644 index 00000000..7c220cdf --- /dev/null +++ b/src/scss/image.scss @@ -0,0 +1,5 @@ +img { + max-width: 100%; + display: block; + margin: auto; +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 54e76324..741ea4f1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,6 +26,7 @@ export const FRIGATE_CARD_VIEWS = [ 'clips', 'snapshot', 'snapshots', + 'image' ] as const; export type FrigateCardView = typeof FRIGATE_CARD_VIEWS[number]; @@ -263,6 +264,7 @@ export const frigateCardConfigSchema = z.object({ .default(180), live_provider: z.enum(LIVE_PROVIDERS).default('frigate'), live_preload: z.boolean().default(false), + image: z.string().optional(), webrtc: z .object({ entity: z.string().optional(), @@ -280,6 +282,7 @@ export const frigateCardConfigSchema = z.object({ live: z.boolean().default(true), clips: z.boolean().default(true), snapshots: z.boolean().default(true), + image: z.boolean().default(false), frigate_ui: z.boolean().default(true), fullscreen: z.boolean().default(true), })