Complete overhaul
This commit is contained in:
102
src/layout.js
Normal file
102
src/layout.js
Normal file
@@ -0,0 +1,102 @@
|
||||
function defaultLayout(cards, cols, config) {
|
||||
function shortestCol() {
|
||||
// Find the shortest column or the first one less than min_height
|
||||
let i = 0;
|
||||
for(let j=0; j<cols.length; j++){
|
||||
if(cols[j].length < config.min_height)
|
||||
return j;
|
||||
if(cols[j].length < cols[i].length)
|
||||
i = j;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
cards.forEach((c) => {
|
||||
if(!c) return; // Gap. Skip
|
||||
const col = cols[shortestCol()];
|
||||
col.appendChild(c);
|
||||
col.length += c.getCardSize ? c.getCardSize() : 1;
|
||||
});
|
||||
}
|
||||
|
||||
function horizontalLayout(cards, cols, config) {
|
||||
let i = 0;
|
||||
cards.forEach((c) => {
|
||||
i += 1;
|
||||
if(!c) return; // Gap. Skip
|
||||
const col = cols[(i-1)%cols.length];
|
||||
col.appendChild(c);
|
||||
col.length += c.getCardSize ? c.getCardSize() : 1;
|
||||
})
|
||||
}
|
||||
|
||||
function verticalLayout(cards, cols, config) {
|
||||
let i = 0;
|
||||
cards.forEach((c) => {
|
||||
if(!c) { // Gap. Skip to next column
|
||||
i += 1;
|
||||
return;
|
||||
}
|
||||
const col = cols[(i)%cols.length];
|
||||
col.appendChild(c);
|
||||
col.length += c.getCardSize ? c.getCardSize() : 1;
|
||||
})
|
||||
}
|
||||
|
||||
export function buildLayout(cards, width, config) {
|
||||
|
||||
const parseValue = (val) => {
|
||||
// Accepts e.g. `5` , `5px` or `50%`.
|
||||
if(typeof(val) === "string")
|
||||
if(val.endsWith("%"))
|
||||
return Math.floor(width*parseInt(val)/100);
|
||||
return parseInt(val)
|
||||
}
|
||||
|
||||
let colnum = 0;
|
||||
if(typeof(config.column_width) === "object") {
|
||||
// If many widths are given, keep adding collumns as long as there is any
|
||||
// horizontal space left
|
||||
let calcWidth = width;
|
||||
while(calcWidth > 0) {
|
||||
// If there are not enough values, use the last one
|
||||
let w = config.column_width[colnum];
|
||||
if(w === undefined) w = config.column_width.slice(-1)[0];
|
||||
|
||||
calcWidth -= parseValue(w);
|
||||
colnum += 1;
|
||||
}
|
||||
|
||||
colnum = Math.max(colnum-1, 1);
|
||||
|
||||
} else {
|
||||
colnum = Math.floor(width / parseValue(config.column_width));
|
||||
}
|
||||
colnum = Math.max(colnum, config.min_columns);
|
||||
colnum = Math.min(colnum, config.max_columns);
|
||||
|
||||
let cols = [];
|
||||
for (let i = 0; i < colnum; i++) {
|
||||
const newCol = document.createElement("div");
|
||||
newCol.classList.add("column");
|
||||
newCol.length = 0;
|
||||
cols.push(newCol);
|
||||
}
|
||||
|
||||
switch(config.layout) {
|
||||
case 'horizontal':
|
||||
horizontalLayout(cards, cols, config);
|
||||
break;
|
||||
case 'vertical':
|
||||
verticalLayout(cards, cols, config);
|
||||
break;
|
||||
case 'auto':
|
||||
default:
|
||||
defaultLayout(cards, cols, config);
|
||||
}
|
||||
|
||||
// Remove empty columns
|
||||
cols = cols.filter((c) => c.childElementCount > 0);
|
||||
|
||||
return cols;
|
||||
}
|
||||
211
src/main.js
Normal file
211
src/main.js
Normal file
@@ -0,0 +1,211 @@
|
||||
import { LitElement, html, css } from "card-tools/src/lit-element";
|
||||
// import "card-tools/src/card-maker";
|
||||
import { hass } from "card-tools/src/hass";
|
||||
|
||||
import {buildLayout} from "./layout";
|
||||
|
||||
class LayoutCard extends LitElement {
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {},
|
||||
_config: {},
|
||||
};
|
||||
}
|
||||
|
||||
async setConfig(config) {
|
||||
this._config = {
|
||||
min_height: 5,
|
||||
|
||||
column_width: 300,
|
||||
max_width: config.column_width || "500px",
|
||||
|
||||
min_columns: config.column_num || 1,
|
||||
max_columns: 100,
|
||||
|
||||
...config,
|
||||
}
|
||||
|
||||
this.cards = [];
|
||||
this.columns = [];
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
window.addEventListener('resize', () => this.place_cards());
|
||||
window.addEventListener('hass-open-menu', () => setTimeout(() => this.place_cards(), 100));
|
||||
window.addEventListener('hass-close-menu', () => setTimeout(() => this.place_cards(), 100));
|
||||
window.addEventListener('location-changed', () => {
|
||||
if(location.hash === "")
|
||||
setTimeout(() => this.place_cards(), 100)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async updated(changedproperties) {
|
||||
if(!this.cards.length) {
|
||||
// Build cards and layout
|
||||
this.cards = await this.build_cards();
|
||||
this.place_cards();
|
||||
}
|
||||
|
||||
if(changedproperties.has("hass") && this.hass && this.cards) {
|
||||
// Update the hass object of every card
|
||||
this.cards.forEach((c) => {
|
||||
if(!c) return;
|
||||
c.hass = this.hass;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async build_card(c) {
|
||||
if(c === "break")
|
||||
return null;
|
||||
const el = document.createElement("card-maker");
|
||||
el.config = c;
|
||||
el.hass = hass();
|
||||
// Cards are initially placed in the staging area
|
||||
// That places them in the DOM and lets us read their getCardSize() function
|
||||
this.shadowRoot.querySelector("#staging").appendChild(el);
|
||||
return new Promise((resolve, reject) => el.updateComplete.then(() => resolve(el)));
|
||||
}
|
||||
|
||||
async build_cards() {
|
||||
// Clear out any cards in the staging area which might have been built but not placed
|
||||
const staging = this.shadowRoot.querySelector("#staging");
|
||||
while(staging.lastChild)
|
||||
staging.removeChild(staging.lastChild);
|
||||
return Promise.all(
|
||||
(this._config.entities || this._config.cards)
|
||||
.map((c) => this.build_card(c))
|
||||
);
|
||||
}
|
||||
|
||||
place_cards() {
|
||||
const width = this.shadowRoot.querySelector("#columns").clientWidth;
|
||||
|
||||
this.columns = buildLayout(
|
||||
this.cards,
|
||||
width,
|
||||
this._config
|
||||
);
|
||||
|
||||
this.format_columns();
|
||||
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
format_columns() {
|
||||
const renderProp = (name, property, index, unit="px") => {
|
||||
// Check if the config option is specified
|
||||
if (this._config[property] === undefined) return "";
|
||||
|
||||
let retval = `${name}: `;
|
||||
const prop = this._config[property];
|
||||
if (typeof(prop) === "object")
|
||||
// Get the last value if there are not enough
|
||||
if(prop.length > index)
|
||||
retval += `${prop[index]}`;
|
||||
else
|
||||
retval += `${prop.slice(-1)}`;
|
||||
else
|
||||
retval += `${prop}`;
|
||||
|
||||
// Add unit (px) if necessary
|
||||
if(!retval.endsWith("px") && !retval.endsWith("%")) retval += unit;
|
||||
return retval + ";"
|
||||
}
|
||||
|
||||
// Set element style for each column
|
||||
for(const [i, c] of this.columns.entries()) {
|
||||
const styles = [
|
||||
renderProp("max-width", "max_width", i),
|
||||
renderProp("min-width", "min_width", i),
|
||||
renderProp("width", "column_width", i),
|
||||
renderProp("flex-grow", "flex_grow", i, ""),
|
||||
]
|
||||
c.style.cssText = ''.concat(...styles);
|
||||
}
|
||||
}
|
||||
|
||||
getCardSize() {
|
||||
if(this.columns)
|
||||
return Math.max.apply(Math, this.columns.map((c) => c.length));
|
||||
}
|
||||
|
||||
isPanel() {
|
||||
let el = this.parentElement;
|
||||
let steps = 10;
|
||||
while(steps--) {
|
||||
if(el.localName === "hui-panel-view") return true;
|
||||
if(el.localName === "div") return false;
|
||||
el = el.parentElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="columns"
|
||||
class="
|
||||
${this._config.rtl ? "rtl": " "}
|
||||
${this.isPanel() ? "panel": " "}
|
||||
"
|
||||
style="
|
||||
${this._config.justify_content ? `justify-content: ${this._config.justify_content};` : ''}
|
||||
">
|
||||
${this.columns.map((col) => html`
|
||||
${col}
|
||||
`)}
|
||||
</div>
|
||||
<div id="staging"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
padding: 0 4px;
|
||||
display: block;
|
||||
margin-bottom: 0!important;
|
||||
}
|
||||
|
||||
#columns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin-top: -8px;
|
||||
}
|
||||
#columns.rtl {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
#columns.panel {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
card-maker>* {
|
||||
display: block;
|
||||
margin: 4px 4px 8px;
|
||||
}
|
||||
card-maker:first-child>* {
|
||||
margin-top: 8px;
|
||||
}
|
||||
card-maker:last-child>* {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
#staging {
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
customElements.define("layout-card", LayoutCard);
|
||||
Reference in New Issue
Block a user