From 165bd4f6389686c46eb9ecf6680035686eb9cbe6 Mon Sep 17 00:00:00 2001 From: DJ Date: Sat, 14 Jan 2017 16:43:20 -0600 Subject: [PATCH] =?UTF-8?q?[Clocks=20/=20Timers]=C2=A0=20adding=20ability?= =?UTF-8?q?=20to=20pause=20and=20resume=20added=20the=20alternating=20paus?= =?UTF-8?q?e=20and=20resume=20timer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platform/features/clock/bundle.js | 36 ++++++ .../clock/src/actions/AbstractTimerAction.js | 7 +- .../clock/src/actions/PauseTimerAction.js | 78 +++++++++++++ .../clock/src/actions/ResumeTimerAction.js | 85 +++++++++++++++ .../clock/src/actions/StartTimerAction.js | 1 - .../clock/src/actions/StopTimerAction.js | 8 +- .../clock/src/controllers/TimerController.js | 17 ++- .../test/actions/PauseTimerActionSpec.js | 103 ++++++++++++++++++ .../test/actions/ResumeActionTimerSpec.js | 103 ++++++++++++++++++ 9 files changed, 431 insertions(+), 7 deletions(-) create mode 100644 platform/features/clock/src/actions/PauseTimerAction.js create mode 100644 platform/features/clock/src/actions/ResumeTimerAction.js create mode 100644 platform/features/clock/test/actions/PauseTimerActionSpec.js create mode 100644 platform/features/clock/test/actions/ResumeActionTimerSpec.js diff --git a/platform/features/clock/bundle.js b/platform/features/clock/bundle.js index 8dae9c07ba..cf71ec9e38 100644 --- a/platform/features/clock/bundle.js +++ b/platform/features/clock/bundle.js @@ -29,6 +29,8 @@ define([ "./src/actions/StartTimerAction", "./src/actions/RestartTimerAction", "./src/actions/StopTimerAction", + "./src/actions/PauseTimerAction", + "./src/actions/ResumeTimerAction", "text!./res/templates/clock.html", "text!./res/templates/timer.html", 'legacyRegistry' @@ -41,6 +43,8 @@ define([ StartTimerAction, RestartTimerAction, StopTimerAction, + PauseTimerAction, + ResumeTimerAction, clockTemplate, timerTemplate, legacyRegistry @@ -152,6 +156,28 @@ define([ "cssclass": "icon-refresh", "priority": "preferred" }, + { + "key": "timer.pause", + "implementation": PauseTimerAction, + "depends": [ + "now" + ], + "category": "contextual", + "name": "Pause", + "cssclass": "icon-pause", + "priority": "preferred" + }, + { + "key": "timer.resume", + "implementation": ResumeTimerAction, + "depends": [ + "now" + ], + "category": "contextual", + "name": "Resume", + "cssclass": "icon-play", + "priority": "preferred" + }, { "key": "timer.stop", "implementation": StopTimerAction, @@ -251,6 +277,16 @@ define([ "name": "hh:mm:ss" } ] + }, + { + "key": "paused", + "control": "boolean", + "name": "PauseCheck" + }, + { + "key": "pausedTime", + "control": "long", + "name": "TimeOfPause" } ], "model": { diff --git a/platform/features/clock/src/actions/AbstractTimerAction.js b/platform/features/clock/src/actions/AbstractTimerAction.js index f507a4f435..58de84dfae 100644 --- a/platform/features/clock/src/actions/AbstractTimerAction.js +++ b/platform/features/clock/src/actions/AbstractTimerAction.js @@ -53,7 +53,12 @@ define( model.timestamp = now(); } - return domainObject.useCapability('mutation', setTimestamp); + function setPaused(model) { + model.paused = false; + } + + return domainObject.useCapability('mutation', setTimestamp) && + domainObject.useCapability('mutation', setPaused); }; return AbstractTimerAction; diff --git a/platform/features/clock/src/actions/PauseTimerAction.js b/platform/features/clock/src/actions/PauseTimerAction.js new file mode 100644 index 0000000000..efd54830e4 --- /dev/null +++ b/platform/features/clock/src/actions/PauseTimerAction.js @@ -0,0 +1,78 @@ +/***************************************************************************** + * 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( + ['./AbstractTimerAction'], + function (AbstractTimerAction) { + + /** + * Implements the "Start" action for timers. + * + * Sets the reference timestamp in a timer to the current + * time, such that it begins counting up. + * + * @extends {platform/features/clock.AbstractTimerAction} + * @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) { + AbstractTimerAction.apply(this, [now, context]); + } + + PauseTimerAction.prototype = + Object.create(AbstractTimerAction.prototype); + + PauseTimerAction.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.timestamp !== undefined && !model.paused; + }; + + PauseTimerAction.prototype.perform = function () { + var domainObject = this.domainObject, + now = this.now; + + function setPaused(model) { + model.paused = true; + } + + function setPausedTime(model) { + model.pausedTime = now(); + } + + return domainObject.useCapability('mutation', setPaused) && + domainObject.useCapability('mutation', setPausedTime); + }; + + return PauseTimerAction; + } +); diff --git a/platform/features/clock/src/actions/ResumeTimerAction.js b/platform/features/clock/src/actions/ResumeTimerAction.js new file mode 100644 index 0000000000..7e5c160d03 --- /dev/null +++ b/platform/features/clock/src/actions/ResumeTimerAction.js @@ -0,0 +1,85 @@ +/***************************************************************************** + * 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( + ['./AbstractTimerAction'], + function (AbstractTimerAction) { + + /** + * Implements the "Start" action for timers. + * + * Sets the reference timestamp in a timer to the current + * time, such that it begins counting up. + * + * @extends {platform/features/clock.AbstractTimerAction} + * @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 ResumeTimerAction(now, context) { + AbstractTimerAction.apply(this, [now, context]); + } + + ResumeTimerAction.prototype = + Object.create(AbstractTimerAction.prototype); + + ResumeTimerAction.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.timestamp !== undefined && + model.paused; + }; + + ResumeTimerAction.prototype.perform = function () { + var domainObject = this.domainObject, + now = this.now; + + function setPaused(model) { + model.paused = false; + } + + function setTimestamp(model) { + var timeShift = now() - model.pausedTime; + model.timestamp = model.timestamp + timeShift; + } + + function setPausedTime(model) { + model.pausedTime = undefined; + } + + return domainObject.useCapability('mutation', setPaused) && + domainObject.useCapability('mutation', setTimestamp) && + domainObject.useCapability('mutation', setPausedTime); + }; + + return ResumeTimerAction; + } +); \ No newline at end of file diff --git a/platform/features/clock/src/actions/StartTimerAction.js b/platform/features/clock/src/actions/StartTimerAction.js index 0336bebc6b..42abe17247 100644 --- a/platform/features/clock/src/actions/StartTimerAction.js +++ b/platform/features/clock/src/actions/StartTimerAction.js @@ -57,6 +57,5 @@ define( }; return StartTimerAction; - } ); diff --git a/platform/features/clock/src/actions/StopTimerAction.js b/platform/features/clock/src/actions/StopTimerAction.js index 1c56d7a1e0..d3d19b160a 100644 --- a/platform/features/clock/src/actions/StopTimerAction.js +++ b/platform/features/clock/src/actions/StopTimerAction.js @@ -64,10 +64,14 @@ define( model.timestamp = undefined; } - return domainObject.useCapability('mutation', setTimestamp); + function setPaused(model) { + model.paused = false; + } + + return domainObject.useCapability('mutation', setTimestamp) && + domainObject.useCapability('mutation', setPaused); }; return StopTimerAction; - } ); diff --git a/platform/features/clock/src/controllers/TimerController.js b/platform/features/clock/src/controllers/TimerController.js index a95ff3d6a4..e2cd2bdb48 100644 --- a/platform/features/clock/src/controllers/TimerController.js +++ b/platform/features/clock/src/controllers/TimerController.js @@ -74,7 +74,15 @@ define( formatKey = model.timerFormat, actionCapability = domainObject.getCapability('action'), actionKey = (timestamp === undefined) ? - 'timer.start' : 'timer.restart'; + 'timer.start' : 'timer.restart'; + + self.paused = model.paused; + self.pausedTime = model.pausedTime; + + //if paused on startup show last known position + if (self.paused && !lastTimestamp){ + lastTimestamp = self.pausedTime; + } updateFormat(formatKey); updateTimestamp(timestamp); @@ -98,8 +106,11 @@ define( function tick() { var lastSign = self.signValue, lastText = self.textValue; - lastTimestamp = now(); - update(); + + if (!self.paused) { + lastTimestamp = now(); + update(); + } // We're running in an animation frame, not in a digest cycle. // We need to trigger a digest cycle if our displayable data // changes. diff --git a/platform/features/clock/test/actions/PauseTimerActionSpec.js b/platform/features/clock/test/actions/PauseTimerActionSpec.js new file mode 100644 index 0000000000..0f8cc1192f --- /dev/null +++ b/platform/features/clock/test/actions/PauseTimerActionSpec.js @@ -0,0 +1,103 @@ +/***************************************************************************** + * 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)); + } + }; + } + + beforeEach(function () { + mockNow = jasmine.createSpy('now'); + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getCapability', 'useCapability', 'getModel'] + ); + + mockDomainObject.useCapability.andCallFake(function (c, v) { + if (c === 'mutation') { + testModel = v(testModel) || testModel; + return asPromise(true); + } + }); + mockDomainObject.getModel.andCallFake(function () { + return testModel; + }); + + testModel = {}; + testContext = { domainObject: mockDomainObject }; + + action = new PauseTimerAction(mockNow, testContext); + }); + + it("updates the model with a timestamp", function () { + mockNow.andReturn(12000); + action.perform(); + expect(testModel.timestamp).toEqual(12000); + }); + + it("applies only to timers without a target time", function () { + //Timer is on + testModel.type = 'timer'; + testModel.timestamp = 12000; + + testModel.paused = true; + expect(PauseTimerAction.appliesTo(testContext)).toBeFalsy(); + + testModel.paused = false; + expect(PauseTimerAction.appliesTo(testContext)).toBeTruthy(); + + //Timer has not started + testModel.timestamp = undefined; + + testModel.paused = true; + expect(PauseTimerAction.appliesTo(testContext)).toBeFalsy(); + + testModel.paused = false; + expect(PauseTimerAction.appliesTo(testContext)).toBeFalsy(); + + //Timer is actually a clock + testModel.type = 'clock'; + testModel.timestamp = 12000; + + testModel.paused = true; + expect(PauseTimerAction.appliesTo(testContext)).toBeFalsy(); + + testModel.paused = false; + expect(PauseTimerAction.appliesTo(testContext)).toBeFalsy(); + }); + }); + } +); diff --git a/platform/features/clock/test/actions/ResumeActionTimerSpec.js b/platform/features/clock/test/actions/ResumeActionTimerSpec.js new file mode 100644 index 0000000000..10f6d943dd --- /dev/null +++ b/platform/features/clock/test/actions/ResumeActionTimerSpec.js @@ -0,0 +1,103 @@ +/***************************************************************************** + * 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/ResumeTimerAction"], + function (ResumeTimerAction) { + + describe("A timer's Resume action", function () { + var mockNow, + mockDomainObject, + testModel, + testContext, + action; + + function asPromise(value) { + return (value || {}).then ? value : { + then: function (callback) { + return asPromise(callback(value)); + } + }; + } + + beforeEach(function () { + mockNow = jasmine.createSpy('now'); + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + ['getCapability', 'useCapability', 'getModel'] + ); + + mockDomainObject.useCapability.andCallFake(function (c, v) { + if (c === 'mutation') { + testModel = v(testModel) || testModel; + return asPromise(true); + } + }); + mockDomainObject.getModel.andCallFake(function () { + return testModel; + }); + + testModel = {}; + testContext = { domainObject: mockDomainObject }; + + action = new ResumeTimerAction(mockNow, testContext); + }); + + it("updates the model with a timestamp", function () { + mockNow.andReturn(12000); + action.perform(); + expect(testModel.timestamp).toEqual(12000); + }); + + it("applies only to timers without a target time", function () { + //Timer is on + testModel.type = 'timer'; + testModel.timestamp = 12000; + + testModel.paused = true; + expect(ResumeTimerAction.appliesTo(testContext)).toBeTruthy(); + + testModel.paused = false; + expect(ResumeTimerAction.appliesTo(testContext)).toBeFalsy(); + + //Timer has not started + testModel.timestamp = undefined; + + testModel.paused = true; + expect(ResumeTimerAction.appliesTo(testContext)).toBeFalsy(); + + testModel.paused = false; + expect(ResumeTimerAction.appliesTo(testContext)).toBeFalsy(); + + //Timer is actually a clock + testModel.type = 'clock'; + testModel.timestamp = 12000; + + testModel.paused = true; + expect(ResumeTimerAction.appliesTo(testContext)).toBeFalsy(); + + testModel.paused = false; + expect(ResumeTimerAction.appliesTo(testContext)).toBeFalsy(); + }); + }); + } +);