Skip to content

Commit

Permalink
refactor(LightController): better naming + comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Gh61 committed Nov 30, 2023
1 parent 89ac3ea commit ee979b5
Show file tree
Hide file tree
Showing 8 changed files with 547 additions and 539 deletions.
12 changes: 6 additions & 6 deletions src/controls/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { customElement, state } from 'lit/decorators.js';
import { classMap } from 'lit-html/directives/class-map.js';
import { Background } from '../core/colors/background';
import { Color } from '../core/colors/color';
import { LightController } from '../core/light-controller';
import { AreaLightController } from '../core/area-light-controller';
import { ViewUtils } from '../core/view-utils';
import { HueLikeLightCardConfig } from '../types/config';
import { Consts } from '../types/consts';
Expand All @@ -16,7 +16,7 @@ import { HueDialogLightTile, ILightSelectedEventDetail } from './dialog-light-ti
import { ILightContainer } from '../types/types-interface';
import { ITileEventDetail } from './dialog-tile';
import { HueLightDetail } from './light-detail';
import { LightContainer } from '../core/light-container';
import { LightController } from '../core/light-controller';
import { HueHistoryStateManager, HueHistoryStep } from './history-state-manager';
import { localize } from '../localize/localize';
import { ActionHandler } from '../core/action-handler';
Expand All @@ -36,13 +36,13 @@ export class HueDialog extends IdLitElement {

private _isRendered = false;
private _config: HueLikeLightCardConfig;
private _ctrl: LightController;
private _ctrl: AreaLightController;
private _actionHandler: ActionHandler;

@state()
private _selectedLight: ILightContainer | null;

public constructor(config: HueLikeLightCardConfig, lightController: LightController, actionHandler: ActionHandler) {
public constructor(config: HueLikeLightCardConfig, lightController: AreaLightController, actionHandler: ActionHandler) {
super('HueDialog');

this._config = config;
Expand All @@ -52,7 +52,7 @@ export class HueDialog extends IdLitElement {

//#region Hass changes

private onLightControllerChanged(propertyName: keyof LightController) {
private onLightControllerChanged(propertyName: keyof AreaLightController) {
// when LightController changed - update this
if (propertyName == 'hass') {
this.requestUpdate();
Expand All @@ -77,7 +77,7 @@ export class HueDialog extends IdLitElement {

// set light into detail
if (this._lightDetailElement) {
this._lightDetailElement.lightContainer = <LightContainer>this._selectedLight;
this._lightDetailElement.lightContainer = <LightController>this._selectedLight;
this._lightDetailElement.show();
}
};
Expand Down
10 changes: 5 additions & 5 deletions src/controls/light-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Consts } from '../types/consts';
import { PropertyValues, css, unsafeCSS } from 'lit';
import { HueBrightnessRollup, IRollupValueChangeEventDetail } from './brightness-rollup';
import { HueColorTempPicker, HueColorTempPickerMarker, IHueColorTempPickerEventDetail } from './color-temp-picker';
import { LightContainer } from '../core/light-container';
import { LightController } from '../core/light-controller';
import { HueColorTempModeSelector } from './color-temp-mode-selector';
import { HaControlSwitch } from '../types/types-hass';
import { HueBigSwitch } from './big-switch';
Expand All @@ -32,7 +32,7 @@ export class HueLightDetail extends IdLitElement {
}

@property()
public lightContainer: LightContainer | null = null;
public lightContainer: LightController | null = null;

/**
* Called after new lightContainer is set.
Expand Down Expand Up @@ -180,7 +180,7 @@ export class HueLightDetail extends IdLitElement {
protected override updated(changedProps: PropertyValues<HueLightDetail>): void {
// register for changes on light
if (changedProps.has('lightContainer')) {
const oldValue = changedProps.get('lightContainer') as LightContainer | null;
const oldValue = changedProps.get('lightContainer') as LightController | null;
if (oldValue) {
oldValue.unregisterOnPropertyChanged(this._id);
}
Expand Down Expand Up @@ -245,7 +245,7 @@ export class HueLightDetail extends IdLitElement {
}
`;

private _lastRenderedContainer: LightContainer | null;
private _lastRenderedContainer: LightController | null;
protected override render() {
this._lastRenderedContainer = this.lightContainer || this._lastRenderedContainer;
const onlySwitch = this._lastRenderedContainer?.features.isEmpty() == true;
Expand All @@ -257,7 +257,7 @@ export class HueLightDetail extends IdLitElement {
</div>`;
}

private onSwitch(ctrl: LightContainer, ev: Event) {
private onSwitch(ctrl: LightController, ev: Event) {
const target = <HaControlSwitch>ev.target;
if (!target)
return;
Expand Down
6 changes: 3 additions & 3 deletions src/core/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { fireEvent } from 'custom-card-helpers';
import { HueDialog } from '../controls/dialog';
import { HueLikeLightCardConfig } from '../types/config';
import { ClickAction, ClickActionData, SceneData } from '../types/types-config';
import { LightController } from './light-controller';
import { AreaLightController } from './area-light-controller';
import { HueLikeLightCard } from '../hue-like-light-card';

export class ActionHandler {
private _config: HueLikeLightCardConfig;
private _ctrl: LightController;
private _ctrl: AreaLightController;
private _owner: HueLikeLightCard;

public constructor(config: HueLikeLightCardConfig, ctrl: LightController, element: HueLikeLightCard) {
public constructor(config: HueLikeLightCardConfig, ctrl: AreaLightController, element: HueLikeLightCard) {
this._config = config;
this._ctrl = ctrl;
this._owner = element;
Expand Down
243 changes: 243 additions & 0 deletions src/core/area-light-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import { HomeAssistant } from 'custom-card-helpers';
import { IHassTextTemplate, ILightContainer, ILightFeatures } from '../types/types-interface';
import { Background } from './colors/background';
import { Color } from './colors/color';
import { GlobalLights } from './global-lights';
import { HassTextTemplate, StaticTextTemplate } from './hass-text-template';
import { LightController } from './light-controller';
import { LightFeaturesCombined } from './light-features';
import { NotifyBase } from './notify-base';
import { IconHelper } from './icon-helper';
import { localize } from '../localize/localize';

/**
* Serves as a controller for lights in single area.
* This can contain multiple lights even some interactions can be different.
* (Instead of turnOn, activate scene).
*/
export class AreaLightController extends NotifyBase<AreaLightController> implements ILightContainer {
private _hass: HomeAssistant;
private _lightGroup?: LightController;
private _lights: LightController[];
private _lightsFeatures: LightFeaturesCombined;
private _defaultColor: Color;

public constructor(entity_ids: string[], defaultColor: Color, lightGroupEntityId?: string) {
super();

// we need at least one
if (!entity_ids.length)
throw new Error('No entity specified.');

this._defaultColor = defaultColor;
this._lights = entity_ids.map(e => GlobalLights.getLightContainer(e));
this._lightsFeatures = new LightFeaturesCombined(() => this._lights.map(l => l.features));
if (lightGroupEntityId) {
this._lightGroup = GlobalLights.getLightContainer(lightGroupEntityId);
}
}

/**
* Returns count of registered lights.
*/
public get count() {
return this._lights.length;
}

/**
* @returns all lit lights.
*/
public getLitLights(): ILightContainer[] {
return this._lights.filter(l => l.isOn());
}

/**
* @returns all lights in this controller.
*/
public getLights(): ILightContainer[] {
return this._lights.map(l => l); // map will cause creation of new array
}

public set hass(hass: HomeAssistant) {
this._hass = hass;
this._lights.forEach(l => l.hass = hass);
if (this._lightGroup) {
this._lightGroup.hass = hass;
}
this.raisePropertyChanged('hass');
}
public get hass() {
return this._hass;
}

public isOn(): boolean {
if (this._lightGroup) {
return this._lightGroup.isOn();
}
return this._lights.some(l => l.isOn());
}
public isOff(): boolean {
if (this._lightGroup) {
return this._lightGroup.isOff();
}
return this._lights.every(l => l.isOff());
}
public isUnavailable(): boolean {
if (this._lightGroup) {
return this._lightGroup.isUnavailable();
}
return this._lights.every(l => l.isUnavailable());
}
public turnOn(): void {
if (this._lightGroup) {
return this._lightGroup.turnOn();
}
this._lights.filter(l => l.isOff()).forEach(l => l.turnOn());
}
public turnOff(): void {
if (this._lightGroup) {
return this._lightGroup.turnOff();
}
this._lights.filter(l => l.isOn()).forEach(l => l.turnOff());
}

public get brightnessValue() {
return this.valueGetFactory();
}
public set brightnessValue(value: number) {
const litLights = this._lights.filter(l => l.isOn());
// when only one light is on, set the value to that light
if (litLights.length == 1) {
litLights[0].brightnessValue = value;
return;
}
else if (litLights.length == 0) { // when no light is on, set value to all lights
this._lights.forEach(l => l.brightnessValue = value);
return;
}

// get percentage change of remaining value
const oldValue = this.brightnessValue;
const valueChange = value - oldValue;
const remainingValue = valueChange > 0 ? (100 - this.brightnessValue) : this.brightnessValue;
const percentualChange = valueChange / remainingValue; // percentual of remaining

// calculate the value for each light
this._lights.filter(l => l.isOn()).forEach(l => {
const lightOldValue = l.brightnessValue;
// of value of this light is the same asi value of controller, set it exactly to value
if (lightOldValue == oldValue) {
l.brightnessValue = value;
return;
}

// get remaining part of this one light
const remainingLightValue = valueChange > 0 ? (100 - l.brightnessValue) : l.brightnessValue;
// compute value increment
const lightValueChange = Math.round(remainingLightValue * percentualChange);
// get new value
let newValue = l.brightnessValue + lightValueChange;

// don't let the value drop to zero, if the target value isn't exactly zero
if (newValue < 1 && value > 0) {
newValue = 1;
}
l.brightnessValue = newValue;
});
}

private valueGetFactory() {
// get average from every light that is on
let total = 0;
let count = 0;
this._lights.forEach(e => {
if (e.isOn()) {
count++;
total += e.brightnessValue;
}
});
if (count == 0)
return 0;

const value = total / count * 1.0;
return value;
}

public getIcon(): string {
if (this._lights.length == 1) {
return this._lights[0].getIcon() || IconHelper.getIcon(1);
}

return IconHelper.getIcon(this._lights.length);
}

public getTitle() {
if (this._lightGroup) {
return this._lightGroup.getTitle();
}

let title = '';
for (let i = 0; i < this._lights.length && i < 3; i++) {
if (i > 0) {
title += ', ';
}
title += this._lights[i].getTitle();
}
if (this._lights.length > 3)
title += ', ...';

return new StaticTextTemplate(title);
}

/**
* @returns localized description of how many lights are on.
*/
public getDescription(description: string | undefined): IHassTextTemplate {
const total = this._lights.length;
let lit = 0;
this._lights.forEach(l => {
if (l.isOn()) {
lit++;
}
});

let result:string;

if (description != null) {
if (description) {
result = description.replace('%s', lit.toString());
return new HassTextTemplate(result);
}
result = '';
}
else if (lit == 0) {
result = localize(this.hass, 'card.description.noLightsOn');
}
else if (lit == total) {
result = localize(this.hass, 'card.description.allLightsOn');
}
else if (lit == 1) {
result = localize(this.hass, 'card.description.oneLightOn');
}
else {
result = localize(this.hass, 'card.description.someLightsAreOn', '%s', lit.toString());
}

return new StaticTextTemplate(result);
}

public getBackground(): Background | null {
const backgrounds = this._lights.filter(l => l.isOn()).map(l => l.getBackground() || this._defaultColor);
if (backgrounds.length == 0)
return null;
return new Background(backgrounds);
}

public getEntityId(): string {
throw Error('Cannot get entity id from LightController');
}

public get features(): ILightFeatures {
return this._lightsFeatures;
}
}
8 changes: 4 additions & 4 deletions src/core/global-lights.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { LightContainer } from './light-container';
import { LightController } from './light-controller';

/**
* Static class making LightContainer instances global.
*/
export class GlobalLights {
private static _containers:Record<string, LightContainer> = {};
private static _containers:Record<string, LightController> = {};

public static getLightContainer(entity_id: string): LightContainer {
public static getLightContainer(entity_id: string): LightController {
let instance = this._containers[entity_id];
if (!instance) {
//console.log(`[GlobalLights] Creating instance for '${entity_id}'`);
instance = new LightContainer(entity_id);
instance = new LightController(entity_id);
this._containers[entity_id] = instance;
}
else {
Expand Down
Loading

0 comments on commit ee979b5

Please sign in to comment.