diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js index 5ee3a43..b4213c0 100644 --- a/dist/plex-meets-homeassistant.js +++ b/dist/plex-meets-homeassistant.js @@ -18701,7 +18701,7 @@ const escapeHtml = (unsafe) => { const CSS_STYLE = { width: 138, - height: 206, + height: 203, expandedWidth: 220, expandedHeight: 324 }; @@ -18954,6 +18954,14 @@ style.textContent = css ` position: relative; background: orange; } + .seasons { + z-index: 4; + position: absolute; + top: ${CSS_STYLE.expandedHeight + 16}px; + width: 100%; + left: 0; + padding: 16px; + } .ratingDetail { background: #ffffff24; padding: 5px 10px; @@ -19069,12 +19077,35 @@ style.textContent = css ` color: hsla(0, 0%, 100%, 0.45); position: relative; } + .seasonTitleElem { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + position: relative; + font-weight: bold; + margin-top: 5px; + } + .seasonEpisodesCount { + } .titleElem { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; position: relative; } + .seasonContainer { + position: relative; + float: left; + margin-right: 16px; + margin-bottom: 15px; + transition: 5s; + } + .seasonElem { + background-repeat: no-repeat; + background-size: contain; + border-radius: 5px; + transition: 0.5s; + } .movieElem { margin-bottom: 5px; background-repeat: no-repeat; @@ -19168,7 +19199,9 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.plexProtocol = 'http'; this.plex = undefined; this.movieElems = []; + this.seasonElemFreshlyLoaded = false; this.detailElem = undefined; + this.seasonsElem = undefined; this.data = {}; this.config = {}; this.requestTimeout = 3000; @@ -19177,9 +19210,9 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.playSupported = false; this.error = ''; this.previousPositions = []; - this.loadInitialData = async (hass) => { + this.loadInitialData = async () => { this.loading = true; - this.renderPage(hass); + this.renderPage(); try { if (this.plex) { const [plexInfo, plexSections] = await Promise.all([this.plex.getServerInfo(), this.plex.getSectionsData()]); @@ -19192,7 +19225,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.error = `Library name ${this.config.libraryName} does not exist.`; } this.loading = false; - this.render(hass); + this.render(); } else { throw Error('Plex not initialized.'); @@ -19201,10 +19234,10 @@ class PlexMeetsHomeAssistant extends HTMLElement { catch (err) { // todo: proper timeout here this.error = `Plex server did not respond.
Details of the error: ${escapeHtml(err.message)}`; - this.renderPage(hass); + this.renderPage(); } }; - this.render = (hass) => { + this.render = () => { this.previousPositions = []; // todo: find a better way to detect resize... setInterval(() => { @@ -19227,13 +19260,13 @@ class PlexMeetsHomeAssistant extends HTMLElement { } } if (renderNeeded) { - this.renderPage(hass); + this.renderPage(); } } }, 100); - this.renderPage(hass); + this.renderPage(); }; - this.renderPage = (hass) => { + this.renderPage = () => { if (this) this.innerHTML = ''; const card = document.createElement('ha-card'); @@ -19264,6 +19297,13 @@ class PlexMeetsHomeAssistant extends HTMLElement { "

"; if (this.playSupported) ; this.content.appendChild(this.detailElem); + this.seasonsElem = document.createElement('div'); + this.seasonsElem.className = 'seasons'; + this.seasonsElem.addEventListener('click', () => { + this.hideBackground(); + this.minimizeAll(); + }); + this.content.appendChild(this.seasonsElem); // todo: figure out why timeout is needed here and do it properly setTimeout(() => { contentbg.addEventListener('click', () => { @@ -19276,7 +19316,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { lodash.forEach(this.data[this.config.libraryName], (movieData) => { if (!this.maxCount || count < this.maxCount) { count += 1; - this.content.appendChild(this.getMovieElement(movieData, hass, this.data.server_id)); + this.content.appendChild(this.getMovieElement(movieData, this.data.server_id)); } else { return true; @@ -19321,6 +19361,20 @@ class PlexMeetsHomeAssistant extends HTMLElement { }, 500); } } + if (this.seasonsElem) { + const doc = document.documentElement; + const top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); + this.seasonsElem.style.top = `${top + 2000}px`; + setTimeout(() => { + if (this.seasonsElem && !this.seasonElemFreshlyLoaded) { + this.seasonsElem.innerHTML = ''; + this.seasonsElem.style.display = 'none'; + // fix for a specific case when user scrolls outside of normal home assistant area for seasons look + const contentbg = this.getElementsByClassName('contentbg'); + contentbg[0].style.height = '100%'; + } + }, 700); + } this.hideDetails(); }; this.hideDetails = () => { @@ -19370,7 +19424,60 @@ class PlexMeetsHomeAssistant extends HTMLElement { }, 200); } if (this.plex) { + this.seasonElemFreshlyLoaded = true; const seasonsData = await this.plex.getLibraryData(data.key.split('/')[3]); + if (this.seasonsElem) { + this.seasonsElem.style.display = 'block'; + this.seasonsElem.innerHTML = ''; + this.seasonsElem.style.transition = `0s`; + this.seasonsElem.style.top = `${top + 2000}px`; + } + lodash.forEach(seasonsData, seasonData => { + if (this.seasonsElem) { + const seasonContainer = document.createElement('div'); + seasonContainer.className = 'seasonContainer'; + seasonContainer.style.width = `${CSS_STYLE.width}px`; + const thumbURL = `${this.plexProtocol}://${this.config.ip}:${this.config.port}/photo/:/transcode?width=${CSS_STYLE.expandedWidth}&height=${CSS_STYLE.expandedHeight}&minSize=1&upscale=1&url=${seasonData.thumb}&X-Plex-Token=${this.config.token}`; + const seasonElem = document.createElement('div'); + seasonElem.className = 'seasonElem'; + seasonElem.style.width = `${CSS_STYLE.width}px`; + seasonElem.style.height = `${CSS_STYLE.height}px`; + seasonElem.style.backgroundImage = `url('${thumbURL}')`; + seasonContainer.append(seasonElem); + const seasonTitleElem = document.createElement('div'); + seasonTitleElem.className = 'seasonTitleElem'; + seasonTitleElem.innerHTML = escapeHtml(seasonData.title); + seasonContainer.append(seasonTitleElem); + const seasonEpisodesCount = document.createElement('div'); + seasonEpisodesCount.className = 'seasonEpisodesCount'; + seasonEpisodesCount.innerHTML = `${escapeHtml(seasonData.leafCount)} episodes`; + seasonContainer.append(seasonEpisodesCount); + seasonContainer.addEventListener('click', event => { + event.stopPropagation(); + (async () => { + if (this.plex) { + console.log(seasonData); + console.log(await this.plex.getLibraryData(seasonData.key.split('/')[3])); + } + })(); + }); + this.seasonsElem.append(seasonContainer); + setTimeout(() => { + this.seasonElemFreshlyLoaded = false; + }, 700); + } + }); + setTimeout(() => { + if (this.seasonsElem) { + this.seasonsElem.style.transition = `0.7s`; + this.seasonsElem.style.top = `${top + CSS_STYLE.expandedHeight + 16}px`; + const requiredBodyHeight = parseInt(this.seasonsElem.style.top.replace('px', ''), 10) + this.seasonsElem.scrollHeight; + const contentbg = this.getElementsByClassName('contentbg'); + if (requiredBodyHeight > contentbg[0].scrollHeight) { + contentbg[0].style.height = `${requiredBodyHeight}px`; + } + } + }, 200); console.log(seasonsData); } }; @@ -19384,7 +19491,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { contentbg[0].style.zIndex = '1'; contentbg[0].style.backgroundColor = 'rgba(0,0,0,0)'; }; - this.getMovieElement = (data, hass, serverID) => { + this.getMovieElement = (data, serverID) => { const thumbURL = `${this.plexProtocol}://${this.config.ip}:${this.config.port}/photo/:/transcode?width=${CSS_STYLE.expandedWidth}&height=${CSS_STYLE.expandedHeight}&minSize=1&upscale=1&url=${data.thumb}&X-Plex-Token=${this.config.token}`; const container = document.createElement('div'); container.className = 'container'; @@ -19441,7 +19548,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { console.log(command); // eslint-disable-next-line @typescript-eslint/camelcase const { entity_id } = this.config; - hass.callService('androidtv', 'adb_command', { + this.hass.callService('androidtv', 'adb_command', { // eslint-disable-next-line @typescript-eslint/camelcase entity_id, command @@ -19505,7 +19612,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { hass.states[this.config.entity_id].attributes.adb_response !== undefined; this.error = ''; if (!this.loading) { - this.loadInitialData(hass); + this.loadInitialData(); } } } diff --git a/src/const.ts b/src/const.ts index f524e49..b75c13e 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,6 +1,6 @@ const CSS_STYLE = { width: 138, - height: 206, + height: 203, expandedWidth: 220, expandedHeight: 324 }; diff --git a/src/modules/style.ts b/src/modules/style.ts index 488038b..80c56b6 100644 --- a/src/modules/style.ts +++ b/src/modules/style.ts @@ -14,6 +14,14 @@ style.textContent = css` position: relative; background: orange; } + .seasons { + z-index: 4; + position: absolute; + top: ${CSS_STYLE.expandedHeight + 16}px; + width: 100%; + left: 0; + padding: 16px; + } .ratingDetail { background: #ffffff24; padding: 5px 10px; @@ -129,12 +137,35 @@ style.textContent = css` color: hsla(0, 0%, 100%, 0.45); position: relative; } + .seasonTitleElem { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + position: relative; + font-weight: bold; + margin-top: 5px; + } + .seasonEpisodesCount { + } .titleElem { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; position: relative; } + .seasonContainer { + position: relative; + float: left; + margin-right: 16px; + margin-bottom: 15px; + transition: 5s; + } + .seasonElem { + background-repeat: no-repeat; + background-size: contain; + border-radius: 5px; + transition: 0.5s; + } .movieElem { margin-bottom: 5px; background-repeat: no-repeat; diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts index 2bc1563..4c21202 100644 --- a/src/plex-meets-homeassistant.ts +++ b/src/plex-meets-homeassistant.ts @@ -14,8 +14,12 @@ class PlexMeetsHomeAssistant extends HTMLElement { movieElems: any = []; + seasonElemFreshlyLoaded = false; + detailElem: HTMLElement | undefined = undefined; + seasonsElem: HTMLElement | undefined = undefined; + data: Record = {}; config: Record = {}; @@ -43,14 +47,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.error = ''; if (!this.loading) { - this.loadInitialData(hass); + this.loadInitialData(); } } } - loadInitialData = async (hass: HomeAssistant): Promise => { + loadInitialData = async (): Promise => { this.loading = true; - this.renderPage(hass); + this.renderPage(); try { if (this.plex) { const [plexInfo, plexSections] = await Promise.all([this.plex.getServerInfo(), this.plex.getSectionsData()]); @@ -65,18 +69,18 @@ class PlexMeetsHomeAssistant extends HTMLElement { } this.loading = false; - this.render(hass); + this.render(); } else { throw Error('Plex not initialized.'); } } catch (err) { // todo: proper timeout here this.error = `Plex server did not respond.
Details of the error: ${escapeHtml(err.message)}`; - this.renderPage(hass); + this.renderPage(); } }; - render = (hass: HomeAssistant): void => { + render = (): void => { this.previousPositions = []; // todo: find a better way to detect resize... @@ -102,15 +106,15 @@ class PlexMeetsHomeAssistant extends HTMLElement { } } if (renderNeeded) { - this.renderPage(hass); + this.renderPage(); } } }, 100); - this.renderPage(hass); + this.renderPage(); }; - renderPage = (hass: HomeAssistant): void => { + renderPage = (): void => { if (this) this.innerHTML = ''; const card = document.createElement('ha-card'); // card.header = this.config.libraryName; @@ -149,6 +153,14 @@ class PlexMeetsHomeAssistant extends HTMLElement { this.content.appendChild(this.detailElem); + this.seasonsElem = document.createElement('div'); + this.seasonsElem.className = 'seasons'; + this.seasonsElem.addEventListener('click', () => { + this.hideBackground(); + this.minimizeAll(); + }); + this.content.appendChild(this.seasonsElem); + // todo: figure out why timeout is needed here and do it properly setTimeout(() => { contentbg.addEventListener('click', () => { @@ -161,7 +173,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { _.forEach(this.data[this.config.libraryName], (movieData: Record) => { if (!this.maxCount || count < this.maxCount) { count += 1; - this.content.appendChild(this.getMovieElement(movieData, hass, this.data.server_id)); + this.content.appendChild(this.getMovieElement(movieData, this.data.server_id)); } else { return true; } @@ -207,6 +219,21 @@ class PlexMeetsHomeAssistant extends HTMLElement { }, 500); } } + if (this.seasonsElem) { + const doc = document.documentElement; + const top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); + this.seasonsElem.style.top = `${top + 2000}px`; + setTimeout(() => { + if (this.seasonsElem && !this.seasonElemFreshlyLoaded) { + this.seasonsElem.innerHTML = ''; + this.seasonsElem.style.display = 'none'; + // fix for a specific case when user scrolls outside of normal home assistant area for seasons look + const contentbg = this.getElementsByClassName('contentbg'); + (contentbg[0] as HTMLElement).style.height = '100%'; + } + }, 700); + } + this.hideDetails(); }; @@ -265,7 +292,72 @@ class PlexMeetsHomeAssistant extends HTMLElement { }, 200); } if (this.plex) { + this.seasonElemFreshlyLoaded = true; const seasonsData = await this.plex.getLibraryData(data.key.split('/')[3]); + if (this.seasonsElem) { + this.seasonsElem.style.display = 'block'; + this.seasonsElem.innerHTML = ''; + this.seasonsElem.style.transition = `0s`; + this.seasonsElem.style.top = `${top + 2000}px`; + } + + _.forEach(seasonsData, seasonData => { + if (this.seasonsElem) { + const seasonContainer = document.createElement('div'); + seasonContainer.className = 'seasonContainer'; + seasonContainer.style.width = `${CSS_STYLE.width}px`; + const thumbURL = `${this.plexProtocol}://${this.config.ip}:${this.config.port}/photo/:/transcode?width=${CSS_STYLE.expandedWidth}&height=${CSS_STYLE.expandedHeight}&minSize=1&upscale=1&url=${seasonData.thumb}&X-Plex-Token=${this.config.token}`; + + const seasonElem = document.createElement('div'); + seasonElem.className = 'seasonElem'; + seasonElem.style.width = `${CSS_STYLE.width}px`; + seasonElem.style.height = `${CSS_STYLE.height}px`; + seasonElem.style.backgroundImage = `url('${thumbURL}')`; + + seasonContainer.append(seasonElem); + + const seasonTitleElem = document.createElement('div'); + seasonTitleElem.className = 'seasonTitleElem'; + seasonTitleElem.innerHTML = escapeHtml(seasonData.title); + seasonContainer.append(seasonTitleElem); + + const seasonEpisodesCount = document.createElement('div'); + seasonEpisodesCount.className = 'seasonEpisodesCount'; + seasonEpisodesCount.innerHTML = `${escapeHtml(seasonData.leafCount)} episodes`; + seasonContainer.append(seasonEpisodesCount); + + seasonContainer.addEventListener('click', event => { + event.stopPropagation(); + (async (): Promise => { + if (this.plex) { + console.log(seasonData); + console.log(await this.plex.getLibraryData(seasonData.key.split('/')[3])); + } + })(); + }); + + this.seasonsElem.append(seasonContainer); + setTimeout(() => { + this.seasonElemFreshlyLoaded = false; + }, 700); + } + }); + + setTimeout(() => { + if (this.seasonsElem) { + this.seasonsElem.style.transition = `0.7s`; + this.seasonsElem.style.top = `${top + CSS_STYLE.expandedHeight + 16}px`; + + const requiredBodyHeight = + parseInt(this.seasonsElem.style.top.replace('px', ''), 10) + this.seasonsElem.scrollHeight; + const contentbg = this.getElementsByClassName('contentbg'); + + if (requiredBodyHeight > (contentbg[0] as HTMLElement).scrollHeight) { + (contentbg[0] as HTMLElement).style.height = `${requiredBodyHeight}px`; + } + } + }, 200); + console.log(seasonsData); } }; @@ -282,7 +374,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { (contentbg[0] as HTMLElement).style.backgroundColor = 'rgba(0,0,0,0)'; }; - getMovieElement = (data: any, hass: HomeAssistant, serverID: string): HTMLDivElement => { + getMovieElement = (data: any, serverID: string): HTMLDivElement => { const thumbURL = `${this.plexProtocol}://${this.config.ip}:${this.config.port}/photo/:/transcode?width=${CSS_STYLE.expandedWidth}&height=${CSS_STYLE.expandedHeight}&minSize=1&upscale=1&url=${data.thumb}&X-Plex-Token=${this.config.token}`; const container = document.createElement('div'); @@ -348,7 +440,7 @@ class PlexMeetsHomeAssistant extends HTMLElement { console.log(command); // eslint-disable-next-line @typescript-eslint/camelcase const { entity_id } = this.config; - hass.callService('androidtv', 'adb_command', { + this.hass.callService('androidtv', 'adb_command', { // eslint-disable-next-line @typescript-eslint/camelcase entity_id, command