You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PlexMeetsHomeAssistant/www/content-card-example.js

442 lines
13 KiB

4 years ago
class ContentCardExample extends HTMLElement {
data = {}; // placeholder
width = 138;
height = 206;
expandedWidth = 220;
expandedHeight = 324;
movieElems = [];
detailElem = undefined;
renderPage = (hass) => {
const _this = this;
if (this) this.innerHTML = "";
const card = document.createElement("ha-card");
//card.header = this.config.libraryName;
this.content = document.createElement("div");
this.content.style.padding = "16px 16px 100px";
card.appendChild(this.content);
this.appendChild(card);
this.content.innerHTML = "";
var count = 0;
var maxCount = false;
const contentbg = document.createElement("div");
contentbg.className = "contentbg";
this.content.appendChild(contentbg);
this.detailElem = document.createElement("div");
this.detailElem.className = "detail";
this.detailElem.innerHTML = "<h1></h1><span class='detailDesc'></span>";
this.content.appendChild(this.detailElem);
//todo: figure out why timeout is needed here and do it properly
setTimeout(function () {
contentbg.addEventListener("click", function (event) {
_this.hideBackground();
_this.minimizeAll();
});
}, 1);
this.data[this.config.libraryName].some((movieData) => {
if (count < maxCount || !maxCount) {
count++;
this.content.appendChild(
this.getMovieElement(movieData, hass, this.data.server_id)
);
} else {
return true;
}
});
const endElem = document.createElement("div");
endElem.style = "clear:both;";
this.content.appendChild(endElem);
this.calculatePositions();
this.loadCustomStyles();
};
4 years ago
set hass(hass) {
if (!this.content) {
const _this = this;
this.previousPositions = [];
//todo: find a better way to detect resize...
setInterval(() => {
if (_this.movieElems.length > 0) {
if (_this.previousPositions.length === 0) {
for (let i = 0; i < _this.movieElems.length; i++) {
_this.previousPositions[i] = {};
_this.previousPositions[i]["top"] =
_this.movieElems[i].parentElement.offsetTop;
_this.previousPositions[i]["left"] =
_this.movieElems[i].parentElement.offsetLeft;
}
}
for (let i = 0; i < _this.movieElems.length; i++) {
if (
_this.previousPositions[i] &&
_this.movieElems[i].dataset.clicked !== "true" &&
(_this.previousPositions[i]["top"] !==
_this.movieElems[i].parentElement.offsetTop ||
_this.previousPositions[i]["left"] !==
_this.movieElems[i].parentElement.offsetLeft)
) {
_this.renderPage(hass);
_this.previousPositions = [];
}
}
4 years ago
}
}, 100);
this.renderPage(hass);
4 years ago
}
}
//todo: run also on resize
calculatePositions = () => {
const _this = this;
//todo: figure out why loop is needed here and do it properly
const setLeftOffsetsInterval = setInterval(() => {
_this.movieElems = _this.getElementsByClassName("movieElem");
for (let i = 0; i < _this.movieElems.length; i++) {
if (_this.movieElems[i].offsetLeft === 0) {
break;
} else {
clearInterval(setLeftOffsetsInterval);
}
_this.movieElems[i].style.left = _this.movieElems[i].offsetLeft + "px";
_this.movieElems[i].dataset.left = _this.movieElems[i].offsetLeft;
_this.movieElems[i].style.top = _this.movieElems[i].offsetTop + "px";
_this.movieElems[i].dataset.top = _this.movieElems[i].offsetTop;
}
}, 10);
};
minimizeAll = () => {
for (let i = 0; i < this.movieElems.length; i++) {
if (this.movieElems[i].dataset.clicked === "true") {
this.movieElems[i].style.width = this.width + "px";
this.movieElems[i].style.height = this.height + "px";
this.movieElems[i].style["z-index"] = 1;
this.movieElems[i].style.position = "absolute";
this.movieElems[i].style.left = this.movieElems[i].dataset.left + "px";
this.movieElems[i].style.top = this.movieElems[i].dataset.top + "px";
const __this = this;
setTimeout(function () {
__this.movieElems[i].dataset.clicked = false;
}, 500);
}
}
this.hideDetails();
};
hideDetails = () => {
this.detailElem.style.color = "rgba(255,255,255,0)";
this.detailElem.style["z-index"] = "0";
};
showDetails = (data) => {
this.detailElem.children[0].innerHTML = data.title;
this.detailElem.children[1].innerHTML = data.summary;
this.detailElem.style.color = "rgba(255,255,255,1)";
this.detailElem.style["z-index"] = "4";
};
showBackground = () => {
const contentbg = this.getElementsByClassName("contentbg");
contentbg[0].style["z-index"] = 2;
contentbg[0].style["background-color"] = "rgba(0,0,0,0.9)";
};
hideBackground = () => {
const contentbg = this.getElementsByClassName("contentbg");
contentbg[0].style["z-index"] = 1;
contentbg[0].style["background-color"] = "rgba(0,0,0,0)";
};
4 years ago
getMovieElement = (data, hass, server_id) => {
const _this = this;
4 years ago
const thumbURL =
this.plexProtocol +
"://" +
this.config.ip +
4 years ago
":" +
this.config.port +
"/photo/:/transcode?width=" +
this.expandedWidth +
"&height=" +
this.expandedHeight +
"&minSize=1&upscale=1&url=" +
4 years ago
data.thumb +
"&X-Plex-Token=" +
this.config.token;
const container = document.createElement("div");
container.className = "container";
container.style.width = this.width + "px";
container.style.height = this.height + 30 + "px";
4 years ago
const movieElem = document.createElement("div");
movieElem.className = "movieElem";
4 years ago
movieElem.style =
"width:" +
this.width +
"px; height:" +
this.height +
"px; background-image: url('" +
thumbURL +
"'); ";
movieElem.addEventListener("click", function (event) {
console.log(data);
if (this.dataset.clicked === "true") {
_this.hideDetails();
this.style.width = _this.width + "px";
this.style.height = _this.height + "px";
this.style["z-index"] = 1;
this.style.top = this.dataset.top + "px";
this.style.left = this.dataset.left + "px";
const __this = this;
setTimeout(function () {
__this.dataset.clicked = false;
}, 500);
_this.hideBackground();
} else {
_this.minimizeAll();
_this.showDetails(data);
const doc = document.documentElement;
const top =
(window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
_this.showBackground();
this.style.width = _this.expandedWidth + "px";
this.style.height = _this.expandedHeight + "px";
this.style["z-index"] = 3;
this.style.left = "16px";
this.style.top = top + 16 + "px";
this.dataset.clicked = true;
}
});
const playButton = this.getPlayButton();
const interactiveArea = document.createElement("div");
interactiveArea.className = "interactiveArea";
interactiveArea.append(playButton);
movieElem.append(interactiveArea);
playButton.addEventListener("click", function (event) {
event.stopPropagation();
4 years ago
var keyParts = data.key.split("/");
var movieID = keyParts[3];
4 years ago
var command =
"am start -a android.intent.action.VIEW 'plex://server://" +
server_id +
"/com.plexapp.plugins.library/library/metadata/" +
movieID +
"'";
console.log(command);
var entity_id = _this.config.entity_id;
4 years ago
hass.callService("androidtv", "adb_command", {
entity_id,
4 years ago
command,
});
});
const titleElem = document.createElement("div");
titleElem.innerHTML = data.title;
titleElem.className = "titleElem";
titleElem.style["margin-top"] = this.height + "px";
const yearElem = document.createElement("div");
yearElem.innerHTML = data.year;
yearElem.className = "yearElem";
container.appendChild(movieElem);
container.appendChild(titleElem);
container.appendChild(yearElem);
return container;
4 years ago
};
loadCustomStyles = () => {
let style = document.createElement("style");
style.textContent = `
.detailDesc {
}
.detail {
position: absolute;
left: 247px;
width: calc(100% - 267px);
z-index: 4;
transition: 0.5s;
color: rgba(255,255,255,0);
}
.contentbg {
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0);
z-index: 0;
transition: 0.5s;
left: 0;
top: 0;
}
.yearElem {
color:hsla(0,0%,100%,.45);
position: relative;
}
.titleElem {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
position: relative;
}
.movieElem {
margin-bottom:5px;
background-repeat: no-repeat;
background-size: contain;
border-radius: 5px;
transition: 0.5s;
position: absolute;
z-index: 1;
}
.container {
z-index: 1;
float:left;
margin-bottom: 20px;
margin-right: 10px;
transition: 0.5s;
}
.interactiveArea {
position: relative;
width: 100%;
height: 100%;
transition: 0.5s;
display: flex;
align-items: center;
justify-content: center;
}
.interactiveArea:hover {
background: rgba(0,0,0,0.3);
}
button[name="playButton"] {
width: 40px;
height: 40px;
border: 2px solid white;
border-radius: 100%;
margin: auto;
cursor: pointer;
transition: 0.2s;
}
button[name="playButton"]:hover {
background: orange !important;
border: 2px solid orange !important;
}
button[name="playButton"]:focus {
outline: 0;
background: orange !important;
border: 2px solid orange !important;
box-shadow: 0 0 0 3px orange !important;
}
button[name="playButton"]::after {
content: '';
display: inline-block;
position: relative;
top: 1px;
left: 2px;
border-style: solid;
border-width: 6px 0 6px 12px;
border-color: transparent transparent transparent white;
transition: 0.2s;
}
.interactiveArea button[name="playButton"] {
background: rgba(0,0,0,0.0);
border: 2px solid rgba(255,255,255,0.0);
}
.interactiveArea:hover button[name="playButton"] {
background: rgba(0,0,0,0.4);
border: 2px solid rgba(255,255,255,1);
}
.interactiveArea button[name="playButton"]:after {
border-color: transparent transparent transparent rgba(255,255,255,0);
}
.interactiveArea:hover button[name="playButton"]:after {
border-color: transparent transparent transparent rgba(255,255,255,1);
}
button[name="playButton"]:hover:after {
border-color: transparent transparent transparent black !important;
}
button[name="playButton"]:focus:after {
border-color: transparent transparent transparent black !important;
}`;
this.appendChild(style);
};
getPlayButton = () => {
const playButton = document.createElement("button");
playButton.name = "playButton";
return playButton;
};
4 years ago
setConfig(config) {
this.plexProtocol = "http";
if (!config.entity_id) {
throw new Error("You need to define an entity_id");
}
if (!config.token) {
throw new Error("You need to define a token");
4 years ago
}
if (!config.ip) {
throw new Error("You need to define a ip");
4 years ago
}
if (!config.port) {
throw new Error("You need to define a port");
}
if (!config.libraryName) {
throw new Error("You need to define a libraryName");
4 years ago
}
this.config = config;
if (config.protocol) {
this.plexProtocol = config.protocol;
}
//todo: replace this with a proper integration
this.data = JSON.parse(this.getData("/local/plexData.json"));
if (this.data[this.config.libraryName] === undefined) {
throw new Error(
"Library name " + this.config.libraryName + " does not exist."
);
}
4 years ago
}
getData = (url) => {
const xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", url, false); // false for synchronous request
xmlHttp.send(null);
return xmlHttp.responseText;
};
4 years ago
// The height of your card. Home Assistant uses this to automatically
// distribute all cards over the available columns.
getCardSize() {
return 3;
}
}
customElements.define("content-card-example", ContentCardExample);