[Timer] Re-implement Timer object in Vue.js (#4311)

* Re-implemented timer and clock and installed as a plugin
* Clock indicator is installed as configuration to clock plugin
* Uses moment

Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
This commit is contained in:
Khalid Adil 2021-11-09 16:41:42 -06:00 committed by GitHub
parent b8fabb7e73
commit 6d4a324fca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1123 additions and 2106 deletions

View File

@ -196,6 +196,7 @@
{indicator: true}
));
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
openmct.install(openmct.plugins.Timer());
openmct.start();
</script>
</html>

View File

@ -1,209 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"./src/services/TickerService",
"./src/services/TimerService",
"./src/controllers/TimerController",
"./src/controllers/RefreshingController",
"./src/actions/StartTimerAction",
"./src/actions/RestartTimerAction",
"./src/actions/StopTimerAction",
"./src/actions/PauseTimerAction",
"./res/templates/timer.html"
], function (
TickerService,
TimerService,
TimerController,
RefreshingController,
StartTimerAction,
RestartTimerAction,
StopTimerAction,
PauseTimerAction,
timerTemplate
) {
return {
name: "platform/features/clock",
definition: {
"name": "Clocks/Timers",
"descriptions": "Domain objects for displaying current & relative times.",
"configuration": {
"paths": {
"moment-duration-format": "moment-duration-format"
},
"shim": {
"moment-duration-format": {
"deps": [
"moment"
]
}
}
},
"extensions": {
"constants": [
{
"key": "CLOCK_INDICATOR_FORMAT",
"value": "YYYY/MM/DD HH:mm:ss"
}
],
"services": [
{
"key": "tickerService",
"implementation": TickerService,
"depends": [
"$timeout",
"now"
]
},
{
"key": "timerService",
"implementation": TimerService,
"depends": ["openmct"]
}
],
"controllers": [
{
"key": "TimerController",
"implementation": TimerController,
"depends": [
"$scope",
"$window",
"now"
]
},
{
"key": "RefreshingController",
"implementation": RefreshingController,
"depends": [
"$scope",
"tickerService"
]
}
],
"views": [
{
"key": "timer",
"type": "timer",
"editable": false,
"template": timerTemplate
}
],
"actions": [
{
"key": "timer.start",
"implementation": StartTimerAction,
"depends": [
"now"
],
"category": "contextual",
"name": "Start",
"cssClass": "icon-play",
"priority": "preferred"
},
{
"key": "timer.pause",
"implementation": PauseTimerAction,
"depends": [
"now"
],
"category": "contextual",
"name": "Pause",
"cssClass": "icon-pause",
"priority": "preferred"
},
{
"key": "timer.restart",
"implementation": RestartTimerAction,
"depends": [
"now"
],
"category": "contextual",
"name": "Restart at 0",
"cssClass": "icon-refresh",
"priority": "preferred"
},
{
"key": "timer.stop",
"implementation": StopTimerAction,
"depends": [
"now"
],
"category": "contextual",
"name": "Stop",
"cssClass": "icon-box-round-corners",
"priority": "preferred"
}
],
"types": [
{
"key": "timer",
"name": "Timer",
"cssClass": "icon-timer",
"description": "A timer that counts up or down to a datetime. Timers can be started, stopped and reset whenever needed, and support a variety of display formats. Each Timer displays the same value to all users. Timers can be added to Display Layouts.",
"priority": 100,
"features": [
"creation"
],
"properties": [
{
"key": "timestamp",
"control": "datetime",
"name": "Target"
},
{
"key": "timerFormat",
"control": "select",
"name": "Display Format",
"options": [
{
"value": "long",
"name": "DDD hh:mm:ss"
},
{
"value": "short",
"name": "hh:mm:ss"
}
]
}
],
"model": {
"timerFormat": "DDD hh:mm:ss"
}
}
],
"runs": [],
"licenses": [
{
"name": "moment-duration-format",
"version": "1.3.0",
"author": "John Madhavan-Reese",
"description": "Duration parsing/formatting",
"website": "https://github.com/jsmreese/moment-duration-format",
"copyright": "Copyright 2014 John Madhavan-Reese",
"license": "license-mit",
"link": "https://github.com/jsmreese/moment-duration-format/blob/master/LICENSE"
}
]
}
}
};
});

View File

@ -1,37 +0,0 @@
<!--
Open MCT, Copyright (c) 2009-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="c-timer u-style-receiver js-style-receiver is-{{timer.timerState}}" ng-controller="TimerController as timer">
<div class="c-timer__controls">
<button ng-click="timer.clickStopButton()"
ng-hide="timer.timerState == 'stopped'"
title="Reset"
class="c-timer__ctrl-reset c-icon-button c-icon-button--major icon-reset"></button>
<button ng-click="timer.clickButton()"
title="{{timer.buttonText()}}"
class="c-timer__ctrl-pause-play c-icon-button c-icon-button--major {{timer.buttonCssClass()}}"></button>
</div>
<div class="c-timer__direction {{timer.signClass()}}"
ng-hide="!timer.signClass()"></div>
<div class="c-timer__value">{{timer.text() || "--:--:--"}}
</div>
<span class="c-timer__ng-controller u-contents" ng-controller="RefreshingController"></span>
</div>

View File

@ -1,70 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Implements the "Pause" action for timers.
*
* Sets the reference pausedTime in a timer to the current
* time, such that it stops counting up.
*
* @implements {Action}
* @memberof platform/features/clock
* @constructor
* @param {Function} now a function which returns the current
* time (typically wrapping `Date.now`)
* @param {ActionContext} context the context for this action
*/
function PauseTimerAction(now, context) {
this.domainObject = context.domainObject;
this.now = now;
}
PauseTimerAction.appliesTo = function (context) {
var model =
(context.domainObject && context.domainObject.getModel())
|| {};
// We show this variant for timers which have
// a target time, or is in a playing state.
return model.type === 'timer'
&& model.timerState === 'started';
};
PauseTimerAction.prototype.perform = function () {
var domainObject = this.domainObject,
now = this.now;
function updateModel(model) {
model.timerState = 'paused';
model.pausedTime = now();
}
return domainObject.useCapability('mutation', updateModel);
};
return PauseTimerAction;
}
);

View File

@ -1,70 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Implements the "Restart at 0" action.
*
* Behaves the same as (and delegates functionality to)
* the "Start" action.
*
* @implements {Action}
* @memberof platform/features/clock
* @constructor
* @param {Function} now a function which returns the current
* time (typically wrapping `Date.now`)
* @param {ActionContext} context the context for this action
*/
function RestartTimerAction(now, context) {
this.domainObject = context.domainObject;
this.now = now;
}
RestartTimerAction.appliesTo = function (context) {
var model =
(context.domainObject && context.domainObject.getModel())
|| {};
// We show this variant for timers which already have a target time.
return model.type === 'timer'
&& model.timerState !== 'stopped';
};
RestartTimerAction.prototype.perform = function () {
var domainObject = this.domainObject,
now = this.now;
function updateModel(model) {
model.timestamp = now();
model.timerState = 'started';
model.pausedTime = undefined;
}
return domainObject.useCapability('mutation', updateModel);
};
return RestartTimerAction;
}
);

View File

@ -1,78 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Implements the "Start" action for timers.
*
* Sets the reference timestamp in a timer to the current
* time, such that it begins counting up.
*
* @implements {Action}
* @memberof platform/features/clock
* @constructor
* @param {Function} now a function which returns the current
* time (typically wrapping `Date.now`)
* @param {ActionContext} context the context for this action
*/
function StartTimerAction(now, context) {
this.domainObject = context.domainObject;
this.now = now;
}
StartTimerAction.appliesTo = function (context) {
var model =
(context.domainObject && context.domainObject.getModel())
|| {};
// We show this variant for timers which do not yet have
// a target time.
return model.type === 'timer'
&& model.timerState !== 'started';
};
StartTimerAction.prototype.perform = function () {
var domainObject = this.domainObject,
now = this.now;
function updateModel(model) {
//if we are resuming
if (model.pausedTime) {
var timeShift = now() - model.pausedTime;
model.timestamp = model.timestamp + timeShift;
} else {
model.timestamp = now();
}
model.timerState = 'started';
model.pausedTime = undefined;
}
return domainObject.useCapability('mutation', updateModel);
};
return StartTimerAction;
}
);

View File

@ -1,70 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Implements the "Stop" action for timers.
*
* Sets the reference timestamp in a timer undefined,
* such that it is reset and makes no movements.
*
* @implements {Action}
* @memberof platform/features/clock
* @constructor
* @param {Function} now a function which returns the current
* time (typically wrapping `Date.now`)
* @param {ActionContext} context the context for this action
*/
function StopTimerAction(now, context) {
this.domainObject = context.domainObject;
this.now = now;
}
StopTimerAction.appliesTo = function (context) {
var model =
(context.domainObject && context.domainObject.getModel())
|| {};
// We show this variant for timers which do not yet have
// a target time.
return model.type === 'timer'
&& model.timerState !== 'stopped';
};
StopTimerAction.prototype.perform = function () {
var domainObject = this.domainObject;
function updateModel(model) {
model.timestamp = undefined;
model.timerState = 'stopped';
model.pausedTime = undefined;
}
return domainObject.useCapability('mutation', updateModel);
};
return StopTimerAction;
}
);

View File

@ -1,55 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Continually refreshes the represented domain object.
*
* This is a short-term workaround to assure Timer views stay
* up-to-date; should be replaced by a global auto-refresh.
*
* @constructor
* @memberof platform/features/clock
* @param {angular.Scope} $scope the Angular scope
* @param {platform/features/clock.TickerService} tickerService
* a service used to align behavior with clock ticks
*/
function RefreshingController($scope, tickerService) {
var unlisten;
function triggerRefresh() {
var persistence = $scope.domainObject
&& $scope.domainObject.getCapability('persistence');
return persistence && persistence.refresh();
}
unlisten = tickerService.listen(triggerRefresh);
$scope.$on('$destroy', unlisten);
}
return RefreshingController;
}
);

View File

@ -1,239 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['./TimerFormatter'],
function (TimerFormatter) {
var FORMATTER = new TimerFormatter();
/**
* Controller for views of a Timer domain object.
*
* @constructor
* @memberof platform/features/clock
* @param {angular.Scope} $scope the Angular scope
* @param $window Angular-provided window object
* @param {Function} now a function which returns the current
* time (typically wrapping `Date.now`)
*/
function TimerController($scope, $window, now) {
var formatter,
active = true,
relativeTimestamp,
lastTimestamp,
relativeTimerState,
self = this;
function update() {
var timeDelta = lastTimestamp - relativeTimestamp;
if (formatter && !isNaN(timeDelta)) {
self.textValue = formatter(timeDelta);
self.signValue = timeDelta < 0 ? "-"
: timeDelta >= 1000 ? "+" : "";
self.signCssClass = timeDelta < 0 ? "icon-minus"
: timeDelta >= 1000 ? "icon-plus" : "";
} else {
self.textValue = "";
self.signValue = "";
self.signCssClass = "";
}
}
function updateFormat(key) {
formatter = FORMATTER[key] || FORMATTER.long;
}
function updateTimestamp(timestamp) {
relativeTimestamp = timestamp;
}
function updateTimerState(timerState) {
self.timerState = relativeTimerState = timerState;
}
function updateActions(actionCapability, actionKey) {
self.relevantAction = actionCapability
&& actionCapability.getActions(actionKey)[0];
self.stopAction = relativeTimerState !== 'stopped'
? actionCapability && actionCapability.getActions('timer.stop')[0] : undefined;
}
function isPaused() {
return relativeTimerState === 'paused';
}
function handleLegacyTimer(model) {
if (model.timerState === undefined) {
model.timerState = model.timestamp === undefined
? 'stopped' : 'started';
}
}
function updateObject(domainObject) {
var model = domainObject.getModel();
handleLegacyTimer(model);
var timestamp = model.timestamp,
formatKey = model.timerFormat,
timerState = model.timerState,
actionCapability = domainObject.getCapability('action'),
actionKey = (timerState !== 'started')
? 'timer.start' : 'timer.pause';
updateFormat(formatKey);
updateTimestamp(timestamp);
updateTimerState(timerState);
updateActions(actionCapability, actionKey);
//if paused on startup show last known position
if (isPaused() && !lastTimestamp) {
lastTimestamp = model.pausedTime;
}
update();
}
function handleObjectChange(domainObject) {
if (domainObject) {
updateObject(domainObject);
}
}
function handleModification() {
handleObjectChange($scope.domainObject);
}
function tick() {
var lastSign = self.signValue,
lastText = self.textValue;
if (!isPaused()) {
lastTimestamp = now();
update();
}
if (relativeTimerState === undefined) {
handleModification();
}
// We're running in an animation frame, not in a digest cycle.
// We need to trigger a digest cycle if our displayable data
// changes.
if (lastSign !== self.signValue || lastText !== self.textValue) {
$scope.$apply();
}
if (active) {
$window.requestAnimationFrame(tick);
}
}
$window.requestAnimationFrame(tick);
// Pull in the timer format from the domain object model
$scope.$watch('domainObject', handleObjectChange);
$scope.$watch('model.modified', handleModification);
// When the scope is destroyed, stop requesting anim. frames
$scope.$on('$destroy', function () {
active = false;
});
this.$scope = $scope;
this.signValue = '';
this.textValue = '';
this.updateObject = updateObject;
}
/**
* Get the CSS class to display the right icon
* for the start/pause button.
* @returns {string} cssclass to display
*/
TimerController.prototype.buttonCssClass = function () {
return this.relevantAction
? this.relevantAction.getMetadata().cssClass : "";
};
/**
* Get the text to show for the start/pause button
* (e.g. in a tooltip)
* @returns {string} name of the action
*/
TimerController.prototype.buttonText = function () {
return this.relevantAction
? this.relevantAction.getMetadata().name : "";
};
/**
* Perform the action associated with the start/pause button.
*/
TimerController.prototype.clickButton = function () {
if (this.relevantAction) {
this.relevantAction.perform();
this.updateObject(this.$scope.domainObject);
}
};
/**
* Perform the action associated with the stop button.
*/
TimerController.prototype.clickStopButton = function () {
if (this.stopAction) {
this.stopAction.perform();
this.updateObject(this.$scope.domainObject);
}
};
/**
* Get the sign (+ or -) of the current timer value, as
* displayable text.
* @returns {string} sign of the current timer value
*/
TimerController.prototype.sign = function () {
return this.signValue;
};
/**
* Get the sign (+ or -) of the current timer value, as
* a CSS class.
* @returns {string} sign of the current timer value
*/
TimerController.prototype.signClass = function () {
return this.signCssClass;
};
/**
* Get the text to display for the current timer value.
* @returns {string} current timer value
*/
TimerController.prototype.text = function () {
return this.textValue;
};
return TimerController;
}
);

View File

@ -1,73 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['moment', 'moment-duration-format'],
function (moment) {
var SHORT_FORMAT = "HH:mm:ss",
LONG_FORMAT = "d[D] HH:mm:ss";
/**
* Provides formatting functions for Timers.
*
* Display formats for timers are a little different from what
* moment.js provides, so we have custom logic here. This specifically
* supports `TimerController`.
*
* @constructor
* @memberof platform/features/clock
*/
function TimerFormatter() {
}
// Round this timestamp down to the second boundary
// (e.g. 1124ms goes down to 1000ms, -2400ms goes down to -3000ms)
function toWholeSeconds(duration) {
return Math.abs(Math.floor(duration / 1000) * 1000);
}
/**
* Format a duration for display, using the short form.
* (e.g. 03:33:11)
* @param {number} duration the duration, in milliseconds
* @param {boolean} sign true if positive
*/
TimerFormatter.prototype.short = function (duration) {
return moment.duration(toWholeSeconds(duration), 'ms')
.format(SHORT_FORMAT, { trim: false });
};
/**
* Format a duration for display, using the long form.
* (e.g. 0d 03:33:11)
* @param {number} duration the duration, in milliseconds
* @param {boolean} sign true if positive
*/
TimerFormatter.prototype.long = function (duration) {
return moment.duration(toWholeSeconds(duration), 'ms')
.format(LONG_FORMAT, { trim: false });
};
return TimerFormatter;
}
);

View File

@ -1,87 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Calls functions every second, as close to the actual second
* tick as is feasible.
* @constructor
* @memberof platform/features/clock
* @param $timeout Angular's $timeout
* @param {Function} now function to provide the current time in ms
*/
function TickerService($timeout, now) {
var self = this;
function tick() {
var timestamp = now(),
millis = timestamp % 1000;
// Only update callbacks if a second has actually passed.
if (timestamp >= self.last + 1000) {
self.callbacks.forEach(function (callback) {
callback(timestamp);
});
self.last = timestamp - millis;
}
// Try to update at exactly the next second
$timeout(tick, 1000 - millis, true);
}
tick();
this.callbacks = [];
this.last = now() - 1000;
}
/**
* Listen for clock ticks. The provided callback will
* be invoked with the current timestamp (in milliseconds
* since Jan 1 1970) at regular intervals, as near to the
* second boundary as possible.
*
* @param {Function} callback callback to invoke
* @returns {Function} a function to unregister this listener
*/
TickerService.prototype.listen = function (callback) {
var self = this;
self.callbacks.push(callback);
// Provide immediate feedback
callback(this.last);
// Provide a deregistration function
return function () {
self.callbacks = self.callbacks.filter(function (cb) {
return cb !== callback;
});
};
};
return TickerService;
}
);

View File

@ -1,113 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['EventEmitter'], function (EventEmitter) {
/**
* Tracks the currently-followed Timer object. Used by
* timelines et al to synchronize to a particular timer.
*
* The TimerService emits `change` events when the active timer
* is changed.
*/
function TimerService(openmct) {
EventEmitter.apply(this);
this.time = openmct.time;
this.objects = openmct.objects;
}
TimerService.prototype = Object.create(EventEmitter.prototype);
/**
* Set (or clear, if `timer` is undefined) the currently active timer.
* @param {DomainObject} timer the new active timer
* @emits change
*/
TimerService.prototype.setTimer = function (timer) {
this.timer = timer;
this.emit('change', timer);
if (this.stopObserving) {
this.stopObserving();
delete this.stopObserving;
}
if (timer) {
this.stopObserving =
this.objects.observe(timer, '*', this.setTimer.bind(this));
}
};
/**
* Get the currently active timer.
* @return {DomainObject} the active timer
* @emits change
*/
TimerService.prototype.getTimer = function () {
return this.timer;
};
/**
* Check if there is a currently active timer.
* @return {boolean} true if there is a timer
*/
TimerService.prototype.hasTimer = function () {
return Boolean(this.timer);
};
/**
* Convert the provided timestamp to milliseconds relative to
* the active timer.
* @return {number} milliseconds since timer start
*/
TimerService.prototype.convert = function (timestamp) {
var clock = this.time.clock();
var canConvert = this.hasTimer()
&& Boolean(clock)
&& this.timer.timerState !== 'stopped';
if (!canConvert) {
return undefined;
}
var now = clock.currentValue();
var delta = this.timer.timerState === 'paused'
? now - this.timer.pausedTime : 0;
var epoch = this.timer.timestamp;
return timestamp - epoch - delta;
};
/**
* Get the value of the active clock, adjusted to be relative to the active
* timer. If there is no clock or no active timer, this will return
* `undefined`.
* @return {number} milliseconds since the start of the active timer
*/
TimerService.prototype.now = function () {
var clock = this.time.clock();
return clock && this.convert(clock.currentValue());
};
return TimerService;
});

View File

@ -1,106 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/actions/PauseTimerAction"],
function (PauseTimerAction) {
describe("A timer's Pause action", function () {
var mockNow,
mockDomainObject,
testModel,
testContext,
action;
function asPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return asPromise(callback(value));
}
};
}
function testState(type, timerState, timestamp, expected) {
testModel.type = type;
testModel.timerState = timerState;
testModel.timestamp = timestamp;
if (expected) {
expect(PauseTimerAction.appliesTo(testContext)).toBeTruthy();
} else {
expect(PauseTimerAction.appliesTo(testContext)).toBeFalsy();
}
}
beforeEach(function () {
mockNow = jasmine.createSpy('now');
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getCapability', 'useCapability', 'getModel']
);
mockDomainObject.useCapability.and.callFake(function (c, v) {
if (c === 'mutation') {
testModel = v(testModel) || testModel;
return asPromise(true);
}
});
mockDomainObject.getModel.and.callFake(function () {
return testModel;
});
testModel = {};
testContext = {domainObject: mockDomainObject};
action = new PauseTimerAction(mockNow, testContext);
});
it("updates the model with a timerState", function () {
testModel.timerState = 'started';
action.perform();
expect(testModel.timerState).toEqual('paused');
});
it("updates the model with a pausedTime", function () {
testModel.pausedTime = undefined;
mockNow.and.returnValue(12000);
action.perform();
expect(testModel.pausedTime).toEqual(12000);
});
it("applies only to timers in a playing state", function () {
//in a stopped state
testState('timer', 'stopped', undefined, false);
//in a paused state
testState('timer', 'paused', 12000, false);
//in a playing state
testState('timer', 'started', 12000, true);
//not a timer
testState('clock', 'started', 12000, false);
});
});
}
);

View File

@ -1,112 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/actions/RestartTimerAction"],
function (RestartTimerAction) {
describe("A timer's restart action", function () {
var mockNow,
mockDomainObject,
testModel,
testContext,
action;
function asPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return asPromise(callback(value));
}
};
}
function testState(type, timerState, timestamp, expected) {
testModel.type = type;
testModel.timerState = timerState;
testModel.timestamp = timestamp;
if (expected) {
expect(RestartTimerAction.appliesTo(testContext)).toBeTruthy();
} else {
expect(RestartTimerAction.appliesTo(testContext)).toBeFalsy();
}
}
beforeEach(function () {
mockNow = jasmine.createSpy('now');
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getCapability', 'useCapability', 'getModel']
);
mockDomainObject.useCapability.and.callFake(function (c, v) {
if (c === 'mutation') {
testModel = v(testModel) || testModel;
return asPromise(true);
}
});
mockDomainObject.getModel.and.callFake(function () {
return testModel;
});
testModel = {};
testContext = { domainObject: mockDomainObject };
action = new RestartTimerAction(mockNow, testContext);
});
it("updates the model with a timestamp", function () {
testModel.pausedTime = 12000;
mockNow.and.returnValue(12000);
action.perform();
expect(testModel.timestamp).toEqual(12000);
});
it("updates the model with a pausedTime", function () {
testModel.pausedTime = 12000;
action.perform();
expect(testModel.pausedTime).toEqual(undefined);
});
it("updates the model with a timerState", function () {
testModel.timerState = 'stopped';
action.perform();
expect(testModel.timerState).toEqual('started');
});
it("applies only to timers in a non-stopped state", function () {
//in a stopped state
testState('timer', 'stopped', undefined, false);
//in a paused state
testState('timer', 'paused', 12000, true);
//in a playing state
testState('timer', 'started', 12000, true);
//not a timer
testState('clock', 'paused', 12000, false);
});
});
}
);

View File

@ -1,111 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/actions/StartTimerAction"],
function (StartTimerAction) {
describe("A timer's start action", function () {
var mockNow,
mockDomainObject,
testModel,
testContext,
action;
function asPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return asPromise(callback(value));
}
};
}
function testState(type, timerState, timestamp, expected) {
testModel.type = type;
testModel.timerState = timerState;
testModel.timestamp = timestamp;
if (expected) {
expect(StartTimerAction.appliesTo(testContext)).toBeTruthy();
} else {
expect(StartTimerAction.appliesTo(testContext)).toBeFalsy();
}
}
beforeEach(function () {
mockNow = jasmine.createSpy('now');
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getCapability', 'useCapability', 'getModel']
);
mockDomainObject.useCapability.and.callFake(function (c, v) {
if (c === 'mutation') {
testModel = v(testModel) || testModel;
return asPromise(true);
}
});
mockDomainObject.getModel.and.callFake(function () {
return testModel;
});
testModel = {};
testContext = {domainObject: mockDomainObject};
action = new StartTimerAction(mockNow, testContext);
});
it("updates the model with a timestamp", function () {
mockNow.and.returnValue(12000);
action.perform();
expect(testModel.timestamp).toEqual(12000);
});
it("updates the model with a pausedTime", function () {
testModel.pausedTime = 12000;
action.perform();
expect(testModel.pausedTime).toEqual(undefined);
});
it("updates the model with a timerState", function () {
testModel.timerState = undefined;
action.perform();
expect(testModel.timerState).toEqual('started');
});
it("applies only to timers not in a playing state", function () {
//in a stopped state
testState('timer', 'stopped', undefined, true);
//in a paused state
testState('timer', 'paused', 12000, true);
//in a playing state
testState('timer', 'started', 12000, false);
//not a timer
testState('clock', 'paused', 12000, false);
});
});
}
);

View File

@ -1,111 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/actions/StopTimerAction"],
function (StopTimerAction) {
describe("A timer's stop action", function () {
var mockNow,
mockDomainObject,
testModel,
testContext,
action;
function asPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return asPromise(callback(value));
}
};
}
function testState(type, timerState, timestamp, expected) {
testModel.type = type;
testModel.timerState = timerState;
testModel.timestamp = timestamp;
if (expected) {
expect(StopTimerAction.appliesTo(testContext)).toBeTruthy();
} else {
expect(StopTimerAction.appliesTo(testContext)).toBeFalsy();
}
}
beforeEach(function () {
mockNow = jasmine.createSpy('now');
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getCapability', 'useCapability', 'getModel']
);
mockDomainObject.useCapability.and.callFake(function (c, v) {
if (c === 'mutation') {
testModel = v(testModel) || testModel;
return asPromise(true);
}
});
mockDomainObject.getModel.and.callFake(function () {
return testModel;
});
testModel = {};
testContext = {domainObject: mockDomainObject};
action = new StopTimerAction(mockNow, testContext);
});
it("updates the model with a timestamp", function () {
mockNow.and.returnValue(12000);
action.perform();
expect(testModel.timestamp).toEqual(undefined);
});
it("updates the model with a pausedTime", function () {
testModel.pausedTime = 12000;
action.perform();
expect(testModel.pausedTime).toEqual(undefined);
});
it("updates the model with a timerState", function () {
testModel.timerState = 'started';
action.perform();
expect(testModel.timerState).toEqual('stopped');
});
it("applies only to timers in a non-stopped state", function () {
//in a stopped state
testState('timer', 'stopped', undefined, false);
//in a paused state
testState('timer', 'paused', 12000, true);
//in a playing state
testState('timer', 'started', 12000, true);
//not a timer
testState('clock', 'paused', 12000, false);
});
});
}
);

View File

@ -1,80 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/controllers/RefreshingController"],
function (RefreshingController) {
describe("The refreshing controller", function () {
var mockScope,
mockTicker,
mockUnticker,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj('$scope', ['$on']);
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.and.returnValue(mockUnticker);
controller = new RefreshingController(mockScope, mockTicker);
});
it("refreshes the represented object on every tick", function () {
var mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getCapability']
),
mockPersistence = jasmine.createSpyObj(
'persistence',
['persist', 'refresh']
);
mockDomainObject.getCapability.and.callFake(function (c) {
return (c === 'persistence') && mockPersistence;
});
mockScope.domainObject = mockDomainObject;
mockTicker.listen.calls.mostRecent().args[0](12321);
expect(mockPersistence.refresh).toHaveBeenCalled();
expect(mockPersistence.persist).not.toHaveBeenCalled();
});
it("subscribes to clock ticks", function () {
expect(mockTicker.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("unsubscribes to ticks when destroyed", function () {
// Make sure $destroy is being listened for...
expect(mockScope.$on.calls.mostRecent().args[0]).toEqual('$destroy');
expect(mockUnticker).not.toHaveBeenCalled();
// ...and makes sure that its listener unsubscribes from ticker
mockScope.$on.calls.mostRecent().args[1]();
expect(mockUnticker).toHaveBeenCalled();
});
});
}
);

View File

@ -1,227 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/controllers/TimerController"],
function (TimerController) {
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000;
describe("A timer view's controller", function () {
var mockScope,
mockWindow,
mockNow,
mockDomainObject,
mockActionCapability,
mockStart,
mockPause,
mockStop,
testModel,
controller;
function invokeWatch(expr, value) {
mockScope.$watch.calls.all().forEach(function (call) {
if (call.args[0] === expr) {
call.args[1](value);
}
});
}
beforeEach(function () {
mockScope = jasmine.createSpyObj(
'$scope',
['$watch', '$on', '$apply']
);
mockWindow = jasmine.createSpyObj(
'$window',
['requestAnimationFrame']
);
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getCapability', 'useCapability', 'getModel']
);
mockActionCapability = jasmine.createSpyObj(
'action',
['getActions']
);
mockStart = jasmine.createSpyObj(
'start',
['getMetadata', 'perform']
);
mockPause = jasmine.createSpyObj(
'paused',
['getMetadata', 'perform']
);
mockStop = jasmine.createSpyObj(
'stopped',
['getMetadata', 'perform']
);
mockNow = jasmine.createSpy('now');
mockDomainObject.getCapability.and.callFake(function (c) {
return (c === 'action') && mockActionCapability;
});
mockDomainObject.getModel.and.callFake(function () {
return testModel;
});
mockActionCapability.getActions.and.callFake(function (k) {
return [{
'timer.start': mockStart,
'timer.pause': mockPause,
'timer.stop': mockStop
}[k]];
});
mockStart.getMetadata.and.returnValue({
cssClass: "icon-play",
name: "Start"
});
mockPause.getMetadata.and.returnValue({
cssClass: "icon-pause",
name: "Pause"
});
mockStop.getMetadata.and.returnValue({
cssClass: "icon-box-round-corners",
name: "Stop"
});
mockScope.domainObject = mockDomainObject;
testModel = {};
controller = new TimerController(mockScope, mockWindow, mockNow);
});
it("watches for the domain object in view", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"domainObject",
jasmine.any(Function)
);
});
it("watches for domain object modifications", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"model.modified",
jasmine.any(Function)
);
});
it("updates on a timer", function () {
expect(mockWindow.requestAnimationFrame)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("displays nothing when there is no target", function () {
// Notify that domain object is available via scope
invokeWatch('domainObject', mockDomainObject);
mockNow.and.returnValue(TEST_TIMESTAMP);
mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
expect(controller.sign()).toEqual("");
expect(controller.signClass()).toEqual("");
expect(controller.text()).toEqual("");
});
it("formats time to display relative to target", function () {
testModel.timestamp = TEST_TIMESTAMP;
testModel.timerFormat = 'long';
// Notify that domain object is available via scope
invokeWatch('domainObject', mockDomainObject);
mockNow.and.returnValue(TEST_TIMESTAMP + 121000);
mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
expect(controller.sign()).toEqual("+");
expect(controller.signClass()).toEqual("icon-plus");
expect(controller.text()).toEqual("0D 00:02:01");
mockNow.and.returnValue(TEST_TIMESTAMP - 121000);
mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
expect(controller.sign()).toEqual("-");
expect(controller.signClass()).toEqual("icon-minus");
expect(controller.text()).toEqual("0D 00:02:01");
mockNow.and.returnValue(TEST_TIMESTAMP);
mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
expect(controller.sign()).toEqual("");
expect(controller.signClass()).toEqual("");
expect(controller.text()).toEqual("0D 00:00:00");
});
it("shows cssClass & name for the applicable start/pause action", function () {
invokeWatch('domainObject', mockDomainObject);
expect(controller.buttonCssClass()).toEqual("icon-play");
expect(controller.buttonText()).toEqual("Start");
testModel.timestamp = 12321;
testModel.timerState = 'started';
invokeWatch('model.modified', 1);
expect(controller.buttonCssClass()).toEqual("icon-pause");
expect(controller.buttonText()).toEqual("Pause");
});
it("performs correct start/pause/stop action on click", function () {
//test start
invokeWatch('domainObject', mockDomainObject);
expect(mockStart.perform).not.toHaveBeenCalled();
controller.clickButton();
expect(mockStart.perform).toHaveBeenCalled();
//test pause
testModel.timestamp = 12321;
testModel.timerState = 'started';
invokeWatch('model.modified', 1);
expect(mockPause.perform).not.toHaveBeenCalled();
controller.clickButton();
expect(mockPause.perform).toHaveBeenCalled();
//test stop
expect(mockStop.perform).not.toHaveBeenCalled();
controller.clickStopButton();
expect(mockStop.perform).toHaveBeenCalled();
});
it("stops requesting animation frames when destroyed", function () {
var initialCount = mockWindow.requestAnimationFrame.calls.count();
// First, check that normally new frames keep getting requested
mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
expect(mockWindow.requestAnimationFrame.calls.count())
.toEqual(initialCount + 1);
mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
expect(mockWindow.requestAnimationFrame.calls.count())
.toEqual(initialCount + 2);
// Now, verify that it stops after $destroy
expect(mockScope.$on.calls.mostRecent().args[0])
.toEqual('$destroy');
mockScope.$on.calls.mostRecent().args[1]();
// Frames should no longer get requested
mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
expect(mockWindow.requestAnimationFrame.calls.count())
.toEqual(initialCount + 2);
mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
expect(mockWindow.requestAnimationFrame.calls.count())
.toEqual(initialCount + 2);
});
});
}
);

View File

@ -1,111 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/controllers/TimerFormatter"],
function (TimerFormatter) {
var MS_IN_SEC = 1000,
MS_IN_MIN = MS_IN_SEC * 60,
MS_IN_HR = MS_IN_MIN * 60,
MS_IN_DAY = MS_IN_HR * 24;
describe("The timer value formatter", function () {
var formatter = new TimerFormatter();
function sum(a, b) {
return a + b;
}
function toDuration(days, hours, mins, secs) {
return [
days * MS_IN_DAY,
hours * MS_IN_HR,
mins * MS_IN_MIN,
secs * MS_IN_SEC
].reduce(sum, 0);
}
it("formats short-form values (no days)", function () {
expect(formatter.short(toDuration(0, 123, 2, 3) + 123))
.toEqual("123:02:03");
});
it("formats negative short-form values (no days)", function () {
expect(formatter.short(-toDuration(0, 123, 2, 3) + 123))
.toEqual("123:02:03");
});
it("formats long-form values (with days)", function () {
expect(formatter.long(toDuration(0, 123, 2, 3) + 123))
.toEqual("5D 03:02:03");
});
it("formats negative long-form values (no days)", function () {
expect(formatter.long(-toDuration(0, 123, 2, 3) + 123))
.toEqual("5D 03:02:03");
});
it("rounds seconds down for positive durations", function () {
expect(formatter.short(MS_IN_SEC + 600))
.toEqual("00:00:01");
});
it("rounds seconds up for negative durations", function () {
expect(formatter.short(-MS_IN_SEC - 600))
.toEqual("00:00:02");
});
it("short-formats correctly around negative time borders", function () {
expect(formatter.short(-1)).toEqual("00:00:01");
expect(formatter.short(-1000)).toEqual("00:00:01");
expect(formatter.short(-1001)).toEqual("00:00:02");
expect(formatter.short(-2000)).toEqual("00:00:02");
expect(formatter.short(-59001)).toEqual("00:01:00");
expect(formatter.short(-60000)).toEqual("00:01:00");
expect(formatter.short(-MS_IN_HR + 999)).toEqual("01:00:00");
expect(formatter.short(-MS_IN_HR)).toEqual("01:00:00");
});
it("differentiates between values around zero", function () {
// These are more than 1000 ms apart so should not appear
// as the same second
expect(formatter.short(-999))
.not.toEqual(formatter.short(999));
});
it("handles negative days", function () {
expect(formatter.long(-10 * MS_IN_DAY))
.toEqual("10D 00:00:00");
expect(formatter.long(-10 * MS_IN_DAY + 100))
.toEqual("10D 00:00:00");
expect(formatter.long(-10 * MS_IN_DAY + 999))
.toEqual("10D 00:00:00");
expect(formatter.short(-10 * MS_IN_DAY + 100))
.toEqual("240:00:00");
});
});
}
);

View File

@ -1,62 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/services/TickerService"],
function (TickerService) {
var TEST_TIMESTAMP = 1433354174000;
describe("The ticker service", function () {
var mockTimeout,
mockNow,
mockCallback,
tickerService;
beforeEach(function () {
mockTimeout = jasmine.createSpy('$timeout');
mockNow = jasmine.createSpy('now');
mockCallback = jasmine.createSpy('callback');
mockNow.and.returnValue(TEST_TIMESTAMP);
tickerService = new TickerService(mockTimeout, mockNow);
});
it("notifies listeners of clock ticks", function () {
tickerService.listen(mockCallback);
mockNow.and.returnValue(TEST_TIMESTAMP + 12321);
mockTimeout.calls.mostRecent().args[0]();
expect(mockCallback)
.toHaveBeenCalledWith(TEST_TIMESTAMP + 12321);
});
it("allows listeners to unregister", function () {
tickerService.listen(mockCallback)(); // Unregister immediately
mockNow.and.returnValue(TEST_TIMESTAMP + 12321);
mockTimeout.calls.mostRecent().args[0]();
expect(mockCallback).not
.toHaveBeenCalledWith(TEST_TIMESTAMP + 12321);
});
});
}
);

View File

@ -1,77 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'../../src/services/TimerService'
], function (TimerService) {
describe("TimerService", function () {
var callback;
var mockmct;
var timerService;
beforeEach(function () {
callback = jasmine.createSpy('callback');
mockmct = {
time: { clock: jasmine.createSpy('clock') },
objects: { observe: jasmine.createSpy('observe') }
};
timerService = new TimerService(mockmct);
timerService.on('change', callback);
});
it("initially emits no change events", function () {
expect(callback).not.toHaveBeenCalled();
});
it("reports no current timer", function () {
expect(timerService.getTimer()).toBeUndefined();
});
describe("setTimer", function () {
var testTimer;
beforeEach(function () {
testTimer = { name: "I am some timer; you are nobody." };
timerService.setTimer(testTimer);
});
it("emits a change event", function () {
expect(callback).toHaveBeenCalled();
});
it("reports the current timer", function () {
expect(timerService.getTimer()).toBe(testTimer);
});
it("observes changes to an object", function () {
var newTimer = { name: "I am another timer." };
expect(mockmct.objects.observe).toHaveBeenCalledWith(
testTimer,
'*',
jasmine.any(Function)
);
mockmct.objects.observe.calls.mostRecent().args[2](newTimer);
expect(timerService.getTimer()).toBe(newTimer);
});
});
});
});

View File

@ -473,6 +473,23 @@ ObjectAPI.prototype.mutate = function (domainObject, path, value) {
}
};
/**
* Updates a domain object based on its latest persisted state. Note that this will mutate the provided object.
* @param {module:openmct.DomainObject} domainObject an object to refresh from its persistence store
* @returns {Promise} the provided object, updated to reflect the latest persisted state of the object.
*/
ObjectAPI.prototype.refresh = async function (domainObject) {
const refreshedObject = await this.get(domainObject.identifier);
if (domainObject.isMutable) {
domainObject.$refresh(refreshedObject);
} else {
utils.refresh(domainObject, refreshedObject);
}
return domainObject;
};
/**
* @private
*/

View File

@ -37,7 +37,6 @@ const DEFAULTS = [
'platform/execution',
'platform/exporters',
'platform/telemetry',
'platform/features/clock',
'platform/forms',
'platform/identity',
'platform/persistence/aggregator',
@ -77,7 +76,6 @@ define([
'../platform/entanglement/bundle',
'../platform/execution/bundle',
'../platform/exporters/bundle',
'../platform/features/clock/bundle',
'../platform/features/static-markup/bundle',
'../platform/forms/bundle',
'../platform/framework/bundle',

View File

@ -41,6 +41,7 @@
<script>
import moment from 'moment';
import momentTimezone from 'moment-timezone';
import ticker from 'utils/clock/Ticker';
export default {
inject: ['openmct', 'domainObject'],
@ -82,8 +83,7 @@ export default {
}
},
mounted() {
const TickerService = this.openmct.$injector.get('tickerService');
this.unlisten = TickerService.listen(this.tick);
this.unlisten = ticker.listen(this.tick);
},
beforeDestroy() {
if (this.unlisten) {

View File

@ -30,6 +30,7 @@
<script>
import moment from 'moment';
import ticker from 'utils/clock/Ticker';
export default {
inject: ['openmct'],
@ -45,10 +46,7 @@ export default {
};
},
mounted() {
this.openmct.on('start', () => {
const TickerService = this.openmct.$injector.get('tickerService');
this.unlisten = TickerService.listen(this.tick);
});
this.unlisten = ticker.listen(this.tick);
},
beforeDestroy() {
if (this.unlisten) {

View File

@ -72,6 +72,7 @@ define([
'./timeline/plugin',
'./hyperlink/plugin',
'./clock/plugin',
'./timer/plugin',
'./DeviceClassifier/plugin'
], function (
_,
@ -125,6 +126,7 @@ define([
Timeline,
Hyperlink,
Clock,
Timer,
DeviceClassifier
) {
const bundleMap = {
@ -232,6 +234,7 @@ define([
plugins.Timeline = Timeline.default;
plugins.Hyperlink = Hyperlink.default;
plugins.Clock = Clock.default;
plugins.Timer = Timer.default;
plugins.DeviceClassifier = DeviceClassifier.default;
return plugins;

View File

@ -0,0 +1,65 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Timer from './components/Timer.vue';
import Vue from 'vue';
export default function TimerViewProvider(openmct) {
return {
key: 'timer.view',
name: 'Timer',
cssClass: 'icon-timer',
canView(domainObject) {
return domainObject.type === 'timer';
},
view: function (domainObject, objectPath) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
Timer
},
provide: {
openmct,
objectPath,
currentView: this
},
data() {
return {
domainObject
};
},
template: '<timer :domain-object="domainObject" />'
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
};
}
};
}

View File

@ -0,0 +1,56 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default class PauseTimerAction {
constructor(openmct) {
this.name = 'Pause';
this.key = 'timer.pause';
this.description = 'Pause the currently displayed timer';
this.group = 'view';
this.cssClass = 'icon-pause';
this.priority = 3;
this.openmct = openmct;
}
invoke(objectPath) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {
return new Error('Unable to run pause timer action. No domainObject provided.');
}
const newConfiguration = { ...domainObject.configuration };
newConfiguration.timerState = 'paused';
newConfiguration.pausedTime = new Date();
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
}
appliesTo(objectPath) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {
return;
}
const { timerState } = domainObject.configuration;
return domainObject.type === 'timer' && timerState === 'started';
}
}

View File

@ -0,0 +1,57 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default class RestartTimerAction {
constructor(openmct) {
this.name = 'Restart at 0';
this.key = 'timer.restart';
this.description = 'Restart the currently displayed timer';
this.group = 'view';
this.cssClass = 'icon-refresh';
this.priority = 2;
this.openmct = openmct;
}
invoke(objectPath) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {
return new Error('Unable to run restart timer action. No domainObject provided.');
}
const newConfiguration = { ...domainObject.configuration };
newConfiguration.timerState = 'started';
newConfiguration.timestamp = new Date();
newConfiguration.pausedTime = undefined;
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
}
appliesTo(objectPath) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {
return;
}
const { timerState } = domainObject.configuration;
return domainObject.type === 'timer' && timerState !== 'stopped';
}
}

View File

@ -0,0 +1,76 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import moment from 'moment';
export default class StartTimerAction {
constructor(openmct) {
this.name = 'Start';
this.key = 'timer.start';
this.description = 'Start the currently displayed timer';
this.group = 'view';
this.cssClass = 'icon-play';
this.priority = 3;
this.openmct = openmct;
}
invoke(objectPath) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {
return new Error('Unable to run start timer action. No domainObject provided.');
}
let { pausedTime, timestamp } = domainObject.configuration;
const newConfiguration = { ...domainObject.configuration };
if (pausedTime) {
pausedTime = moment(pausedTime);
}
if (timestamp) {
timestamp = moment(timestamp);
}
const now = moment(new Date());
if (pausedTime) {
const timeShift = moment.duration(now.diff(pausedTime));
const shiftedTime = timestamp.add(timeShift);
newConfiguration.timestamp = shiftedTime.toDate();
} else if (!timestamp) {
newConfiguration.timestamp = now.toDate();
}
newConfiguration.timerState = 'started';
newConfiguration.pausedTime = undefined;
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
}
appliesTo(objectPath) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {
return;
}
const { timerState } = domainObject.configuration;
return domainObject.type === 'timer' && timerState !== 'started';
}
}

View File

@ -0,0 +1,57 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default class StopTimerAction {
constructor(openmct) {
this.name = 'Stop';
this.key = 'timer.stop';
this.description = 'Stop the currently displayed timer';
this.group = 'view';
this.cssClass = 'icon-box-round-corners';
this.priority = 1;
this.openmct = openmct;
}
invoke(objectPath) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {
return new Error('Unable to run stop timer action. No domainObject provided.');
}
const newConfiguration = { ...domainObject.configuration };
newConfiguration.timerState = 'stopped';
newConfiguration.timestamp = undefined;
newConfiguration.pausedTime = undefined;
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
}
appliesTo(objectPath) {
const domainObject = objectPath[0];
if (!domainObject || !domainObject.configuration) {
return;
}
const { timerState } = domainObject.configuration;
return domainObject.type === 'timer' && timerState !== 'stopped';
}
}

View File

@ -0,0 +1,233 @@
<!--
Open MCT, Copyright (c) 2009-2021, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div
class="c-timer u-style-receiver js-style-receiver"
:class="[`is-${timerState}`]"
>
<div class="c-timer__controls">
<button
title="Reset"
class="c-timer__ctrl-reset c-icon-button c-icon-button--major icon-reset"
:class="[{'hide': timerState === 'stopped' }]"
@click="restartTimer"
></button>
<button :title="timerStateButtonText"
class="c-timer__ctrl-pause-play c-icon-button c-icon-button--major"
:class="[timerStateButtonIcon]"
@click="toggleStateButton"
></button>
</div>
<div
class="c-timer__direction"
:class="[{'hide': !timerSign }, `icon-${timerSign}`]"
></div>
<div class="c-timer__value">{{ timeTextValue || "--:--:--" }}</div>
</div>
</template>
<script>
import ticker from 'utils/clock/Ticker';
const moment = require("moment-timezone");
const momentDurationFormatSetup = require("moment-duration-format");
momentDurationFormatSetup(moment);
export default {
inject: ['openmct', 'currentView', 'objectPath'],
props: {
domainObject: {
type: Object,
required: true
}
},
data() {
return {
lastTimestamp: undefined,
active: true
};
},
computed: {
configuration() {
let configuration;
if (this.domainObject && this.domainObject.configuration) {
configuration = this.domainObject.configuration;
}
return configuration;
},
relativeTimestamp() {
let relativeTimestamp;
if (this.configuration && this.configuration.timestamp) {
relativeTimestamp = moment(this.configuration.timestamp).toDate();
} else if (this.configuration && this.configuration.timestamp === undefined) {
relativeTimestamp = undefined;
}
return relativeTimestamp;
},
timeDelta() {
return this.lastTimestamp - this.relativeTimestamp;
},
timeTextValue() {
if (isNaN(this.timeDelta)) {
return null;
}
const toWholeSeconds = Math.abs(Math.floor(this.timeDelta / 1000) * 1000);
return moment.duration(toWholeSeconds, 'ms').format(this.format, { trim: false });
},
pausedTime() {
let pausedTime;
if (this.configuration && this.configuration.pausedTime) {
pausedTime = moment(this.configuration.pausedTime).toDate();
} else if (this.configuration && this.configuration.pausedTime === undefined) {
pausedTime = undefined;
}
return pausedTime;
},
timerState() {
let timerState = 'started';
if (this.configuration && this.configuration.timerState) {
timerState = this.configuration.timerState;
}
return timerState;
},
timerStateButtonText() {
let buttonText = 'Pause';
if (['paused', 'stopped'].includes(this.timerState)) {
buttonText = 'Start';
}
return buttonText;
},
timerStateButtonIcon() {
let buttonIcon = 'icon-pause';
if (['paused', 'stopped'].includes(this.timerState)) {
buttonIcon = 'icon-play';
}
return buttonIcon;
},
timerFormat() {
let timerFormat = 'long';
if (this.configuration && this.configuration.timerFormat) {
timerFormat = this.configuration.timerFormat;
}
return timerFormat;
},
format() {
let format;
if (this.timerFormat === 'long') {
format = 'd[D] HH:mm:ss';
}
if (this.timerFormat === 'short') {
format = 'HH:mm:ss';
}
return format;
},
timerType() {
let timerType = null;
if (isNaN(this.timeDelta)) {
return timerType;
}
if (this.timeDelta < 0) {
timerType = 'countDown';
} else if (this.timeDelta >= 1000) {
timerType = 'countUp';
}
return timerType;
},
timerSign() {
let timerSign = null;
if (this.timerType === 'countUp') {
timerSign = 'plus';
} else if (this.timerType === 'countDown') {
timerSign = 'minus';
}
return timerSign;
}
},
mounted() {
this.$nextTick(() => {
if (this.configuration && this.configuration.timerState === undefined) {
const timerAction = !this.relativeTimestamp ? 'stop' : 'start';
this.triggerAction(`timer.${timerAction}`);
}
window.requestAnimationFrame(this.tick);
this.unlisten = ticker.listen(() => {
this.openmct.objects.refresh(this.domainObject);
});
});
},
destroyed() {
this.active = false;
if (this.unlisten) {
this.unlisten();
}
},
methods: {
tick() {
const isTimerRunning = !['paused', 'stopped'].includes(this.timerState);
if (isTimerRunning) {
this.lastTimestamp = new Date();
}
if (this.timerState === 'paused' && !this.lastTimestamp) {
this.lastTimestamp = this.pausedTime;
}
if (this.active) {
window.requestAnimationFrame(this.tick);
}
},
restartTimer() {
this.triggerAction('timer.restart');
},
toggleStateButton() {
if (this.timerState === 'started') {
this.triggerAction('timer.pause');
} else if (['paused', 'stopped'].includes(this.timerState)) {
this.triggerAction('timer.start');
}
},
triggerAction(actionKey) {
const action = this.openmct.actions.getAction(actionKey);
if (action) {
action.invoke(this.objectPath, this.currentView);
}
}
}
};
</script>

119
src/plugins/timer/plugin.js Normal file
View File

@ -0,0 +1,119 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import TimerViewProvider from './TimerViewProvider';
import PauseTimerAction from './actions/PauseTimerAction';
import RestartTimerAction from './actions/RestartTimerAction';
import StartTimerAction from './actions/StartTimerAction';
import StopTimerAction from './actions/StopTimerAction';
export default function TimerPlugin() {
return function install(openmct) {
openmct.types.addType('timer', {
name: 'Timer',
description: 'A timer that counts up or down to a datetime. Timers can be started, stopped and reset whenever needed, and support a variety of display formats. Each Timer displays the same value to all users. Timers can be added to Display Layouts.',
creatable: true,
cssClass: 'icon-timer',
initialize: function (domainObject) {
domainObject.configuration = {
timerFormat: 'long',
timestamp: undefined,
timezone: 'UTC',
timerState: undefined,
pausedTime: undefined
};
},
"form": [
{
"key": "timestamp",
"control": "datetime",
"name": "Target",
property: [
'configuration',
'timestamp'
]
},
{
"key": "timerFormat",
"name": "Display Format",
"control": "select",
"options": [
{
"value": "long",
"name": "DDD hh:mm:ss"
},
{
"value": "short",
"name": "hh:mm:ss"
}
],
property: [
'configuration',
'timerFormat'
]
}
]
});
openmct.objectViews.addProvider(new TimerViewProvider(openmct));
openmct.actions.register(new PauseTimerAction(openmct));
openmct.actions.register(new RestartTimerAction(openmct));
openmct.actions.register(new StartTimerAction(openmct));
openmct.actions.register(new StopTimerAction(openmct));
openmct.objects.addGetInterceptor({
appliesTo: (identifier, domainObject) => {
return domainObject && domainObject.type === 'timer';
},
invoke: (identifier, domainObject) => {
if (domainObject.configuration) {
return domainObject;
}
const configuration = {};
if (domainObject.timerFormat) {
configuration.timerFormat = domainObject.timerFormat;
}
if (domainObject.timestamp) {
configuration.timestamp = domainObject.timestamp;
}
if (domainObject.timerState) {
configuration.timerState = domainObject.timerState;
}
if (domainObject.pausedTime) {
configuration.pausedTime = domainObject.pausedTime;
}
openmct.objects.mutate(domainObject, 'configuration', configuration);
return domainObject;
}
});
};
}

View File

@ -0,0 +1,354 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, spyOnBuiltins, resetApplicationState } from 'utils/testing';
import timerPlugin from './plugin';
import Vue from 'vue';
describe("Timer plugin:", () => {
let openmct;
let timerDefinition;
let element;
let child;
let appHolder;
let timerDomainObject;
function setupTimer() {
return new Promise((resolve, reject) => {
timerDomainObject = {
identifier: {
key: 'timer',
namespace: 'test-namespace'
},
type: 'timer'
};
appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
document.body.appendChild(appHolder);
openmct = createOpenMct();
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct.install(timerPlugin());
timerDefinition = openmct.types.get('timer').definition;
timerDefinition.initialize(timerDomainObject);
openmct.on('start', resolve);
openmct.start(appHolder);
});
}
describe("should still work if it's in the old format", () => {
let timerViewProvider;
let timerView;
let timerViewObject;
let mutableTimerObject;
let timerObjectPath;
const relativeTimestamp = 1634774400000; // Oct 21 2021, 12:00 AM
beforeEach(async () => {
await setupTimer();
timerViewObject = {
identifier: {
key: 'timer',
namespace: 'test-namespace'
},
type: 'timer',
id: "test-object",
name: 'Timer',
timerFormat: 'short',
timestamp: relativeTimestamp,
timerState: 'paused',
pausedTime: relativeTimestamp
};
const applicableViews = openmct.objectViews.get(timerViewObject, [timerViewObject]);
timerViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'timer.view');
mutableTimerObject = await openmct.objects.getMutable(timerViewObject.identifier);
timerObjectPath = [mutableTimerObject];
timerView = timerViewProvider.view(mutableTimerObject, timerObjectPath);
timerView.show(child);
await Vue.nextTick();
});
it("should migrate old object properties to the configuration section", () => {
openmct.objects.applyGetInterceptors(timerViewObject.identifier, timerViewObject);
expect(timerViewObject.configuration.timerFormat).toBe('short');
expect(timerViewObject.configuration.timestamp).toBe(relativeTimestamp);
expect(timerViewObject.configuration.timerState).toBe('paused');
expect(timerViewObject.configuration.pausedTime).toBe(relativeTimestamp);
});
});
describe("Timer view:", () => {
let timerViewProvider;
let timerView;
let timerViewObject;
let mutableTimerObject;
let timerObjectPath;
beforeEach(async () => {
await setupTimer();
spyOnBuiltins(['requestAnimationFrame']);
window.requestAnimationFrame.and.callFake((cb) => setTimeout(cb, 500));
const baseTimestamp = 1634688000000; // Oct 20, 2021, 12:00 AM
const relativeTimestamp = 1634774400000; // Oct 21 2021, 12:00 AM
jasmine.clock().install();
const baseTime = new Date(baseTimestamp);
jasmine.clock().mockDate(baseTime);
timerViewObject = {
...timerDomainObject,
id: "test-object",
name: 'Timer',
configuration: {
timerFormat: 'long',
timestamp: relativeTimestamp,
timezone: 'UTC',
timerState: undefined,
pausedTime: undefined
}
};
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve(timerViewObject));
spyOn(openmct.objects, 'save').and.returnValue(Promise.resolve(true));
const applicableViews = openmct.objectViews.get(timerViewObject, [timerViewObject]);
timerViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'timer.view');
mutableTimerObject = await openmct.objects.getMutable(timerViewObject.identifier);
timerObjectPath = [mutableTimerObject];
timerView = timerViewProvider.view(mutableTimerObject, timerObjectPath);
timerView.show(child);
await Vue.nextTick();
});
afterEach(() => {
jasmine.clock().uninstall();
timerView.destroy();
openmct.objects.destroyMutable(mutableTimerObject);
if (appHolder) {
appHolder.remove();
}
return resetApplicationState(openmct);
});
it("has name as Timer", () => {
expect(timerDefinition.name).toEqual('Timer');
});
it("is creatable", () => {
expect(timerDefinition.creatable).toEqual(true);
});
it("provides timer view", () => {
expect(timerViewProvider).toBeDefined();
});
it("renders timer element", () => {
const timerElement = element.querySelectorAll('.c-timer');
expect(timerElement.length).toBe(1);
});
it("renders major elements", () => {
const timerElement = element.querySelector('.c-timer');
const resetButton = timerElement.querySelector('.c-timer__ctrl-reset');
const pausePlayButton = timerElement.querySelector('.c-timer__ctrl-pause-play');
const timerDirectionIcon = timerElement.querySelector('.c-timer__direction');
const timerValue = timerElement.querySelector('.c-timer__value');
const hasMajorElements = Boolean(resetButton && pausePlayButton && timerDirectionIcon && timerValue);
expect(hasMajorElements).toBe(true);
});
it("gets errors from actions if configuration is not passed", async () => {
await Vue.nextTick();
const objectPath = _.cloneDeep(timerObjectPath);
delete objectPath[0].configuration;
let action = openmct.actions.getAction('timer.start');
let actionResults = action.invoke(objectPath);
let actionFilterWithoutConfig = action.appliesTo(objectPath);
await openmct.objects.mutate(timerObjectPath[0], 'configuration', { timerState: 'started' });
let actionFilterWithConfig = action.appliesTo(timerObjectPath);
let actionError = new Error('Unable to run start timer action. No domainObject provided.');
expect(actionResults).toEqual(actionError);
expect(actionFilterWithoutConfig).toBe(undefined);
expect(actionFilterWithConfig).toBe(false);
action = openmct.actions.getAction('timer.stop');
actionResults = action.invoke(objectPath);
actionFilterWithoutConfig = action.appliesTo(objectPath);
await openmct.objects.mutate(timerObjectPath[0], 'configuration', { timerState: 'stopped' });
actionFilterWithConfig = action.appliesTo(timerObjectPath);
actionError = new Error('Unable to run stop timer action. No domainObject provided.');
expect(actionResults).toEqual(actionError);
expect(actionFilterWithoutConfig).toBe(undefined);
expect(actionFilterWithConfig).toBe(false);
action = openmct.actions.getAction('timer.pause');
actionResults = action.invoke(objectPath);
actionFilterWithoutConfig = action.appliesTo(objectPath);
await openmct.objects.mutate(timerObjectPath[0], 'configuration', { timerState: 'paused' });
actionFilterWithConfig = action.appliesTo(timerObjectPath);
actionError = new Error('Unable to run pause timer action. No domainObject provided.');
expect(actionResults).toEqual(actionError);
expect(actionFilterWithoutConfig).toBe(undefined);
expect(actionFilterWithConfig).toBe(false);
action = openmct.actions.getAction('timer.restart');
actionResults = action.invoke(objectPath);
actionFilterWithoutConfig = action.appliesTo(objectPath);
await openmct.objects.mutate(timerObjectPath[0], 'configuration', { timerState: 'stopped' });
actionFilterWithConfig = action.appliesTo(timerObjectPath);
actionError = new Error('Unable to run restart timer action. No domainObject provided.');
expect(actionResults).toEqual(actionError);
expect(actionFilterWithoutConfig).toBe(undefined);
expect(actionFilterWithConfig).toBe(false);
});
it("displays a started timer ticking down to a future date", async () => {
const newBaseTime = 1634774400000; // Oct 21 2021, 12:00 AM
openmct.objects.mutate(timerViewObject, 'configuration.timestamp', newBaseTime);
jasmine.clock().tick(5000);
await Vue.nextTick();
const timerElement = element.querySelector('.c-timer');
const timerPausePlayButton = timerElement.querySelector('.c-timer__ctrl-pause-play');
const timerDirectionIcon = timerElement.querySelector('.c-timer__direction');
const timerValue = timerElement.querySelector('.c-timer__value').innerText;
expect(timerPausePlayButton.classList.contains('icon-pause')).toBe(true);
expect(timerDirectionIcon.classList.contains('icon-minus')).toBe(true);
expect(timerValue).toBe('0D 23:59:55');
});
it("displays a started timer ticking up from a past date", async () => {
const newBaseTime = 1634601600000; // Oct 19, 2021, 12:00 AM
openmct.objects.mutate(timerViewObject, 'configuration.timestamp', newBaseTime);
jasmine.clock().tick(5000);
await Vue.nextTick();
const timerElement = element.querySelector('.c-timer');
const timerPausePlayButton = timerElement.querySelector('.c-timer__ctrl-pause-play');
const timerDirectionIcon = timerElement.querySelector('.c-timer__direction');
const timerValue = timerElement.querySelector('.c-timer__value').innerText;
expect(timerPausePlayButton.classList.contains('icon-pause')).toBe(true);
expect(timerDirectionIcon.classList.contains('icon-plus')).toBe(true);
expect(timerValue).toBe('1D 00:00:05');
});
it("displays a paused timer correctly in the DOM", async () => {
jasmine.clock().tick(5000);
await Vue.nextTick();
let action = openmct.actions.getAction('timer.pause');
if (action) {
action.invoke(timerObjectPath, timerView);
}
await Vue.nextTick();
const timerElement = element.querySelector('.c-timer');
const timerPausePlayButton = timerElement.querySelector('.c-timer__ctrl-pause-play');
let timerValue = timerElement.querySelector('.c-timer__value').innerText;
expect(timerPausePlayButton.classList.contains('icon-play')).toBe(true);
expect(timerValue).toBe('0D 23:59:55');
jasmine.clock().tick(5000);
await Vue.nextTick();
expect(timerValue).toBe('0D 23:59:55');
action = openmct.actions.getAction('timer.start');
if (action) {
action.invoke(timerObjectPath, timerView);
}
await Vue.nextTick();
action = openmct.actions.getAction('timer.pause');
if (action) {
action.invoke(timerObjectPath, timerView);
}
await Vue.nextTick();
timerValue = timerElement.querySelector('.c-timer__value').innerText;
expect(timerValue).toBe('1D 00:00:00');
});
it("displays a stopped timer correctly in the DOM", async () => {
const action = openmct.actions.getAction('timer.stop');
if (action) {
action.invoke(timerObjectPath, timerView);
}
await Vue.nextTick();
const timerElement = element.querySelector('.c-timer');
const timerValue = timerElement.querySelector('.c-timer__value').innerText;
const timerResetButton = timerElement.querySelector('.c-timer__ctrl-reset');
const timerPausePlayButton = timerElement.querySelector('.c-timer__ctrl-pause-play');
expect(timerResetButton.classList.contains('hide')).toBe(true);
expect(timerPausePlayButton.classList.contains('icon-play')).toBe(true);
expect(timerValue).toBe('--:--:--');
});
it("displays a restarted timer correctly in the DOM", async () => {
const action = openmct.actions.getAction('timer.restart');
if (action) {
action.invoke(timerObjectPath, timerView);
}
jasmine.clock().tick(5000);
await Vue.nextTick();
const timerElement = element.querySelector('.c-timer');
const timerValue = timerElement.querySelector('.c-timer__value').innerText;
const timerPausePlayButton = timerElement.querySelector('.c-timer__ctrl-pause-play');
expect(timerPausePlayButton.classList.contains('icon-pause')).toBe(true);
expect(timerValue).toBe('0D 00:00:05');
});
});
});

81
src/utils/clock/Ticker.js Normal file
View File

@ -0,0 +1,81 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
class Ticker {
constructor() {
this.callbacks = [];
this.last = new Date() - 1000;
this.tick();
}
/**
* Calls functions every second, as close to the actual second
* tick as is feasible.
* @constructor
* @memberof utils/clock
*/
tick() {
const timestamp = new Date();
const millis = timestamp % 1000;
// Only update callbacks if a second has actually passed.
if (timestamp >= this.last + 1000) {
this.callbacks.forEach(function (callback) {
callback(timestamp);
});
this.last = timestamp - millis;
}
// Try to update at exactly the next second
setTimeout(() => {
this.tick();
}, 1000 - millis, true);
}
/**
* Listen for clock ticks. The provided callback will
* be invoked with the current timestamp (in milliseconds
* since Jan 1 1970) at regular intervals, as near to the
* second boundary as possible.
*
* @param {Function} callback callback to invoke
* @returns {Function} a function to unregister this listener
*/
listen(callback) {
this.callbacks.push(callback);
// Provide immediate feedback
callback(this.last);
// Provide a deregistration function
return () => {
this.callbacks = this.callbacks.filter(function (cb) {
return cb !== callback;
});
};
}
}
let ticker = new Ticker();
export default ticker;