Concept of properties
Properties are used in ThermalManager
, ThermalRegistry
, ThermalGroup
and ThermalFileInstance
. Every "property" object encapsulates a complex functionality related to a particular attribute of thermal display.
How it works
Every property is a readonly object with:
- readonly
value
holding the current value - no default setter
- individual setters for every property, for every use case, because setters usually have many side effects
addListener( key, listener )
method for registering callbacks to value change- possible additional data in individual properties
Properties are read only
Properties are added to structure classes like this:
thermalRegistry.ts
export class ThermalRegistry {
// ...
public readonly opacity = new OpacityDrive(this, 1);
public readonly minmax = new MinmaxRegistryProperty(this, undefined);
public readonly loading = new LoadingState(this, false);
public readonly range = new RangeDriver(this, undefined);
public readonly histogram = new HistogramState(this, []);
// ...
}
AbstractProperty
Properties are based on AbstractProperty (opens in a new tab). Principle is the folllowing:
/** A common basis for all observable properties */
export abstract class AbstractProperty<
ValueType extends PropertyListenersTypes, // Type of the value
ParentType extends IBaseProperty // The class to which the property is assigned
> {
public constructor(
public readonly parent: ParentType,
public readonly _initial: ValueType
) {
this._value = this.validate(this._initial);
}
// Internal value
protected _value: ValueType;
// Public value is readonly
public get value() {
return this._value;
};
// Protected setter calls all necessary effects and triggers event listeners
protected set value(
value: ValueType
) {
this._value = this.validate(value);
this.afterSetEffect(this._value);
Object.values(this._listeners).forEach(listener => listener(this._value));
}
// Abstract methods
/** Implement the validation method. */
protected abstract validate(value: ValueType): ValueType;
/** implement the sideeffects method. */
protected abstract afterSetEffect(value: ValueType): void
// Listeners management
protected _listeners: {
[index: string]: PropertyListenerFn<ValueType>
} = {}
addListener(
id: string,
listener: PropertyListenerFn<ValueType>
) {
if (id in this._listeners) {
delete this._listeners[id];
}
this._listeners[id] = listener;
}
removeListener(
id: string
) {
if (id in this._listeners) {
delete this._listeners[id];
}
}
Example of implementation
export class PaletteDrive extends AbstractProperty< PaletteId, ThermalManager > {
/** The main functionality is handled by the abstract class. Here. we write only the missing parts. */
/** The public setter */
public setPalette( key: PaletteId ) {
// Only store value here. The propagation of new value is in `afterSetEffect`
this.value = key;
}
/** What to do after new value is set */
protected afterSetEffect(value: PaletteId) {
// impose the new value to all child instances
this.parent.forEveryRegistry( registry => {
registry.forEveryGroup( group => group
.instances
.forEveryInstance( instance => instance
.recievePalette( value )
)
);
} );
}
/** Expose all available palettes */
public get availablePalettes() {
return ThermalPalettes;
}
/** Expose the current palette object (internal value is string, so here are full data) */
public get currentPalette() {
return this.availablePalettes[ this.value ];
}
/** We do not validate the value in this case */
protected validate(value: PaletteId): PaletteId {
return value;
}
}