389 lines
8.4 KiB
TypeScript
389 lines
8.4 KiB
TypeScript
|
import jquery = require('jquery');
|
||
|
import ko = require('knockout');
|
||
|
import { Log } from 'log';
|
||
|
|
||
|
|
||
|
var instance: Stairs = null;
|
||
|
|
||
|
|
||
|
export class StairsStaticMode
|
||
|
{
|
||
|
public Brightness = ko.observable(0);
|
||
|
public Ease = ko.observable(false);
|
||
|
|
||
|
public read(data: any): void
|
||
|
{
|
||
|
this.Brightness(data.brightness);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export interface IStairsCustomBrightness
|
||
|
{
|
||
|
value: KnockoutObservable<number>;
|
||
|
}
|
||
|
|
||
|
export class StairsCustomMode
|
||
|
{
|
||
|
public Brightness = ko.observableArray<IStairsCustomBrightness>([]);
|
||
|
|
||
|
public init(stepCount: number): void
|
||
|
{
|
||
|
// Each item is an object containing an observable; we can't
|
||
|
// add the observable directly otherwise the foreach template
|
||
|
// will unwrap it before we can attach it to the range input.
|
||
|
var values: Array<IStairsCustomBrightness> = [];
|
||
|
for (var index = 0; index < stepCount; index++)
|
||
|
values.push({ value: ko.observable(0) });
|
||
|
|
||
|
this.Brightness(values);
|
||
|
}
|
||
|
|
||
|
public read(data: any): void
|
||
|
{
|
||
|
var brightness = data.brightness.map((item: number): IStairsCustomBrightness =>
|
||
|
{
|
||
|
return { value: ko.observable(item) };
|
||
|
});
|
||
|
|
||
|
brightness.reverse();
|
||
|
this.Brightness(brightness);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export class StairsAlternateMode
|
||
|
{
|
||
|
public Interval = ko.observable(500);
|
||
|
public Brightness = ko.observable(0);
|
||
|
|
||
|
public read(data: any): void
|
||
|
{
|
||
|
this.Interval(data.interval);
|
||
|
this.Brightness(data.brightness);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
export enum StairsMode
|
||
|
{
|
||
|
Unknown = 0,
|
||
|
Static = 1,
|
||
|
Custom = 2,
|
||
|
Alternate = 3
|
||
|
}
|
||
|
|
||
|
export class StairsModeParameters
|
||
|
{
|
||
|
public Static = new StairsStaticMode();
|
||
|
public Custom = new StairsCustomMode();
|
||
|
public Alternate = new StairsAlternateMode();
|
||
|
|
||
|
public Current = ko.observable(StairsMode.Unknown);
|
||
|
|
||
|
|
||
|
public read(data: any): void
|
||
|
{
|
||
|
switch (this.Current())
|
||
|
{
|
||
|
case StairsMode.Static: this.Static.read(data); break;
|
||
|
case StairsMode.Custom: this.Custom.read(data); break;
|
||
|
case StairsMode.Alternate: this.Alternate.read(data); break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
export class StairsRangeValue
|
||
|
{
|
||
|
public Start = ko.observable(0);
|
||
|
public End = ko.observable(4095);
|
||
|
}
|
||
|
|
||
|
|
||
|
export class StairsRange
|
||
|
{
|
||
|
public UseScaling = ko.observable(false);
|
||
|
public Values = ko.observableArray<StairsRangeValue>([]);
|
||
|
|
||
|
public read(data: any): void
|
||
|
{
|
||
|
this.UseScaling(data.useScaling);
|
||
|
|
||
|
var values = data.values.map((item: any): StairsRangeValue =>
|
||
|
{
|
||
|
var value = new StairsRangeValue();
|
||
|
value.Start(item.start);
|
||
|
value.End(item.end);
|
||
|
|
||
|
return value;
|
||
|
});
|
||
|
|
||
|
values.reverse();
|
||
|
this.Values(values);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
export class Stairs
|
||
|
{
|
||
|
public Loading = ko.observable(false);
|
||
|
public SavingMode = ko.observable(false);
|
||
|
public SavingSettings = ko.observable(false);
|
||
|
public Saving = ko.pureComputed<boolean>(() =>
|
||
|
{
|
||
|
return this.SavingMode() || this.SavingSettings();
|
||
|
});
|
||
|
|
||
|
public Mode = new StairsModeParameters();
|
||
|
public Range = new StairsRange();
|
||
|
|
||
|
private pingTimer: number = null;
|
||
|
private pingRequest: JQueryXHR = null;
|
||
|
|
||
|
private updatingFromServer = true;
|
||
|
private updateModeTimeout: number = null;
|
||
|
private updateRangeTimeout: number = null;
|
||
|
|
||
|
|
||
|
public static instance(): Stairs
|
||
|
{
|
||
|
if (instance == null)
|
||
|
instance = new Stairs();
|
||
|
|
||
|
return instance;
|
||
|
}
|
||
|
|
||
|
|
||
|
constructor()
|
||
|
{
|
||
|
this.ping();
|
||
|
}
|
||
|
|
||
|
|
||
|
public ping(): void
|
||
|
{
|
||
|
if (this.pingRequest !== null)
|
||
|
{
|
||
|
Log.verbose('Stairs.ping', 'Ping request already running, skipping');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.pingTimer !== null)
|
||
|
{
|
||
|
clearTimeout(this.pingTimer);
|
||
|
this.pingTimer = null;
|
||
|
}
|
||
|
|
||
|
if (this.Loading() || this.Saving())
|
||
|
{
|
||
|
this.pingTimer = setTimeout(() => this.ping(), 5000);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Log.verbose('Stairs.ping', 'Starting Ping request');
|
||
|
this.updatingFromServer = true;
|
||
|
this.Loading(true);
|
||
|
|
||
|
this.pingRequest = $.ajax({
|
||
|
url: '/ping',
|
||
|
dataType: 'json',
|
||
|
cache: false
|
||
|
});
|
||
|
|
||
|
this.pingRequest.done((data: any) =>
|
||
|
{
|
||
|
Log.verbose('Stairs.ping', data);
|
||
|
|
||
|
this.Mode.Custom.init(data.stepCount);
|
||
|
$.when(this.getMode(), this.getRange())
|
||
|
.done(() => this.pingComplete(true))
|
||
|
.fail(() => this.pingComplete(false));
|
||
|
});
|
||
|
|
||
|
this.pingRequest.fail(() =>
|
||
|
{
|
||
|
Log.warning('Stairs.ping', 'Ping failed');
|
||
|
this.pingComplete(true);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
private pingComplete(success: boolean)
|
||
|
{
|
||
|
this.pingRequest = null;
|
||
|
|
||
|
if (success)
|
||
|
//this.pingTimer = setTimeout(() => this.ping(), 5000);
|
||
|
{}
|
||
|
else
|
||
|
this.pingTimer = setTimeout(() => this.ping(), 5000);
|
||
|
|
||
|
if (success)
|
||
|
{
|
||
|
this.Loading(false);
|
||
|
this.updatingFromServer = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
private getMode(): JQueryPromise<any>
|
||
|
{
|
||
|
Log.verbose('Stairs.getMode', 'Requesting Mode');
|
||
|
|
||
|
var request = $.ajax({
|
||
|
url: '/getMode',
|
||
|
dataType: 'json',
|
||
|
cache: false
|
||
|
});
|
||
|
|
||
|
request.done((response) =>
|
||
|
{
|
||
|
Log.verbose('Stairs.getMode', response);
|
||
|
|
||
|
this.Mode.Current(response.mode);
|
||
|
this.Mode.read(response.data);
|
||
|
});
|
||
|
|
||
|
return request;
|
||
|
}
|
||
|
|
||
|
|
||
|
private getRange(): JQueryPromise<any>
|
||
|
{
|
||
|
Log.verbose('Stairs.getRange', 'Requesting Range configuration');
|
||
|
|
||
|
var request = $.ajax({
|
||
|
url: '/getRange',
|
||
|
dataType: 'json',
|
||
|
cache: false
|
||
|
});
|
||
|
|
||
|
request.done((response) =>
|
||
|
{
|
||
|
Log.verbose('Stairs.getRange', response);
|
||
|
|
||
|
this.Range.read(response);
|
||
|
});
|
||
|
|
||
|
return request;
|
||
|
}
|
||
|
|
||
|
|
||
|
private updateMode = ko.computed(() =>
|
||
|
{
|
||
|
if (this.Loading() || this.SavingMode.peek()) return;
|
||
|
|
||
|
var url = '/setMode/';
|
||
|
switch (this.Mode.Current())
|
||
|
{
|
||
|
case StairsMode.Static:
|
||
|
url += 'Static?brightness=' + encodeURIComponent(this.Mode.Static.Brightness().toString());
|
||
|
break;
|
||
|
|
||
|
case StairsMode.Custom:
|
||
|
var values = this.Mode.Custom.Brightness().map((item: IStairsCustomBrightness) => { return item.value(); });
|
||
|
values.reverse();
|
||
|
url += 'Custom?brightness=' + encodeURIComponent(values.join());
|
||
|
break;
|
||
|
|
||
|
case StairsMode.Alternate:
|
||
|
url += 'Alternate?interval=' + encodeURIComponent(this.Mode.Alternate.Interval().toString()) +
|
||
|
'&brightness=' + encodeURIComponent(this.Mode.Alternate.Brightness().toString());
|
||
|
break;
|
||
|
|
||
|
/*
|
||
|
case 'Slide':
|
||
|
url += '?interval=' + encodeURIComponent(this.slide.interval()) +
|
||
|
'&brightness=' + encodeURIComponent(this.slide.brightness()) +
|
||
|
'&direction=' + encodeURIComponent(this.slide.direction()) +
|
||
|
'&fadeOutTime=' + encodeURIComponent(this.slide.fadeOutTime());
|
||
|
break;
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
|
||
|
// Exit after checking all the parameters, so the observers
|
||
|
// are properly subscribed
|
||
|
if (this.updatingFromServer) return;
|
||
|
|
||
|
if (this.updateModeTimeout !== null)
|
||
|
{
|
||
|
clearTimeout(this.updateModeTimeout);
|
||
|
this.updateModeTimeout = null;
|
||
|
}
|
||
|
|
||
|
this.updateModeTimeout = setTimeout(() =>
|
||
|
{
|
||
|
Log.info("Stairs.updateMode", url);
|
||
|
|
||
|
// TODO retry on failure
|
||
|
this.SavingMode(true);
|
||
|
|
||
|
$.ajax(
|
||
|
{
|
||
|
url: url,
|
||
|
dataType: 'json',
|
||
|
cache: false
|
||
|
})
|
||
|
.always(() =>
|
||
|
{
|
||
|
this.SavingMode(false);
|
||
|
});
|
||
|
|
||
|
clearTimeout(this.updateModeTimeout);
|
||
|
this.updateModeTimeout = null;
|
||
|
}, 200);
|
||
|
|
||
|
return true;
|
||
|
});
|
||
|
|
||
|
|
||
|
private updateSettings = ko.computed(() =>
|
||
|
{
|
||
|
if (this.Loading() || this.SavingSettings.peek()) return;
|
||
|
|
||
|
var url = '/setRange?useScaling=' + this.Range.UseScaling().toString();
|
||
|
|
||
|
var start = this.Range.Values().map((item: StairsRangeValue): number => { return item.Start(); });
|
||
|
var end = this.Range.Values().map((item: StairsRangeValue): number => { return item.End(); });
|
||
|
|
||
|
start.reverse();
|
||
|
end.reverse();
|
||
|
|
||
|
url += '&start=' + encodeURIComponent(start.join());
|
||
|
url += '&end=' + encodeURIComponent(end.join());
|
||
|
|
||
|
// Exit after checking all the parameters, so the observers
|
||
|
// are properly subscribed
|
||
|
if (this.updatingFromServer) return;
|
||
|
|
||
|
if (this.updateRangeTimeout !== null)
|
||
|
{
|
||
|
clearTimeout(this.updateRangeTimeout);
|
||
|
this.updateRangeTimeout = null;
|
||
|
}
|
||
|
|
||
|
this.updateRangeTimeout = setTimeout(() =>
|
||
|
{
|
||
|
Log.info("Stairs.updateSettings", url);
|
||
|
|
||
|
// TODO retry on failure
|
||
|
this.SavingSettings(true);
|
||
|
|
||
|
$.ajax(
|
||
|
{
|
||
|
url: url,
|
||
|
dataType: 'json',
|
||
|
cache: false
|
||
|
})
|
||
|
.always(() =>
|
||
|
{
|
||
|
this.SavingSettings(false);
|
||
|
});
|
||
|
|
||
|
clearTimeout(this.updateRangeTimeout);
|
||
|
this.updateRangeTimeout = null;
|
||
|
}, 200);
|
||
|
|
||
|
return true;
|
||
|
});
|
||
|
}
|