Skip to content

Commit

Permalink
feat(editor): Preview in the GUI card selector
Browse files Browse the repository at this point in the history
  • Loading branch information
RomRider committed Jan 31, 2021
1 parent 37c5ce3 commit d4cd7a3
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/ui-lovelace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ views:
- type: custom:apexcharts-card
header:
show: true
title: Scatter
title: scatter
chart_type: scatter
apex_config:
responsive:
Expand Down
157 changes: 145 additions & 12 deletions src/apexcharts-card.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LitElement, html, customElement, property, TemplateResult, CSSResult, PropertyValues } from 'lit-element';
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
import { ChartCardConfig, EntityCachePoints, EntityEntryCache } from './types';
import { HomeAssistant } from 'custom-card-helpers';
import { getLovelace, HomeAssistant } from 'custom-card-helpers';
import localForage from 'localforage';
import * as pjson from '../package.json';
import {
Expand All @@ -23,7 +23,7 @@ import { HassEntity } from 'home-assistant-js-websocket';
import { getLayoutConfig } from './apex-layouts';
import GraphEntry from './graphEntry';
import { createCheckers } from 'ts-interface-checker';
import { ChartCardExternalConfig } from './types-config';
import { ChartCardExternalConfig, ChartCardSeriesExternalConfig } from './types-config';
import exportedTypeSuite from './types-config-ti';
import { DEFAULT_FLOAT_PRECISION, DEFAULT_SHOW_LEGEND_VALUE, moment, NO_VALUE, TIMESERIES_TYPES } from './const';
import {
Expand Down Expand Up @@ -100,6 +100,8 @@ class ChartsCard extends LitElement {

private _seriesOffset: number[] = [];

@property({ type: Boolean }) private _warning = false;

public connectedCallback() {
super.connectedCallback();
if (this._config && this._hass && !this._loaded) {
Expand Down Expand Up @@ -168,6 +170,12 @@ class ChartsCard extends LitElement {
}
}
});
if (this._config.series.some((_, index) => this._entities[index] === undefined)) {
this._warning = true;
return;
} else if (this._warning) {
this._warning = false;
}
if (updated) {
this._entities = [...this._entities];
if (!this._updating && !this._config.update_interval) {
Expand Down Expand Up @@ -240,19 +248,36 @@ class ChartsCard extends LitElement {
}
validateInterval(serie.group_by.duration, `series[${index}].group_by.duration`);
if (serie.entity) {
return new GraphEntry(
const editMode = getLovelace()?.editMode;
// disable caching for editor
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const caching = editMode === true ? false : this._config!.cache;
const graphEntry = new GraphEntry(
index,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this._graphSpan!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this._config!.cache,
caching,
serie,
this._config?.span,
);
if (this._hass) graphEntry.hass = this._hass;
return graphEntry;
}
return undefined;
});
}
// Reset only happens in editor mode
if (this._apexChart) {
this._apexChart.destroy();
this._apexChart = undefined;
this._loaded = false;
this._dataLoaded = false;
this._updating = false;
}
if (this._config && this._hass && !this._loaded) {
this._initialLoad();
}
}

static get styles(): CSSResult {
Expand All @@ -261,8 +286,8 @@ class ChartsCard extends LitElement {

protected render(): TemplateResult {
if (!this._config || !this._hass) return html``;
if (this._config.series.some((_, index) => this._entities[index] === undefined)) {
return this.renderWarnings();
if (this._warning || this._config.series.some((_, index) => this._entities[index] === undefined)) {
return this._renderWarnings();
}

const spinnerClass: ClassInfo = {
Expand Down Expand Up @@ -293,7 +318,7 @@ class ChartsCard extends LitElement {
`;
}

renderWarnings() {
private _renderWarnings(): TemplateResult {
return html`
<ha-card class="warning">
<hui-warning>
Expand Down Expand Up @@ -366,13 +391,17 @@ class ChartsCard extends LitElement {
if (!this._config || !this._apexChart || !this._graphs) return;

const { start, end } = this._getSpanDates();
const editMode = getLovelace()?.editMode;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const caching = editMode === true ? false : this._config!.cache;
try {
const promise = this._graphs.map((graph, index) =>
graph?._updateHistory(
const promise = this._graphs.map((graph, index) => {
if (graph) graph.cache = caching;
return graph?._updateHistory(
this._seriesOffset[index] ? new Date(start.getTime() + this._seriesOffset[index]) : start,
this._seriesOffset[index] ? new Date(end.getTime() + this._seriesOffset[index]) : end,
),
);
);
});
await Promise.all(promise);
let graphData: unknown = {};
if (TIMESERIES_TYPES.includes(this._config.chart_type)) {
Expand Down Expand Up @@ -495,6 +524,110 @@ class ChartsCard extends LitElement {
public getCardSize(): number {
return 3;
}

static getStubConfig(hass: HomeAssistant, entities: string[], entitiesFallback: string[]) {
const entityFilter = (stateObj: HassEntity): boolean => {
return !isNaN(Number(stateObj.state));
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _arrayFilter = (array: any[], conditions: Array<(value: any) => boolean>, maxSize: number) => {
if (!maxSize || maxSize > array.length) {
maxSize = array.length;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const filteredArray: any[] = [];

for (let i = 0; i < array.length && filteredArray.length < maxSize; i++) {
let meetsConditions = true;

for (const condition of conditions) {
if (!condition(array[i])) {
meetsConditions = false;
break;
}
}

if (meetsConditions) {
filteredArray.push(array[i]);
}
}

return filteredArray;
};
const _findEntities = (
hass: HomeAssistant,
maxEntities: number,
entities: string[],
entitiesFallback: string[],
includeDomains?: string[],
entityFilter?: (stateObj: HassEntity) => boolean,
) => {
const conditions: Array<(value: string) => boolean> = [];

if (includeDomains?.length) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
conditions.push((eid) => includeDomains!.includes(eid.split('.')[0]));
}

if (entityFilter) {
conditions.push((eid) => hass.states[eid] && entityFilter(hass.states[eid]));
}

const entityIds = _arrayFilter(entities, conditions, maxEntities);

if (entityIds.length < maxEntities && entitiesFallback.length) {
const fallbackEntityIds = _findEntities(
hass,
maxEntities - entityIds.length,
entitiesFallback,
[],
includeDomains,
entityFilter,
);

entityIds.push(...fallbackEntityIds);
}

return entityIds;
};
const includeDomains = ['sensor'];
const maxEntities = 2;

const foundEntities = _findEntities(hass, maxEntities, entities, entitiesFallback, includeDomains, entityFilter);
const conf = {
header: { show: true, title: 'ApexCharts-Card', show_states: true, colorize_states: true },
series: [] as ChartCardSeriesExternalConfig[],
};
if (foundEntities[0]) {
conf.series[0] = {
entity: foundEntities[0],
data_generator: `// REMOVE ME
const now = new Date();
const data = [];
for(let i = 0; i <= 24; i++) {
data.push([now.getTime() - i * 1000 * 60 * 60, Math.floor((Math.random() * 10) + 1)])
}
return data.reverse();
`,
};
}
if (foundEntities[1]) {
conf.series[1] = {
entity: foundEntities[1],
type: 'column',
data_generator: `// REMOVE ME
const now = new Date();
const data = [];
for(let i = 0; i <= 24; i++) {
data.push([now.getTime() - i * 1000 * 60 * 60, Math.floor((Math.random() * 10) + 1)])
}
return data.reverse();
`,
};
}
return conf;
}
}

// Configure the preview in the Lovelace card picker
Expand All @@ -504,6 +637,6 @@ class ChartsCard extends LitElement {
(window as any).customCards.push({
type: 'apexcharts-card',
name: 'ApexCharts Card',
preview: false,
preview: true,
description: 'A graph card based on ApexCharts',
});
4 changes: 4 additions & 0 deletions src/graphEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export default class GraphEntry {
return this._realEnd;
}

set cache(cache: boolean) {
this._cache = cache;
}

private async _getCache(key: string, compressed: boolean): Promise<EntityEntryCache | undefined> {
const data: EntityEntryCache | undefined | null = await localForage.getItem(
`${key}_${this._md5Config}${compressed ? '' : '-raw'}`,
Expand Down
20 changes: 20 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TinyColor } from '@ctrl/tinycolor';
import parse from 'parse-duration';
import { ChartCardPrettyTime } from './types-config';
import { DEFAULT_MAX, DEFAULT_MIN, moment, NO_VALUE } from './const';
import { LovelaceConfig } from 'custom-card-helpers';

export function compress(data: unknown): string {
return lzStringCompress(JSON.stringify(data));
Expand Down Expand Up @@ -148,3 +149,22 @@ export function getPercentFromValue(value: number, min: number | undefined, max:
const lMax = max === undefined ? DEFAULT_MAX : max;
return ((value - lMin) * 100) / (lMax - lMin);
}

export function getLovelace(): LovelaceConfig | null {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let root: any = document.querySelector('home-assistant');
root = root && root.shadowRoot;
root = root && root.querySelector('home-assistant-main');
root = root && root.shadowRoot;
root = root && root.querySelector('app-drawer-layout partial-panel-resolver');
root = (root && root.shadowRoot) || root;
root = root && root.querySelector('ha-panel-lovelace');
root = root && root.shadowRoot;
root = root && root.querySelector('hui-root');
if (root) {
const ll = root.lovelace;
ll.current_view = root.___curView;
return ll;
}
return null;
}

0 comments on commit d4cd7a3

Please sign in to comment.