mirror of
https://github.com/nasa/openmct.git
synced 2025-06-15 05:38:12 +00:00
[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:
65
src/plugins/timer/TimerViewProvider.js
Normal file
65
src/plugins/timer/TimerViewProvider.js
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
56
src/plugins/timer/actions/PauseTimerAction.js
Normal file
56
src/plugins/timer/actions/PauseTimerAction.js
Normal 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';
|
||||
}
|
||||
}
|
57
src/plugins/timer/actions/RestartTimerAction.js
Normal file
57
src/plugins/timer/actions/RestartTimerAction.js
Normal 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';
|
||||
}
|
||||
}
|
76
src/plugins/timer/actions/StartTimerAction.js
Normal file
76
src/plugins/timer/actions/StartTimerAction.js
Normal 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';
|
||||
}
|
||||
}
|
57
src/plugins/timer/actions/StopTimerAction.js
Normal file
57
src/plugins/timer/actions/StopTimerAction.js
Normal 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';
|
||||
}
|
||||
}
|
233
src/plugins/timer/components/Timer.vue
Normal file
233
src/plugins/timer/components/Timer.vue
Normal 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
119
src/plugins/timer/plugin.js
Normal 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;
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
354
src/plugins/timer/pluginSpec.js
Normal file
354
src/plugins/timer/pluginSpec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user