Compare commits
15 Commits
tc-e2e-tes
...
multi-user
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de5914b82d | ||
|
|
41bbbb136b | ||
|
|
0f199b6aff | ||
|
|
95e686038d | ||
|
|
f705bf9a61 | ||
|
|
50559ac502 | ||
|
|
f0ef93dd3f | ||
|
|
3ae14cf786 | ||
|
|
194eb43607 | ||
|
|
3c2b032526 | ||
|
|
d4e51cbaf1 | ||
|
|
7c58b19c3e | ||
|
|
16e1ac2529 | ||
|
|
4885c816dc | ||
|
|
42b545917c |
@@ -242,6 +242,10 @@ workflows:
|
||||
name: e2e-stable
|
||||
node-version: lts/hydrogen
|
||||
suite: stable
|
||||
- perf-test:
|
||||
node-version: lts/hydrogen
|
||||
- visual-test:
|
||||
node-version: lts/hydrogen
|
||||
|
||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||
jobs:
|
||||
|
||||
@@ -28,6 +28,8 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'vue/no-v-for-template-key': 'off',
|
||||
'vue/no-v-for-template-key-on-child': 'error',
|
||||
'prettier/prettier': 'error',
|
||||
'you-dont-need-lodash-underscore/omit': 'off',
|
||||
'you-dont-need-lodash-underscore/throttle': 'off',
|
||||
|
||||
@@ -67,7 +67,8 @@ const config = {
|
||||
MCT: path.join(projectRootDir, 'src/MCT'),
|
||||
testUtils: path.join(projectRootDir, 'src/utils/testUtils.js'),
|
||||
objectUtils: path.join(projectRootDir, 'src/api/objects/object-utils.js'),
|
||||
utils: path.join(projectRootDir, 'src/utils')
|
||||
utils: path.join(projectRootDir, 'src/utils'),
|
||||
vue: path.join(projectRootDir, 'node_modules/@vue/compat/dist/vue.esm-bundler.js'),
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
@@ -121,7 +122,15 @@ const config = {
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
use: 'vue-loader'
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
compilerOptions: {
|
||||
whitespace: 'preserve',
|
||||
compatConfig: {
|
||||
MODE: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
|
||||
@@ -25,11 +25,6 @@ module.exports = merge(common, {
|
||||
'**/.*' // dotfiles and dotfolders
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue: path.join(projectRootDir, 'node_modules/vue/dist/vue.js')
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__OPENMCT_ROOT_RELATIVE__: '"dist/"'
|
||||
|
||||
@@ -13,11 +13,6 @@ const projectRootDir = path.resolve(__dirname, '..');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
resolve: {
|
||||
alias: {
|
||||
vue: path.join(projectRootDir, 'node_modules/vue/dist/vue.min.js')
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__OPENMCT_ROOT_RELATIVE__: '""'
|
||||
|
||||
6
API.md
@@ -2,7 +2,7 @@
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
**Table of Contents**
|
||||
|
||||
- [Building Applications With Open MCT](#developing-applications-with-open-mct)
|
||||
- [Developing Applications With Open MCT](#developing-applications-with-open-mct)
|
||||
- [Scope and purpose of this document](#scope-and-purpose-of-this-document)
|
||||
- [Building From Source](#building-from-source)
|
||||
- [Starting an Open MCT application](#starting-an-open-mct-application)
|
||||
@@ -26,7 +26,7 @@
|
||||
- [Value Hints](#value-hints)
|
||||
- [The Time Conductor and Telemetry](#the-time-conductor-and-telemetry)
|
||||
- [Telemetry Providers](#telemetry-providers)
|
||||
- [Telemetry Requests and Responses.](#telemetry-requests-and-responses)
|
||||
- [Telemetry Requests and Responses](#telemetry-requests-and-responses)
|
||||
- [Request Strategies **draft**](#request-strategies-draft)
|
||||
- [`latest` request strategy](#latest-request-strategy)
|
||||
- [`minmax` request strategy](#minmax-request-strategy)
|
||||
@@ -873,6 +873,8 @@ function without any arguments.
|
||||
|
||||
#### Stopping an active clock
|
||||
|
||||
_As of July 2023, this method will be deprecated. Open MCT will always have a ticking clock._
|
||||
|
||||
The `stopClock` method can be used to stop an active clock, and to clear it. It
|
||||
will stop the clock from ticking, and set the active clock to `undefined`.
|
||||
|
||||
|
||||
@@ -314,13 +314,13 @@ async function _isInEditMode(page, identifier) {
|
||||
*/
|
||||
async function setTimeConductorMode(page, isFixedTimespan = true) {
|
||||
// Click 'mode' button
|
||||
await page.locator('.c-mode-button').click();
|
||||
|
||||
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Time Conductor Mode Menu' }).click();
|
||||
// Switch time conductor mode
|
||||
if (isFixedTimespan) {
|
||||
await page.locator('data-testid=conductor-modeOption-fixed').click();
|
||||
await page.getByRole('menuitem', { name: /Fixed Timespan/ }).click();
|
||||
} else {
|
||||
await page.locator('data-testid=conductor-modeOption-realtime').click();
|
||||
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,9 +342,12 @@ async function setRealTimeMode(page) {
|
||||
|
||||
/**
|
||||
* @typedef {Object} OffsetValues
|
||||
* @property {string | undefined} hours
|
||||
* @property {string | undefined} mins
|
||||
* @property {string | undefined} secs
|
||||
* @property {string | undefined} startHours
|
||||
* @property {string | undefined} startMins
|
||||
* @property {string | undefined} startSecs
|
||||
* @property {string | undefined} endHours
|
||||
* @property {string | undefined} endMins
|
||||
* @property {string | undefined} endSecs
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -353,23 +356,36 @@ async function setRealTimeMode(page) {
|
||||
* @param {OffsetValues} offset
|
||||
* @param {import('@playwright/test').Locator} offsetButton
|
||||
*/
|
||||
async function setTimeConductorOffset(page, { hours, mins, secs }, offsetButton) {
|
||||
await offsetButton.click();
|
||||
|
||||
if (hours) {
|
||||
await page.fill('.pr-time-controls__hrs', hours);
|
||||
async function setTimeConductorOffset(
|
||||
page,
|
||||
{ startHours, startMins, startSecs, endHours, endMins, endSecs }
|
||||
) {
|
||||
if (startHours) {
|
||||
await page.getByRole('spinbutton', { name: 'Start offset hours' }).fill(startHours);
|
||||
}
|
||||
|
||||
if (mins) {
|
||||
await page.fill('.pr-time-controls__mins', mins);
|
||||
if (startMins) {
|
||||
await page.getByRole('spinbutton', { name: 'Start offset minutes' }).fill(startMins);
|
||||
}
|
||||
|
||||
if (secs) {
|
||||
await page.fill('.pr-time-controls__secs', secs);
|
||||
if (startSecs) {
|
||||
await page.getByRole('spinbutton', { name: 'Start offset seconds' }).fill(startSecs);
|
||||
}
|
||||
|
||||
if (endHours) {
|
||||
await page.getByRole('spinbutton', { name: 'End offset hours' }).fill(endHours);
|
||||
}
|
||||
|
||||
if (endMins) {
|
||||
await page.getByRole('spinbutton', { name: 'End offset minutes' }).fill(endMins);
|
||||
}
|
||||
|
||||
if (endSecs) {
|
||||
await page.getByRole('spinbutton', { name: 'End offset seconds' }).fill(endSecs);
|
||||
}
|
||||
|
||||
// Click the check button
|
||||
await page.locator('.pr-time__buttons .icon-check').click();
|
||||
await page.locator('.pr-time-input--buttons .icon-check').click();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -378,8 +394,9 @@ async function setTimeConductorOffset(page, { hours, mins, secs }, offsetButton)
|
||||
* @param {OffsetValues} offset
|
||||
*/
|
||||
async function setStartOffset(page, offset) {
|
||||
const startOffsetButton = page.locator('data-testid=conductor-start-offset-button');
|
||||
await setTimeConductorOffset(page, offset, startOffsetButton);
|
||||
// Click 'mode' button
|
||||
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
|
||||
await setTimeConductorOffset(page, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,8 +405,52 @@ async function setStartOffset(page, offset) {
|
||||
* @param {OffsetValues} offset
|
||||
*/
|
||||
async function setEndOffset(page, offset) {
|
||||
const endOffsetButton = page.locator('data-testid=conductor-end-offset-button');
|
||||
await setTimeConductorOffset(page, offset, endOffsetButton);
|
||||
// Click 'mode' button
|
||||
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
|
||||
await setTimeConductorOffset(page, offset);
|
||||
}
|
||||
|
||||
async function setTimeConductorBounds(page, startDate, endDate) {
|
||||
// Bring up the time conductor popup
|
||||
await page.click('.l-shell__time-conductor.c-compact-tc');
|
||||
|
||||
await setTimeBounds(page, startDate, endDate);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
}
|
||||
|
||||
async function setIndependentTimeConductorBounds(page, startDate, endDate) {
|
||||
// Activate Independent Time Conductor in Fixed Time Mode
|
||||
await page.getByRole('switch').click();
|
||||
|
||||
// Bring up the time conductor popup
|
||||
await page.click('.c-conductor-holder--compact .c-compact-tc');
|
||||
|
||||
await expect(page.locator('.itc-popout')).toBeVisible();
|
||||
|
||||
await setTimeBounds(page, startDate, endDate);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
}
|
||||
|
||||
async function setTimeBounds(page, startDate, endDate) {
|
||||
if (startDate) {
|
||||
// Fill start time
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Start date' })
|
||||
.fill(startDate.toString().substring(0, 10));
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Start time' })
|
||||
.fill(startDate.toString().substring(11, 19));
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
// Fill end time
|
||||
await page.getByRole('textbox', { name: 'End date' }).fill(endDate.toString().substring(0, 10));
|
||||
await page
|
||||
.getByRole('textbox', { name: 'End time' })
|
||||
.fill(endDate.toString().substring(11, 19));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -503,6 +564,8 @@ module.exports = {
|
||||
setRealTimeMode,
|
||||
setStartOffset,
|
||||
setEndOffset,
|
||||
setTimeConductorBounds,
|
||||
setIndependentTimeConductorBounds,
|
||||
selectInspectorTab,
|
||||
waitForPlotsToRender
|
||||
};
|
||||
|
||||
@@ -4,19 +4,23 @@
|
||||
{
|
||||
"origin": "http://localhost:8080",
|
||||
"localStorage": [
|
||||
{
|
||||
"name": "tcHistory",
|
||||
"value": "{\"utc\":[{\"start\":1658617494563,\"end\":1658619294563},{\"start\":1658617090044,\"end\":1658618890044},{\"start\":1658616460484,\"end\":1658618260484},{\"start\":1658608882159,\"end\":1658610682159},{\"start\":1654537164464,\"end\":1654538964464},{\"start\":1652301954635,\"end\":1652303754635}]}"
|
||||
},
|
||||
{
|
||||
"name": "mct",
|
||||
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1658619295366,\"modified\":1658619295366},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002},\"2d02a680-eb7e-4645-bba2-dd298f76efb8\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4291d80c-303c-4d8d-85e1-10f012b864fb\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1654538965702,\"location\":\"mine\",\"persisted\":1654538965702},\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2b6bf89f-877b-42b8-acc1-a9a575efdbe1\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658610682787,\"location\":\"mine\",\"persisted\":1658610682787},\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"b9a9c413-4b94-401d-b0c7-5e404f182616\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618261112,\"location\":\"mine\",\"persisted\":1658618261112},\"3e294eae-6124-409b-a870-554d1bdcdd6f\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"108043b1-9c88-4e1d-8deb-fbf2cdb528f9\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618890910,\"location\":\"mine\",\"persisted\":1658618890910},\"ec24d05d-5df5-4c96-9241-b73636cd19a9\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4062bd9b-b788-43dd-ab0a-8fa10a78d4b3\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658619295363,\"location\":\"mine\",\"persisted\":1658619295363}}"
|
||||
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1689710689554,\"modified\":1689710689554},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002},\"2d02a680-eb7e-4645-bba2-dd298f76efb8\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4291d80c-303c-4d8d-85e1-10f012b864fb\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1654538965702,\"location\":\"mine\",\"persisted\":1654538965702},\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2b6bf89f-877b-42b8-acc1-a9a575efdbe1\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658610682787,\"location\":\"mine\",\"persisted\":1658610682787},\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"b9a9c413-4b94-401d-b0c7-5e404f182616\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618261112,\"location\":\"mine\",\"persisted\":1658618261112},\"3e294eae-6124-409b-a870-554d1bdcdd6f\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"108043b1-9c88-4e1d-8deb-fbf2cdb528f9\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618890910,\"location\":\"mine\",\"persisted\":1658618890910},\"ec24d05d-5df5-4c96-9241-b73636cd19a9\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4062bd9b-b788-43dd-ab0a-8fa10a78d4b3\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658619295363,\"location\":\"mine\",\"persisted\":1658619295363},\"0ec517e8-6c11-4d98-89b5-c300fe61b304\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550}}"
|
||||
},
|
||||
{
|
||||
"name": "mct-tree-expanded",
|
||||
"value": "[]"
|
||||
},
|
||||
{
|
||||
"name": "tcHistory",
|
||||
"value": "{\"utc\":[{\"start\":1658617494563,\"end\":1658619294563},{\"start\":1658617090044,\"end\":1658618890044},{\"start\":1658616460484,\"end\":1658618260484},{\"start\":1658608882159,\"end\":1658610682159},{\"start\":1654537164464,\"end\":1654538964464},{\"start\":1652301954635,\"end\":1652303754635}]}"
|
||||
},
|
||||
{
|
||||
"name": "mct-recent-objects",
|
||||
"value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1689710689554,\"modified\":1689710689554},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"domainObject\":{\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1689710689554,\"modified\":1689710689554},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1689710689554,\"modified\":1689710689554}}]"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ test.describe('Form Validation Behavior', () => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
|
||||
await page.click('button:has-text("Create")');
|
||||
await page.click(':nth-match(:text("Folder"), 2)');
|
||||
await page.getByRole('menuitem', { name: ' Folder' }).click();
|
||||
|
||||
// Fill in empty string into title and trigger validation with 'Tab'
|
||||
await page.click('text=Properties Title Notes >> input[type="text"]');
|
||||
|
||||
@@ -28,10 +28,10 @@ const { createDomainObjectWithDefaults, createNotification } = require('../../ap
|
||||
const { test, expect } = require('../../pluginFixtures');
|
||||
|
||||
test.describe('Notifications List', () => {
|
||||
test('Notifications can be dismissed individually', async ({ page }) => {
|
||||
test.fixme('Notifications can be dismissed individually', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/6122'
|
||||
description: 'https://github.com/nasa/openmct/issues/6820'
|
||||
});
|
||||
|
||||
// Go to baseURL
|
||||
|
||||
@@ -98,6 +98,8 @@ test.describe('Time List', () => {
|
||||
const startBound = testPlan.TEST_GROUP[0].start;
|
||||
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
|
||||
|
||||
await page.goto(timelist.url);
|
||||
|
||||
// Switch to fixed time mode with all plan events within the bounds
|
||||
await page.goto(
|
||||
`${timelist.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=timelist.view`
|
||||
@@ -110,7 +112,7 @@ test.describe('Time List', () => {
|
||||
|
||||
await test.step('Does not show milliseconds in times', async () => {
|
||||
// Get the first activity
|
||||
const row = await page.locator('.js-list-item').first();
|
||||
const row = page.locator('.js-list-item').first();
|
||||
// Verify that none fo the times have milliseconds displayed.
|
||||
// Example: 2024-11-17T16:00:00Z is correct and 2024-11-17T16:00:00.000Z is wrong
|
||||
|
||||
|
||||
@@ -21,7 +21,11 @@
|
||||
*****************************************************************************/
|
||||
|
||||
const { test, expect } = require('../../../pluginFixtures');
|
||||
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions');
|
||||
const {
|
||||
createDomainObjectWithDefaults,
|
||||
createPlanFromJSON,
|
||||
setIndependentTimeConductorBounds
|
||||
} = require('../../../appActions');
|
||||
|
||||
const testPlan = {
|
||||
TEST_GROUP: [
|
||||
@@ -78,9 +82,6 @@ test.describe('Time Strip', () => {
|
||||
});
|
||||
|
||||
// Constant locators
|
||||
const independentTimeConductorInputs = page.locator(
|
||||
'.l-shell__main-independent-time-conductor .c-input--datetime'
|
||||
);
|
||||
const activityBounds = page.locator('.activity-bounds');
|
||||
|
||||
// Goto baseURL
|
||||
@@ -122,9 +123,7 @@ test.describe('Time Strip', () => {
|
||||
});
|
||||
|
||||
await test.step('TimeStrip can use the Independent Time Conductor', async () => {
|
||||
// Activate Independent Time Conductor in Fixed Time Mode
|
||||
await page.click('.c-toggle-switch__slider');
|
||||
expect(await activityBounds.count()).toEqual(0);
|
||||
expect(await activityBounds.count()).toEqual(5);
|
||||
|
||||
// Set the independent time bounds so that only one event is shown
|
||||
const startBound = testPlan.TEST_GROUP[0].start;
|
||||
@@ -132,12 +131,7 @@ test.describe('Time Strip', () => {
|
||||
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
|
||||
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
|
||||
|
||||
await independentTimeConductorInputs.nth(0).fill('');
|
||||
await independentTimeConductorInputs.nth(0).fill(startBoundString);
|
||||
await page.keyboard.press('Enter');
|
||||
await independentTimeConductorInputs.nth(1).fill('');
|
||||
await independentTimeConductorInputs.nth(1).fill(endBoundString);
|
||||
await page.keyboard.press('Enter');
|
||||
await setIndependentTimeConductorBounds(page, startBoundString, endBoundString);
|
||||
expect(await activityBounds.count()).toEqual(1);
|
||||
});
|
||||
|
||||
@@ -156,9 +150,6 @@ test.describe('Time Strip', () => {
|
||||
await page.click("button[title='Save']");
|
||||
await page.click("li[title='Save and Finish Editing']");
|
||||
|
||||
// Activate Independent Time Conductor in Fixed Time Mode
|
||||
await page.click('.c-toggle-switch__slider');
|
||||
|
||||
// All events should be displayed at this point because the
|
||||
// initial independent context bounds will match the global bounds
|
||||
expect(await activityBounds.count()).toEqual(5);
|
||||
@@ -169,12 +160,7 @@ test.describe('Time Strip', () => {
|
||||
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
|
||||
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
|
||||
|
||||
await independentTimeConductorInputs.nth(0).fill('');
|
||||
await independentTimeConductorInputs.nth(0).fill(startBoundString);
|
||||
await page.keyboard.press('Enter');
|
||||
await independentTimeConductorInputs.nth(1).fill('');
|
||||
await independentTimeConductorInputs.nth(1).fill(endBoundString);
|
||||
await page.keyboard.press('Enter');
|
||||
await setIndependentTimeConductorBounds(page, startBoundString, endBoundString);
|
||||
|
||||
// Verify that two events are displayed
|
||||
expect(await activityBounds.count()).toEqual(2);
|
||||
|
||||
@@ -41,7 +41,7 @@ test.describe('Clock Generator CRUD Operations', () => {
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
// Click Clock
|
||||
await page.click('text=Clock');
|
||||
await page.getByRole('menuitem').first().click();
|
||||
|
||||
// Click .icon-arrow-down
|
||||
await page.locator('.icon-arrow-down').click();
|
||||
|
||||
@@ -25,7 +25,8 @@ const {
|
||||
createDomainObjectWithDefaults,
|
||||
setStartOffset,
|
||||
setFixedTimeMode,
|
||||
setRealTimeMode
|
||||
setRealTimeMode,
|
||||
setIndependentTimeConductorBounds
|
||||
} = require('../../../../appActions');
|
||||
|
||||
test.describe('Display Layout', () => {
|
||||
@@ -206,6 +207,56 @@ test.describe('Display Layout', () => {
|
||||
expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
|
||||
});
|
||||
|
||||
test('independent time works with display layouts and its children', async ({ page }) => {
|
||||
await setFixedTimeMode(page);
|
||||
// Create Example Imagery
|
||||
const exampleImageryObject = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Example Imagery'
|
||||
});
|
||||
// Create a Display Layout
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Display Layout'
|
||||
});
|
||||
// Edit Display Layout
|
||||
await page.locator('[title="Edit"]').click();
|
||||
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// Add the Sine Wave Generator to the Display Layout and save changes
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const exampleImageryTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(exampleImageryObject.name)
|
||||
});
|
||||
let layoutGridHolder = page.locator('.l-layout__grid-holder');
|
||||
await exampleImageryTreeItem.dragTo(layoutGridHolder);
|
||||
|
||||
//adjust so that we can see the independent time conductor toggle
|
||||
// Adjust object height
|
||||
await page.locator('div[title="Resize object height"] > input').click();
|
||||
await page.locator('div[title="Resize object height"] > input').fill('70');
|
||||
|
||||
// Adjust object width
|
||||
await page.locator('div[title="Resize object width"] > input').click();
|
||||
await page.locator('div[title="Resize object width"] > input').fill('70');
|
||||
|
||||
await page.locator('button[title="Save"]').click();
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
|
||||
const startDate = '2021-12-30 01:01:00.000Z';
|
||||
const endDate = '2021-12-30 01:11:00.000Z';
|
||||
await setIndependentTimeConductorBounds(page, startDate, endDate);
|
||||
|
||||
// check image date
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
||||
|
||||
// flip it off
|
||||
await page.getByRole('switch').click();
|
||||
// timestamp shouldn't be in the past anymore
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
|
||||
});
|
||||
|
||||
test('When multiple plots are contained in a layout, we only ask for annotations once @couchdb', async ({
|
||||
page
|
||||
}) => {
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
*****************************************************************************/
|
||||
|
||||
const { test, expect } = require('../../../../pluginFixtures');
|
||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
||||
const {
|
||||
createDomainObjectWithDefaults,
|
||||
setIndependentTimeConductorBounds
|
||||
} = require('../../../../appActions');
|
||||
|
||||
test.describe('Flexible Layout', () => {
|
||||
let sineWaveObject;
|
||||
@@ -158,4 +161,47 @@ test.describe('Flexible Layout', () => {
|
||||
// Verify that the item has been removed from the layout
|
||||
expect(await page.locator('.c-fl-container__frame').count()).toEqual(0);
|
||||
});
|
||||
|
||||
test('independent time works with flexible layouts and its children', async ({ page }) => {
|
||||
// Create Example Imagery
|
||||
const exampleImageryObject = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Example Imagery'
|
||||
});
|
||||
// Create a Flexible Layout
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Flexible Layout'
|
||||
});
|
||||
// Edit Display Layout
|
||||
await page.locator('[title="Edit"]').click();
|
||||
|
||||
// Expand the 'My Items' folder in the left tree
|
||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
|
||||
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
||||
const treePane = page.getByRole('tree', {
|
||||
name: 'Main Tree'
|
||||
});
|
||||
const exampleImageryTreeItem = treePane.getByRole('treeitem', {
|
||||
name: new RegExp(exampleImageryObject.name)
|
||||
});
|
||||
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
||||
await exampleImageryTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
||||
|
||||
await page.locator('button[title="Save"]').click();
|
||||
await page.locator('text=Save and Finish Editing').click();
|
||||
|
||||
// flip on independent time conductor
|
||||
await setIndependentTimeConductorBounds(
|
||||
page,
|
||||
'2021-12-30 01:01:00.000Z',
|
||||
'2021-12-30 01:11:00.000Z'
|
||||
);
|
||||
|
||||
// check image date
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
||||
|
||||
// flip it off
|
||||
await page.getByRole('switch').click();
|
||||
// timestamp shouldn't be in the past anymore
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ but only assume that example imagery is present.
|
||||
/* globals process */
|
||||
const { waitForAnimations } = require('../../../../baseFixtures');
|
||||
const { test, expect } = require('../../../../pluginFixtures');
|
||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
||||
const { createDomainObjectWithDefaults, setRealTimeMode } = require('../../../../appActions');
|
||||
const backgroundImageSelector = '.c-imagery__main-image__background-image';
|
||||
const panHotkey = process.platform === 'linux' ? ['Shift', 'Alt'] : ['Alt'];
|
||||
const tagHotkey = ['Shift', 'Alt'];
|
||||
@@ -46,6 +46,7 @@ test.describe('Example Imagery Object', () => {
|
||||
// Verify that the created object is focused
|
||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText(exampleImagery.name);
|
||||
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
|
||||
await page.locator(backgroundImageSelector).waitFor();
|
||||
});
|
||||
|
||||
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
|
||||
@@ -70,6 +71,68 @@ test.describe('Example Imagery Object', () => {
|
||||
await dragContrastSliderAndAssertFilterValues(page);
|
||||
});
|
||||
|
||||
test('Can use independent time conductor to change time', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/6821'
|
||||
});
|
||||
// Test independent fixed time with global fixed time
|
||||
// flip on independent time conductor
|
||||
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
|
||||
await page.getByRole('button', { name: 'Independent Time Conductor Settings' }).click();
|
||||
await page.getByRole('textbox', { name: 'Start date' }).fill('');
|
||||
await page.getByRole('textbox', { name: 'Start date' }).fill('2021-12-30');
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByRole('textbox', { name: 'Start time' }).fill('');
|
||||
await page.getByRole('textbox', { name: 'Start time' }).type('01:01:00');
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByRole('textbox', { name: 'End date' }).fill('');
|
||||
await page.getByRole('textbox', { name: 'End date' }).type('2021-12-30');
|
||||
await page.keyboard.press('Tab');
|
||||
await page.getByRole('textbox', { name: 'End time' }).fill('');
|
||||
await page.getByRole('textbox', { name: 'End time' }).type('01:11:00');
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Enter');
|
||||
// expect(await page.getByRole('button', { name: 'Submit time bounds' }).isEnabled()).toBe(true);
|
||||
// await page.getByRole('button', { name: 'Submit time bounds' }).click();
|
||||
|
||||
// check image date
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
||||
|
||||
// flip it off
|
||||
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
|
||||
// timestamp shouldn't be in the past anymore
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
|
||||
|
||||
// Test independent fixed time with global realtime
|
||||
await setRealTimeMode(page);
|
||||
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
|
||||
// check image date to be in the past
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
||||
// flip it off
|
||||
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
|
||||
// timestamp shouldn't be in the past anymore
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
|
||||
|
||||
// Test independent realtime with global realtime
|
||||
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
|
||||
// check image date
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
||||
// change independent time to realtime
|
||||
await page.getByRole('button', { name: 'Independent Time Conductor Settings' }).click();
|
||||
await page.getByRole('button', { name: 'Independent Time Conductor Mode Menu' }).click();
|
||||
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
|
||||
// timestamp shouldn't be in the past anymore
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
|
||||
// back to the past
|
||||
await page.getByRole('button', { name: 'Independent Time Conductor Mode Menu' }).click();
|
||||
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
|
||||
await page.getByRole('button', { name: 'Independent Time Conductor Mode Menu' }).click();
|
||||
await page.getByRole('menuitem', { name: /Fixed Timespan/ }).click();
|
||||
// check image date to be in the past
|
||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
|
||||
const deltaYStep = 100; //equivalent to 1x zoom
|
||||
|
||||
@@ -189,11 +252,9 @@ test.describe('Example Imagery Object', () => {
|
||||
test('Using the zoom features does not pause telemetry', async ({ page }) => {
|
||||
const pausePlayButton = page.locator('.c-button.pause-play');
|
||||
|
||||
// open the time conductor drop down
|
||||
await page.locator('.c-mode-button').click();
|
||||
// switch to realtime
|
||||
await setRealTimeMode(page);
|
||||
|
||||
// Click local clock
|
||||
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
|
||||
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
|
||||
|
||||
// Zoom in via button
|
||||
@@ -203,7 +264,7 @@ test.describe('Example Imagery Object', () => {
|
||||
|
||||
test('Uses low fetch priority', async ({ page }) => {
|
||||
const priority = await page.locator('.js-imageryView-image').getAttribute('fetchpriority');
|
||||
await expect(priority).toBe('low');
|
||||
expect(priority).toBe('low');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -233,14 +294,11 @@ test.describe('Example Imagery in Display Layout', () => {
|
||||
description: 'https://github.com/nasa/openmct/issues/3647'
|
||||
});
|
||||
|
||||
// Click time conductor mode button
|
||||
await page.locator('.c-mode-button').click();
|
||||
|
||||
// set realtime mode
|
||||
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
|
||||
await setRealTimeMode(page);
|
||||
|
||||
// pause/play button
|
||||
const pausePlayButton = await page.locator('.c-button.pause-play');
|
||||
const pausePlayButton = page.locator('.c-button.pause-play');
|
||||
|
||||
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
|
||||
|
||||
@@ -259,14 +317,11 @@ test.describe('Example Imagery in Display Layout', () => {
|
||||
description: 'https://github.com/nasa/openmct/issues/3647'
|
||||
});
|
||||
|
||||
// Click time conductor mode button
|
||||
await page.locator('.c-mode-button').click();
|
||||
|
||||
// set realtime mode
|
||||
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
|
||||
await setRealTimeMode(page);
|
||||
|
||||
// pause/play button
|
||||
const pausePlayButton = await page.locator('.c-button.pause-play');
|
||||
const pausePlayButton = page.locator('.c-button.pause-play');
|
||||
await pausePlayButton.click();
|
||||
await expect.soft(pausePlayButton).toHaveClass(/is-paused/);
|
||||
|
||||
@@ -544,11 +599,8 @@ async function performImageryViewOperationsAndAssert(page) {
|
||||
const nextImageButton = page.locator('.c-nav--next');
|
||||
await nextImageButton.click();
|
||||
|
||||
// Click time conductor mode button
|
||||
await page.locator('.c-mode-button').click();
|
||||
|
||||
// Select local clock mode
|
||||
await page.locator('[data-testid=conductor-modeOption-realtime]').click();
|
||||
// set realtime mode
|
||||
await setRealTimeMode(page);
|
||||
|
||||
// Zoom in on next image
|
||||
await mouseZoomOnImageAndAssert(page, 2);
|
||||
|
||||
@@ -51,10 +51,9 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
page.on('request', (request) => notebookElementsRequests.push(request));
|
||||
|
||||
//Clicking Add Page generates
|
||||
let [notebookUrlRequest, allDocsRequest] = await Promise.all([
|
||||
let [notebookUrlRequest] = await Promise.all([
|
||||
// Waits for the next request with the specified url
|
||||
page.waitForRequest(`**/openmct/${testNotebook.uuid}`),
|
||||
page.waitForRequest('**/openmct/_all_docs?include_docs=true'),
|
||||
// Triggers the request
|
||||
page.click('[aria-label="Add Page"]')
|
||||
]);
|
||||
@@ -64,15 +63,13 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
|
||||
// Assert that only two requests are made
|
||||
// Network Requests are:
|
||||
// 1) The actual POST to create the page
|
||||
// 2) The shared worker event from 👆 request
|
||||
expect(notebookElementsRequests.length).toBe(2);
|
||||
expect(notebookElementsRequests.length).toBe(1);
|
||||
|
||||
// Assert on request object
|
||||
expect(notebookUrlRequest.postDataJSON().metadata.name).toBe(testNotebook.name);
|
||||
expect(notebookUrlRequest.postDataJSON().model.persisted).toBeGreaterThanOrEqual(
|
||||
notebookUrlRequest.postDataJSON().model.modified
|
||||
);
|
||||
expect(allDocsRequest.postDataJSON().keys).toContain(testNotebook.uuid);
|
||||
|
||||
// Add an entry
|
||||
// Network Requests are:
|
||||
|
||||
@@ -134,7 +134,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
||||
// Click the context menu button for the new page
|
||||
await page.getByTitle('Open context menu').click();
|
||||
// Delete the page
|
||||
await page.getByRole('listitem', { name: 'Delete Page' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Delete Page' }).click();
|
||||
// Click OK button
|
||||
await page.getByRole('button', { name: 'Ok' }).click();
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
Testsuite for plot autoscale.
|
||||
*/
|
||||
|
||||
const { selectInspectorTab } = require('../../../../appActions');
|
||||
const { selectInspectorTab, setTimeConductorBounds } = require('../../../../appActions');
|
||||
const { test, expect } = require('../../../../pluginFixtures');
|
||||
test.use({
|
||||
viewport: {
|
||||
@@ -107,7 +107,7 @@ test.describe('Autoscale', () => {
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
// Ensure the drag worked.
|
||||
await testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00', '2.50', '3.00', '3.50']);
|
||||
await testYTicks(page, ['-0.50', '0.00', '0.50', '1.00', '1.50', '2.00', '2.50', '3.00']);
|
||||
|
||||
//Wait for canvas to stablize.
|
||||
await canvas.hover({ trial: true });
|
||||
@@ -131,12 +131,7 @@ async function setTimeRange(
|
||||
// Set a specific time range for consistency, otherwise it will change
|
||||
// on every test to a range based on the current time.
|
||||
|
||||
const timeInputs = page.locator('input.c-input--datetime');
|
||||
await timeInputs.first().click();
|
||||
await timeInputs.first().fill(start);
|
||||
|
||||
await timeInputs.nth(1).click();
|
||||
await timeInputs.nth(1).fill(end);
|
||||
await setTimeConductorBounds(page, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
@@ -26,7 +26,7 @@ necessarily be used for reference when writing new tests in this area.
|
||||
*/
|
||||
|
||||
const { test, expect } = require('../../../../pluginFixtures');
|
||||
const { selectInspectorTab } = require('../../../../appActions');
|
||||
const { selectInspectorTab, setTimeConductorBounds } = require('../../../../appActions');
|
||||
|
||||
test.describe('Log plot tests', () => {
|
||||
test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({
|
||||
@@ -87,12 +87,10 @@ async function makeOverlayPlot(page, myItemsFolderName) {
|
||||
// Set a specific time range for consistency, otherwise it will change
|
||||
// on every test to a range based on the current time.
|
||||
|
||||
const timeInputs = page.locator('input.c-input--datetime');
|
||||
await timeInputs.first().click();
|
||||
await timeInputs.first().fill('2022-03-29 22:00:00.000Z');
|
||||
const start = '2022-03-29 22:00:00.000Z';
|
||||
const end = '2022-03-29 22:00:30.000Z';
|
||||
|
||||
await timeInputs.nth(1).click();
|
||||
await timeInputs.nth(1).fill('2022-03-29 22:00:30.000Z');
|
||||
await setTimeConductorBounds(page, start, end);
|
||||
|
||||
// create overlay plot
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ const {
|
||||
waitForPlotsToRender
|
||||
} = require('../../../../appActions');
|
||||
|
||||
test.describe('Plot Tagging', () => {
|
||||
test.describe.fixme('Plot Tagging', () => {
|
||||
/**
|
||||
* Given a canvas and a set of points, tags the points on the canvas.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
@@ -167,6 +167,10 @@ test.describe('Plot Tagging', () => {
|
||||
});
|
||||
|
||||
test('Tags work with Overlay Plots', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/6822'
|
||||
});
|
||||
//Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
|
||||
test.slow();
|
||||
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
const { createDomainObjectWithDefaults } = require('../../../../appActions');
|
||||
const {
|
||||
createDomainObjectWithDefaults,
|
||||
setTimeConductorBounds
|
||||
} = require('../../../../appActions');
|
||||
const { test, expect } = require('../../../../pluginFixtures');
|
||||
|
||||
test.describe('Telemetry Table', () => {
|
||||
@@ -51,18 +54,14 @@ test.describe('Telemetry Table', () => {
|
||||
await expect(tableWrapper).toHaveClass(/is-paused/);
|
||||
|
||||
// Subtract 5 minutes from the current end bound datetime and set it
|
||||
const endTimeInput = page.locator('input[type="text"].c-input--datetime').nth(1);
|
||||
await endTimeInput.click();
|
||||
|
||||
let endDate = await endTimeInput.inputValue();
|
||||
// Bring up the time conductor popup
|
||||
let endDate = await page.locator('[aria-label="End bounds"]').textContent();
|
||||
endDate = new Date(endDate);
|
||||
|
||||
endDate.setUTCMinutes(endDate.getUTCMinutes() - 5);
|
||||
endDate = endDate.toISOString().replace(/T/, ' ');
|
||||
|
||||
await endTimeInput.fill('');
|
||||
await endTimeInput.fill(endDate);
|
||||
await page.keyboard.press('Enter');
|
||||
await setTimeConductorBounds(page, undefined, endDate);
|
||||
|
||||
await expect(tableWrapper).not.toHaveClass(/is-paused/);
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ const {
|
||||
setFixedTimeMode,
|
||||
setRealTimeMode,
|
||||
setStartOffset,
|
||||
setEndOffset
|
||||
setEndOffset,
|
||||
setTimeConductorBounds
|
||||
} = require('../../../../appActions');
|
||||
|
||||
test.describe('Time conductor operations', () => {
|
||||
@@ -40,38 +41,36 @@ test.describe('Time conductor operations', () => {
|
||||
let endDate = 'xxxx-01-01 02:00:00.000Z';
|
||||
endDate = year + endDate.substring(4);
|
||||
|
||||
const startTimeLocator = page.locator('input[type="text"]').first();
|
||||
const endTimeLocator = page.locator('input[type="text"]').nth(1);
|
||||
|
||||
// Click start time
|
||||
await startTimeLocator.click();
|
||||
|
||||
// Click end time
|
||||
await endTimeLocator.click();
|
||||
|
||||
await endTimeLocator.fill(endDate.toString());
|
||||
await startTimeLocator.fill(startDate.toString());
|
||||
await setTimeConductorBounds(page, startDate, endDate);
|
||||
|
||||
// invalid start date
|
||||
startDate = year + 1 + startDate.substring(4);
|
||||
await startTimeLocator.fill(startDate.toString());
|
||||
await endTimeLocator.click();
|
||||
await setTimeConductorBounds(page, startDate);
|
||||
|
||||
const startDateValidityStatus = await startTimeLocator.evaluate((element) =>
|
||||
// Bring up the time conductor popup
|
||||
const timeConductorMode = await page.locator('.c-compact-tc');
|
||||
await timeConductorMode.click();
|
||||
const startDateLocator = page.locator('input[type="text"]').first();
|
||||
const endDateLocator = page.locator('input[type="text"]').nth(2);
|
||||
|
||||
await endDateLocator.click();
|
||||
|
||||
const startDateValidityStatus = await startDateLocator.evaluate((element) =>
|
||||
element.checkValidity()
|
||||
);
|
||||
expect(startDateValidityStatus).not.toBeTruthy();
|
||||
|
||||
// fix to valid start date
|
||||
startDate = year - 1 + startDate.substring(4);
|
||||
await startTimeLocator.fill(startDate.toString());
|
||||
await setTimeConductorBounds(page, startDate);
|
||||
|
||||
// invalid end date
|
||||
endDate = year - 2 + endDate.substring(4);
|
||||
await endTimeLocator.fill(endDate.toString());
|
||||
await startTimeLocator.click();
|
||||
await setTimeConductorBounds(page, undefined, endDate);
|
||||
|
||||
const endDateValidityStatus = await endTimeLocator.evaluate((element) =>
|
||||
await startDateLocator.click();
|
||||
|
||||
const endDateValidityStatus = await endDateLocator.evaluate((element) =>
|
||||
element.checkValidity()
|
||||
);
|
||||
expect(endDateValidityStatus).not.toBeTruthy();
|
||||
@@ -83,11 +82,11 @@ test.describe('Time conductor operations', () => {
|
||||
test.describe('Time conductor input fields real-time mode', () => {
|
||||
test('validate input fields in real-time mode', async ({ page }) => {
|
||||
const startOffset = {
|
||||
secs: '23'
|
||||
startSecs: '23'
|
||||
};
|
||||
|
||||
const endOffset = {
|
||||
secs: '31'
|
||||
endSecs: '31'
|
||||
};
|
||||
|
||||
// Go to baseURL
|
||||
@@ -100,15 +99,13 @@ test.describe('Time conductor input fields real-time mode', () => {
|
||||
await setStartOffset(page, startOffset);
|
||||
|
||||
// Verify time was updated on time offset button
|
||||
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText(
|
||||
'00:30:23'
|
||||
);
|
||||
await expect(page.locator('.c-compact-tc__setting-value.icon-minus')).toContainText('00:30:23');
|
||||
|
||||
// Set end time offset
|
||||
await setEndOffset(page, endOffset);
|
||||
|
||||
// Verify time was updated on preceding time offset button
|
||||
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:31');
|
||||
await expect(page.locator('.c-compact-tc__setting-value.icon-plus')).toContainText('00:00:31');
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -119,12 +116,12 @@ test.describe('Time conductor input fields real-time mode', () => {
|
||||
page
|
||||
}) => {
|
||||
const startOffset = {
|
||||
mins: '30',
|
||||
secs: '23'
|
||||
startMins: '30',
|
||||
startSecs: '23'
|
||||
};
|
||||
|
||||
const endOffset = {
|
||||
secs: '01'
|
||||
endSecs: '01'
|
||||
};
|
||||
|
||||
// Convert offsets to milliseconds
|
||||
@@ -150,12 +147,10 @@ test.describe('Time conductor input fields real-time mode', () => {
|
||||
await setRealTimeMode(page);
|
||||
|
||||
// Verify updated start time offset persists after mode switch
|
||||
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText(
|
||||
'00:30:23'
|
||||
);
|
||||
await expect(page.locator('.c-compact-tc__setting-value.icon-minus')).toContainText('00:30:23');
|
||||
|
||||
// Verify updated end time offset persists after mode switch
|
||||
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01');
|
||||
await expect(page.locator('.c-compact-tc__setting-value.icon-plus')).toContainText('00:00:01');
|
||||
|
||||
// Verify url parameters persist after mode switch
|
||||
await page.waitForNavigation({ waitUntil: 'networkidle' });
|
||||
@@ -203,11 +198,11 @@ test.describe('Time Conductor History', () => {
|
||||
// with startBound at 2022-01-01 00:00:00.000Z
|
||||
// and endBound at 2022-01-01 00:00:00.200Z
|
||||
await page.goto(
|
||||
'./#/browse/mine?view=grid&tc.mode=fixed&tc.startBound=1640995200000&tc.endBound=1640995200200&tc.timeSystem=utc&hideInspector=true',
|
||||
{ waitUntil: 'networkidle' }
|
||||
'./#/browse/mine?view=grid&tc.mode=fixed&tc.startBound=1640995200000&tc.endBound=1640995200200&tc.timeSystem=utc&hideInspector=true'
|
||||
);
|
||||
await page.locator("[aria-label='Time Conductor History']").hover({ trial: true });
|
||||
await page.locator("[aria-label='Time Conductor History']").click();
|
||||
await page.getByRole('button', { name: 'Time Conductor Settings' }).click();
|
||||
await page.getByRole('button', { name: 'Time Conductor History' }).hover({ trial: true });
|
||||
await page.getByRole('button', { name: 'Time Conductor History' }).click();
|
||||
|
||||
// Validate history item format
|
||||
const historyItem = page.locator('text="2022-01-01 00:00:00 + 200ms"');
|
||||
|
||||
@@ -59,53 +59,60 @@ test.describe('Recent Objects', () => {
|
||||
await page.mouse.move(0, 100);
|
||||
await page.mouse.up();
|
||||
});
|
||||
test('Navigated objects show up in recents, object renames and deletions are reflected', async ({
|
||||
page
|
||||
}) => {
|
||||
// Verify that both created objects appear in the list and are in the correct order
|
||||
await assertInitialRecentObjectsListState();
|
||||
|
||||
// Navigate to the folder by clicking on the main object name in the recent objects list item
|
||||
await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
|
||||
await page.waitForURL(`**/${folderA.uuid}?*`);
|
||||
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
|
||||
|
||||
// Rename
|
||||
folderA.name = `${folderA.name}-NEW!`;
|
||||
await page.locator('.l-browse-bar__object-name').fill('');
|
||||
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
// Verify rename has been applied in recent objects list item and objects paths
|
||||
expect(
|
||||
await page
|
||||
.getByRole('navigation', {
|
||||
name: clock.name
|
||||
})
|
||||
.locator('a')
|
||||
.filter({
|
||||
hasText: folderA.name
|
||||
})
|
||||
.count()
|
||||
).toBeGreaterThan(0);
|
||||
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
|
||||
|
||||
// Delete
|
||||
await page.click('button[title="Show selected item in tree"]');
|
||||
// Delete the folder via the left tree pane treeitem context menu
|
||||
await page
|
||||
.getByRole('treeitem', { name: new RegExp(folderA.name) })
|
||||
.locator('a')
|
||||
.click({
|
||||
button: 'right'
|
||||
test.fixme(
|
||||
'Navigated objects show up in recents, object renames and deletions are reflected',
|
||||
async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/6818'
|
||||
});
|
||||
await page.getByRole('menuitem', { name: /Remove/ }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
// Verify that the folder and clock are no longer in the recent objects list
|
||||
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
|
||||
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
|
||||
});
|
||||
// Verify that both created objects appear in the list and are in the correct order
|
||||
await assertInitialRecentObjectsListState();
|
||||
|
||||
// Navigate to the folder by clicking on the main object name in the recent objects list item
|
||||
await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
|
||||
await page.waitForURL(`**/${folderA.uuid}?*`);
|
||||
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
|
||||
|
||||
// Rename
|
||||
folderA.name = `${folderA.name}-NEW!`;
|
||||
await page.locator('.l-browse-bar__object-name').fill('');
|
||||
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
// Verify rename has been applied in recent objects list item and objects paths
|
||||
expect(
|
||||
await page
|
||||
.getByRole('navigation', {
|
||||
name: clock.name
|
||||
})
|
||||
.locator('a')
|
||||
.filter({
|
||||
hasText: folderA.name
|
||||
})
|
||||
.count()
|
||||
).toBeGreaterThan(0);
|
||||
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
|
||||
|
||||
// Delete
|
||||
await page.click('button[title="Show selected item in tree"]');
|
||||
// Delete the folder via the left tree pane treeitem context menu
|
||||
await page
|
||||
.getByRole('treeitem', { name: new RegExp(folderA.name) })
|
||||
.locator('a')
|
||||
.click({
|
||||
button: 'right'
|
||||
});
|
||||
await page.getByRole('menuitem', { name: /Remove/ }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
// Verify that the folder and clock are no longer in the recent objects list
|
||||
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
|
||||
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
|
||||
}
|
||||
);
|
||||
|
||||
test('Clicking on an object in the path of a recent object navigates to the object', async ({
|
||||
page,
|
||||
openmctConfig
|
||||
|
||||
@@ -77,11 +77,11 @@ test.describe('Grand Search', () => {
|
||||
|
||||
// Click [aria-label="OpenMCT Search"] a >> nth=0
|
||||
await page.locator('[aria-label="Search Result"] >> nth=0').click();
|
||||
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
|
||||
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeInViewport();
|
||||
|
||||
// Fill [aria-label="OpenMCT Search"] input[type="search"]
|
||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('foo');
|
||||
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
|
||||
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).not.toBeInViewport();
|
||||
|
||||
// Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
|
||||
await page
|
||||
|
||||
@@ -52,7 +52,8 @@ test.describe('Visual - addInit', () => {
|
||||
path: path.join(__dirname, '../../helper', './addInitRestrictedNotebook.js')
|
||||
});
|
||||
//Go to baseURL
|
||||
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
||||
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||
await page.getByTitle('Collapse Browse Pane').click();
|
||||
|
||||
await createDomainObjectWithDefaults(page, { type: CUSTOM_NAME });
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ test.describe('Visual - Controlled Clock @localStorage', () => {
|
||||
|
||||
test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => {
|
||||
// Go to baseURL
|
||||
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
||||
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||
await page.getByTitle('Collapse Browse Pane').click();
|
||||
|
||||
await page.locator('a:has-text("Unnamed Overlay Plot Overlay Plot")').click();
|
||||
//Ensure that we're on the Unnamed Overlay Plot object
|
||||
|
||||
@@ -39,7 +39,8 @@ const { createDomainObjectWithDefaults } = require('../../appActions');
|
||||
test.describe('Visual - Default', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Go to baseURL and Hide Tree
|
||||
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
||||
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||
await page.getByTitle('Collapse Browse Pane').click();
|
||||
});
|
||||
test.use({
|
||||
clockOptions: {
|
||||
@@ -99,6 +100,8 @@ test.describe('Visual - Default', () => {
|
||||
let endDate = 'xxxx-01-01 02:00:00.000Z';
|
||||
endDate = year + endDate.substring(4);
|
||||
|
||||
await page.getByRole('button', { name: 'Time Conductor Settings' }).click();
|
||||
|
||||
await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
|
||||
await page.locator('input[type="text"]').first().fill(startDate.toString());
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ const percySnapshot = require('@percy/playwright');
|
||||
test.describe('Grand Search', () => {
|
||||
test.beforeEach(async ({ page, theme }) => {
|
||||
//Go to baseURL and Hide Tree
|
||||
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
|
||||
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||
await page.getByTitle('Collapse Browse Pane').click();
|
||||
});
|
||||
test.use({
|
||||
clockOptions: {
|
||||
|
||||
@@ -62,7 +62,7 @@ export default class SinewaveLimitProvider extends EventEmitter {
|
||||
const id = this.#getObjectKeyString(domainObject);
|
||||
|
||||
if (this.#isRealTime === undefined) {
|
||||
this.#updateRealTime(this.#openmct.time.clock());
|
||||
this.#updateRealTime(this.#openmct.time.getMode());
|
||||
}
|
||||
|
||||
this.#handleClockUpdate();
|
||||
@@ -92,15 +92,15 @@ export default class SinewaveLimitProvider extends EventEmitter {
|
||||
|
||||
if (observers && !this.#watchingTheClock) {
|
||||
this.#watchingTheClock = true;
|
||||
this.#openmct.time.on('clock', this.#updateRealTime, this);
|
||||
this.#openmct.time.on('modeChanged', this.#updateRealTime, this);
|
||||
} else if (!observers && this.#watchingTheClock) {
|
||||
this.#watchingTheClock = false;
|
||||
this.#openmct.time.off('clock', this.#updateRealTime, this);
|
||||
this.#openmct.time.off('modeChanged', this.#updateRealTime, this);
|
||||
}
|
||||
}
|
||||
|
||||
#updateRealTime(clock) {
|
||||
this.#isRealTime = clock !== undefined;
|
||||
#updateRealTime(mode) {
|
||||
this.#isRealTime = mode !== 'fixed';
|
||||
|
||||
if (!this.#isRealTime) {
|
||||
Object.keys(this.#observingStaleness).forEach((id) => {
|
||||
|
||||
@@ -156,9 +156,9 @@ export default function () {
|
||||
key: 'thumbnail',
|
||||
...formatThumbnail
|
||||
});
|
||||
openmct.telemetry.addProvider(getRealtimeProvider());
|
||||
openmct.telemetry.addProvider(getHistoricalProvider());
|
||||
openmct.telemetry.addProvider(getLadProvider());
|
||||
openmct.telemetry.addProvider(getRealtimeProvider(openmct));
|
||||
openmct.telemetry.addProvider(getHistoricalProvider(openmct));
|
||||
openmct.telemetry.addProvider(getLadProvider(openmct));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -207,14 +207,14 @@ function getImageLoadDelay(domainObject) {
|
||||
return imageLoadDelay;
|
||||
}
|
||||
|
||||
function getRealtimeProvider() {
|
||||
function getRealtimeProvider(openmct) {
|
||||
return {
|
||||
supportsSubscribe: (domainObject) => domainObject.type === 'example.imagery',
|
||||
subscribe: (domainObject, callback) => {
|
||||
const delay = getImageLoadDelay(domainObject);
|
||||
const interval = setInterval(() => {
|
||||
const imageSamples = getImageSamples(domainObject.configuration);
|
||||
const datum = pointForTimestamp(Date.now(), domainObject.name, imageSamples, delay);
|
||||
const datum = pointForTimestamp(openmct.time.now(), domainObject.name, imageSamples, delay);
|
||||
callback(datum);
|
||||
}, delay);
|
||||
|
||||
@@ -225,7 +225,7 @@ function getRealtimeProvider() {
|
||||
};
|
||||
}
|
||||
|
||||
function getHistoricalProvider() {
|
||||
function getHistoricalProvider(openmct) {
|
||||
return {
|
||||
supportsRequest: (domainObject, options) => {
|
||||
return domainObject.type === 'example.imagery' && options.strategy !== 'latest';
|
||||
@@ -233,17 +233,12 @@ function getHistoricalProvider() {
|
||||
request: (domainObject, options) => {
|
||||
const delay = getImageLoadDelay(domainObject);
|
||||
let start = options.start;
|
||||
const end = Math.min(options.end, Date.now());
|
||||
const end = Math.min(options.end, openmct.time.now());
|
||||
const data = [];
|
||||
while (start <= end && data.length < delay) {
|
||||
data.push(
|
||||
pointForTimestamp(
|
||||
start,
|
||||
domainObject.name,
|
||||
getImageSamples(domainObject.configuration),
|
||||
delay
|
||||
)
|
||||
);
|
||||
const imageSamples = getImageSamples(domainObject.configuration);
|
||||
const generatedDataPoint = pointForTimestamp(start, domainObject.name, imageSamples, delay);
|
||||
data.push(generatedDataPoint);
|
||||
start += delay;
|
||||
}
|
||||
|
||||
@@ -252,7 +247,7 @@ function getHistoricalProvider() {
|
||||
};
|
||||
}
|
||||
|
||||
function getLadProvider() {
|
||||
function getLadProvider(openmct) {
|
||||
return {
|
||||
supportsRequest: (domainObject, options) => {
|
||||
return domainObject.type === 'example.imagery' && options.strategy === 'latest';
|
||||
@@ -260,7 +255,7 @@ function getLadProvider() {
|
||||
request: (domainObject, options) => {
|
||||
const delay = getImageLoadDelay(domainObject);
|
||||
const datum = pointForTimestamp(
|
||||
Date.now(),
|
||||
openmct.time.now(),
|
||||
domainObject.name,
|
||||
getImageSamples(domainObject.configuration),
|
||||
delay
|
||||
|
||||
@@ -24,7 +24,7 @@ function SimpleVuePlugin() {
|
||||
container.appendChild(vm.$mount().$el);
|
||||
},
|
||||
destroy: function (container) {
|
||||
vm.$destroy();
|
||||
//vm.$destroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body></body>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
<script>
|
||||
const THIRTY_SECONDS = 30 * 1000;
|
||||
const ONE_MINUTE = THIRTY_SECONDS * 2;
|
||||
|
||||
10
package.json
@@ -12,6 +12,8 @@
|
||||
"@types/eventemitter3": "1.2.0",
|
||||
"@types/jasmine": "4.3.4",
|
||||
"@types/lodash": "4.14.192",
|
||||
"@vue/compat": "^3.1.0",
|
||||
"@vue/compiler-sfc": "^3.1.0",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
"codecov": "3.8.3",
|
||||
@@ -22,8 +24,8 @@
|
||||
"d3-scale": "3.3.0",
|
||||
"d3-selection": "3.0.0",
|
||||
"eslint": "8.43.0",
|
||||
"eslint-plugin-compat": "4.1.4",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
"eslint-plugin-compat": "4.1.4",
|
||||
"eslint-plugin-playwright": "0.12.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"eslint-plugin-vue": "9.15.0",
|
||||
@@ -66,11 +68,10 @@
|
||||
"style-loader": "3.3.3",
|
||||
"typescript": "5.1.3",
|
||||
"uuid": "9.0.0",
|
||||
"vue": "2.6.14",
|
||||
"vue": "^3.1.0",
|
||||
"vue-eslint-parser": "9.3.1",
|
||||
"vue-loader": "15.9.8",
|
||||
"vue-template-compiler": "2.6.14",
|
||||
"webpack": "5.88.0",
|
||||
"vue-loader": "^16.0.0",
|
||||
"webpack-cli": "5.1.1",
|
||||
"webpack-dev-server": "4.15.1",
|
||||
"webpack-merge": "5.9.0"
|
||||
@@ -97,6 +98,7 @@
|
||||
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots",
|
||||
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js --grep-invert @unstable",
|
||||
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js --grep-invert @couchdb",
|
||||
"test:e2e:watch": "npx playwright test --ui --config=e2e/playwright-ci.config.js",
|
||||
"test:perf": "npx playwright test --config=e2e/playwright-performance.config.js",
|
||||
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2023/gm' ./src/ui/layout/AboutDialog.vue",
|
||||
"update-copyright-date": "npm run update-about-dialog-copyright && grep -lr --null --include=*.{js,scss,vue,ts,sh,html,md,frag} 'Copyright (c) 20' . | xargs -r0 perl -pi -e 's/Copyright\\s\\(c\\)\\s20\\d\\d\\-20\\d\\d/Copyright \\(c\\)\\ 2014\\-2023/gm'",
|
||||
|
||||
42
src/MCT.js
@@ -96,6 +96,7 @@ define([
|
||||
};
|
||||
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this.defaultClock = 'local';
|
||||
[
|
||||
/**
|
||||
* Tracks current selection state of the application.
|
||||
@@ -342,7 +343,17 @@ define([
|
||||
* @param {HTMLElement} [domElement] the DOM element in which to run
|
||||
* MCT; if undefined, MCT will be run in the body of the document
|
||||
*/
|
||||
MCT.prototype.start = function (domElement = document.body, isHeadlessMode = false) {
|
||||
MCT.prototype.start = function (
|
||||
domElement = document.body.firstElementChild,
|
||||
isHeadlessMode = false
|
||||
) {
|
||||
// Create element to mount Layout if it doesn't exist
|
||||
if (domElement === null) {
|
||||
domElement = document.createElement('div');
|
||||
document.body.appendChild(domElement);
|
||||
}
|
||||
domElement.id = 'openmct-app';
|
||||
|
||||
if (this.types.get('layout') === undefined) {
|
||||
this.install(
|
||||
this.plugins.DisplayLayout({
|
||||
@@ -353,6 +364,10 @@ define([
|
||||
|
||||
this.element = domElement;
|
||||
|
||||
if (!this.time.getClock()) {
|
||||
this.time.setClock(this.defaultClock);
|
||||
}
|
||||
|
||||
this.router.route(/^\/$/, () => {
|
||||
this.router.setPath('/browse/');
|
||||
});
|
||||
@@ -365,25 +380,30 @@ define([
|
||||
*/
|
||||
|
||||
if (!isHeadlessMode) {
|
||||
const appLayout = new Vue({
|
||||
const appLayout = Vue.createApp({
|
||||
components: {
|
||||
Layout: Layout.default
|
||||
},
|
||||
provide: {
|
||||
openmct: this
|
||||
openmct: Vue.markRaw(this)
|
||||
},
|
||||
template: '<Layout ref="layout"></Layout>'
|
||||
});
|
||||
domElement.appendChild(appLayout.$mount().$el);
|
||||
const component = appLayout.mount(domElement);
|
||||
component.$nextTick(() => {
|
||||
this.layout = component.$refs.layout;
|
||||
this.app = appLayout;
|
||||
Browse(this);
|
||||
window.addEventListener('beforeunload', this.destroy);
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
});
|
||||
} else {
|
||||
window.addEventListener('beforeunload', this.destroy);
|
||||
|
||||
this.layout = appLayout.$refs.layout;
|
||||
Browse(this);
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', this.destroy);
|
||||
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
};
|
||||
|
||||
MCT.prototype.startHeadless = function () {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*****************************************************************************/
|
||||
import objectUtils from '../objects/object-utils';
|
||||
import CompositionProvider from './CompositionProvider';
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
/**
|
||||
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject
|
||||
@@ -167,7 +168,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
|
||||
*/
|
||||
add(parent, childId) {
|
||||
if (!this.includes(parent, childId)) {
|
||||
const composition = structuredClone(parent.composition);
|
||||
const composition = structuredClone(toRaw(parent.composition));
|
||||
composition.push(childId);
|
||||
this.publicAPI.objects.mutate(parent, 'composition', composition);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@ import TextAreaField from './components/controls/TextAreaField.vue';
|
||||
import TextField from './components/controls/TextField.vue';
|
||||
import ToggleSwitchField from './components/controls/ToggleSwitchField.vue';
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
import mount from 'utils/mount';
|
||||
export const DEFAULT_CONTROLS_MAP = {
|
||||
autocomplete: AutoCompleteField,
|
||||
checkbox: CheckBoxField,
|
||||
@@ -69,31 +68,40 @@ export default class FormControl {
|
||||
*/
|
||||
_getControlViewProvider(control) {
|
||||
const self = this;
|
||||
let rowComponent;
|
||||
let _destroy = null;
|
||||
|
||||
return {
|
||||
show(element, model, onChange) {
|
||||
rowComponent = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
FormControlComponent: DEFAULT_CONTROLS_MAP[control]
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
FormControlComponent: DEFAULT_CONTROLS_MAP[control]
|
||||
},
|
||||
provide: {
|
||||
openmct: self.openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
model,
|
||||
onChange
|
||||
};
|
||||
},
|
||||
template: `<FormControlComponent :model="model" @onChange="onChange"></FormControlComponent>`
|
||||
},
|
||||
provide: {
|
||||
openmct: self.openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
model,
|
||||
onChange
|
||||
};
|
||||
},
|
||||
template: `<FormControlComponent :model="model" @onChange="onChange"></FormControlComponent>`
|
||||
});
|
||||
{
|
||||
element,
|
||||
app: self.openmct.app
|
||||
}
|
||||
);
|
||||
_destroy = destroy;
|
||||
|
||||
return rowComponent;
|
||||
return vNode;
|
||||
},
|
||||
destroy() {
|
||||
rowComponent.$destroy();
|
||||
if (_destroy) {
|
||||
_destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
import FormController from './FormController';
|
||||
import FormProperties from './components/FormProperties.vue';
|
||||
|
||||
import Vue from 'vue';
|
||||
import _ from 'lodash';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
export default class FormsAPI {
|
||||
constructor(openmct) {
|
||||
@@ -156,25 +156,28 @@ export default class FormsAPI {
|
||||
formCancel = onFormAction(reject);
|
||||
});
|
||||
|
||||
const vm = new Vue({
|
||||
components: { FormProperties },
|
||||
provide: {
|
||||
openmct: self.openmct
|
||||
const { destroy } = mount(
|
||||
{
|
||||
components: { FormProperties },
|
||||
provide: {
|
||||
openmct: self.openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formStructure,
|
||||
onChange: onFormPropertyChange,
|
||||
onCancel: formCancel,
|
||||
onSave: formSave
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<FormProperties :model="formStructure" @onChange="onChange" @onCancel="onCancel" @onSave="onSave"></FormProperties>'
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formStructure,
|
||||
onChange: onFormPropertyChange,
|
||||
onCancel: formCancel,
|
||||
onSave: formSave
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<FormProperties :model="formStructure" @onChange="onChange" @onCancel="onCancel" @onSave="onSave"></FormProperties>'
|
||||
}).$mount();
|
||||
|
||||
const formElement = vm.$el;
|
||||
element.append(formElement);
|
||||
{
|
||||
element,
|
||||
app: self.openmct.app
|
||||
}
|
||||
);
|
||||
|
||||
function onFormPropertyChange(data) {
|
||||
if (onChange) {
|
||||
@@ -195,8 +198,7 @@ export default class FormsAPI {
|
||||
|
||||
function onFormAction(callback) {
|
||||
return () => {
|
||||
formElement.remove();
|
||||
vm.$destroy();
|
||||
destroy();
|
||||
|
||||
if (callback) {
|
||||
callback(changes);
|
||||
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onChange(data) {
|
||||
this.$set(this.invalidProperties, data.model.key, data.invalid);
|
||||
this.invalidProperties[data.model.key] = data.invalid;
|
||||
|
||||
this.$emit('onChange', data);
|
||||
},
|
||||
|
||||
@@ -26,9 +26,7 @@
|
||||
{{ row.name }}
|
||||
</div>
|
||||
<div class="c-form-row__state-indicator" :class="reqClass"></div>
|
||||
<div v-if="row.control" class="c-form-row__controls">
|
||||
<div ref="rowElement"></div>
|
||||
</div>
|
||||
<div v-if="row.control" ref="rowElement" class="c-form-row__controls"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -91,7 +89,7 @@ export default {
|
||||
|
||||
this.formControl.show(this.$refs.rowElement, this.row, this.onChange);
|
||||
},
|
||||
destroyed() {
|
||||
unmounted() {
|
||||
const destroy = this.formControl.destroy;
|
||||
if (destroy) {
|
||||
destroy();
|
||||
|
||||
@@ -166,7 +166,7 @@ export default {
|
||||
this.options = this.model.options;
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
unmounted() {
|
||||
document.body.removeEventListener('click', this.handleOutsideClick);
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import MenuAPI from './MenuAPI';
|
||||
import Menu from './menu';
|
||||
import { createOpenMct, createMouseEvent, resetApplicationState } from '../../utils/testing';
|
||||
import Vue from 'vue';
|
||||
|
||||
describe('The Menu API', () => {
|
||||
let openmct;
|
||||
@@ -137,14 +138,13 @@ describe('The Menu API', () => {
|
||||
it('invokes the destroy method when menu is dismissed', (done) => {
|
||||
menuOptions.onDestroy = done;
|
||||
|
||||
menuAPI.showMenu(x, y, actionsArray, menuOptions);
|
||||
spyOn(menuAPI, '_clearMenuComponent').and.callThrough();
|
||||
|
||||
const vueComponent = menuAPI.menuComponent.component;
|
||||
spyOn(vueComponent, '$destroy');
|
||||
menuAPI.showMenu(x, y, actionsArray, menuOptions);
|
||||
|
||||
document.body.click();
|
||||
|
||||
expect(vueComponent.$destroy).toHaveBeenCalled();
|
||||
expect(menuAPI._clearMenuComponent).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('invokes the onDestroy callback if passed in', (done) => {
|
||||
@@ -185,7 +185,7 @@ describe('The Menu API', () => {
|
||||
superMenuItem.dispatchEvent(mouseOverEvent);
|
||||
const itemDescription = document.querySelector('.l-item-description__description');
|
||||
|
||||
menuAPI.menuComponent.component.$nextTick(() => {
|
||||
Vue.nextTick(() => {
|
||||
expect(menuElement).not.toBeNull();
|
||||
expect(itemDescription.innerText).toEqual(actionsArray[0].description);
|
||||
|
||||
|
||||
@@ -20,17 +20,16 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<div class="c-menu" :class="options.menuClass">
|
||||
<div class="c-menu" :class="options.menuClass" :style="styleObject">
|
||||
<ul v-if="options.actions.length && options.actions[0].length" role="menu">
|
||||
<template v-for="(actionGroups, index) in options.actions">
|
||||
<div :key="index" role="group">
|
||||
<template v-for="(actionGroups, index) in options.actions" :key="index">
|
||||
<div role="group">
|
||||
<li
|
||||
v-for="action in actionGroups"
|
||||
:key="action.name"
|
||||
role="menuitem"
|
||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||
:title="action.description"
|
||||
:data-testid="action.testId || false"
|
||||
@click="action.onItemClicked"
|
||||
>
|
||||
{{ action.name }}
|
||||
@@ -42,8 +41,8 @@
|
||||
class="c-menu__section-separator"
|
||||
></div>
|
||||
<li v-if="actionGroups.length === 0" :key="index">No actions defined.</li>
|
||||
</div></template
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</ul>
|
||||
|
||||
<ul v-else role="menu">
|
||||
@@ -53,7 +52,6 @@
|
||||
role="menuitem"
|
||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||
:title="action.description"
|
||||
:data-testid="action.testId || false"
|
||||
@click="action.onItemClicked"
|
||||
>
|
||||
{{ action.name }}
|
||||
@@ -64,7 +62,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popupMenuMixin from '../mixins/popupMenuMixin';
|
||||
export default {
|
||||
mixins: [popupMenuMixin],
|
||||
inject: ['options']
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -20,21 +20,20 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<div class="c-menu" :class="[options.menuClass, 'c-super-menu']">
|
||||
<div class="c-menu" :class="[options.menuClass, 'c-super-menu']" :style="styleObject">
|
||||
<ul
|
||||
v-if="options.actions.length && options.actions[0].length"
|
||||
role="menu"
|
||||
class="c-super-menu__menu"
|
||||
>
|
||||
<template v-for="(actionGroups, index) in options.actions">
|
||||
<div :key="index" role="group">
|
||||
<template v-for="(actionGroups, index) in options.actions" :key="index">
|
||||
<div role="group">
|
||||
<li
|
||||
v-for="action in actionGroups"
|
||||
:key="action.name"
|
||||
role="menuitem"
|
||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||
:title="action.description"
|
||||
:data-testid="action.testId || false"
|
||||
@click="action.onItemClicked"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
@@ -59,7 +58,6 @@
|
||||
role="menuitem"
|
||||
:class="action.cssClass"
|
||||
:title="action.description"
|
||||
:data-testid="action.testId || false"
|
||||
@click="action.onItemClicked"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
@@ -80,9 +78,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popupMenuMixin from '../mixins/popupMenuMixin';
|
||||
export default {
|
||||
mixins: [popupMenuMixin],
|
||||
inject: ['options'],
|
||||
data: function () {
|
||||
return {
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import MenuComponent from './components/Menu.vue';
|
||||
import SuperMenuComponent from './components/SuperMenu.vue';
|
||||
import Vue from 'vue';
|
||||
import { h } from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
export const MENU_PLACEMENT = {
|
||||
TOP: 'top',
|
||||
@@ -51,138 +52,68 @@ class Menu extends EventEmitter {
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
this.emit('destroy');
|
||||
document.body.removeChild(this.component.$el);
|
||||
if (this.destroy) {
|
||||
this.destroy();
|
||||
this.destroy = null;
|
||||
}
|
||||
document.removeEventListener('click', this.dismiss);
|
||||
this.component.$destroy();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.component.$mount();
|
||||
document.body.appendChild(this.component.$el);
|
||||
|
||||
let position = this._calculatePopupPosition(this.component.$el);
|
||||
|
||||
this.component.$el.style.left = `${position.x}px`;
|
||||
this.component.$el.style.top = `${position.y}px`;
|
||||
|
||||
document.addEventListener('click', this.dismiss);
|
||||
this.emit('destroy');
|
||||
}
|
||||
|
||||
showMenu() {
|
||||
this.component = new Vue({
|
||||
components: {
|
||||
MenuComponent
|
||||
if (this.destroy) {
|
||||
return;
|
||||
}
|
||||
const { vNode, destroy } = mount({
|
||||
render() {
|
||||
return h(MenuComponent);
|
||||
},
|
||||
provide: {
|
||||
options: this.options
|
||||
},
|
||||
template: '<menu-component />'
|
||||
// TODO: Remove this exception upon full migration to Vue 3
|
||||
// https://v3-migration.vuejs.org/breaking-changes/render-function-api.html#render-function-argument
|
||||
compatConfig: {
|
||||
RENDER_FUNCTION: false
|
||||
}
|
||||
});
|
||||
|
||||
this.el = vNode.el;
|
||||
this.destroy = destroy;
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
showSuperMenu() {
|
||||
this.component = new Vue({
|
||||
components: {
|
||||
SuperMenuComponent
|
||||
const { vNode, destroy } = mount({
|
||||
data() {
|
||||
return {
|
||||
top: '0px',
|
||||
left: '0px'
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return h(SuperMenuComponent);
|
||||
},
|
||||
provide: {
|
||||
options: this.options
|
||||
},
|
||||
template: '<super-menu-component />'
|
||||
// TODO: Remove this exception upon full migration to Vue 3
|
||||
// https://v3-migration.vuejs.org/breaking-changes/render-function-api.html#render-function-argument
|
||||
compatConfig: {
|
||||
RENDER_FUNCTION: false
|
||||
}
|
||||
});
|
||||
|
||||
this.el = vNode.el;
|
||||
this.destroy = destroy;
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_calculatePopupPosition(menuElement) {
|
||||
let menuDimensions = menuElement.getBoundingClientRect();
|
||||
|
||||
if (!this.options.placement) {
|
||||
this.options.placement = MENU_PLACEMENT.BOTTOM_RIGHT;
|
||||
}
|
||||
|
||||
const menuPosition = this._getMenuPositionBasedOnPlacement(menuDimensions);
|
||||
|
||||
return this._preventMenuOverflow(menuPosition, menuDimensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getMenuPositionBasedOnPlacement(menuDimensions) {
|
||||
let eventPosX = this.options.x;
|
||||
let eventPosY = this.options.y;
|
||||
|
||||
// Adjust popup menu based on placement
|
||||
switch (this.options.placement) {
|
||||
case MENU_PLACEMENT.TOP:
|
||||
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
|
||||
eventPosY = this.options.y - menuDimensions.height;
|
||||
break;
|
||||
case MENU_PLACEMENT.BOTTOM:
|
||||
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
|
||||
break;
|
||||
case MENU_PLACEMENT.LEFT:
|
||||
eventPosX = this.options.x - menuDimensions.width;
|
||||
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
|
||||
break;
|
||||
case MENU_PLACEMENT.RIGHT:
|
||||
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
|
||||
break;
|
||||
case MENU_PLACEMENT.TOP_LEFT:
|
||||
eventPosX = this.options.x - menuDimensions.width;
|
||||
eventPosY = this.options.y - menuDimensions.height;
|
||||
break;
|
||||
case MENU_PLACEMENT.TOP_RIGHT:
|
||||
eventPosY = this.options.y - menuDimensions.height;
|
||||
break;
|
||||
case MENU_PLACEMENT.BOTTOM_LEFT:
|
||||
eventPosX = this.options.x - menuDimensions.width;
|
||||
break;
|
||||
case MENU_PLACEMENT.BOTTOM_RIGHT:
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
x: eventPosX,
|
||||
y: eventPosY
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_preventMenuOverflow(menuPosition, menuDimensions) {
|
||||
let { x: eventPosX, y: eventPosY } = menuPosition;
|
||||
let overflowX = eventPosX + menuDimensions.width - document.body.clientWidth;
|
||||
let overflowY = eventPosY + menuDimensions.height - document.body.clientHeight;
|
||||
|
||||
if (overflowX > 0) {
|
||||
eventPosX = eventPosX - overflowX;
|
||||
}
|
||||
|
||||
if (overflowY > 0) {
|
||||
eventPosY = eventPosY - overflowY;
|
||||
}
|
||||
|
||||
if (eventPosX < 0) {
|
||||
eventPosX = 0;
|
||||
}
|
||||
|
||||
if (eventPosY < 0) {
|
||||
eventPosY = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
x: eventPosX,
|
||||
y: eventPosY
|
||||
};
|
||||
show() {
|
||||
document.body.appendChild(this.el);
|
||||
document.addEventListener('click', this.dismiss);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
111
src/api/menu/mixins/popupMenuMixin.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import { MENU_PLACEMENT } from '../menu';
|
||||
export default {
|
||||
methods: {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_calculatePopupPosition(menuElement) {
|
||||
let menuDimensions = menuElement.getBoundingClientRect();
|
||||
|
||||
if (!this.options.placement) {
|
||||
this.options.placement = MENU_PLACEMENT.BOTTOM_RIGHT;
|
||||
}
|
||||
|
||||
const menuPosition = this._getMenuPositionBasedOnPlacement(menuDimensions);
|
||||
|
||||
return this._preventMenuOverflow(menuPosition, menuDimensions);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getMenuPositionBasedOnPlacement(menuDimensions) {
|
||||
let eventPosX = this.options.x;
|
||||
let eventPosY = this.options.y;
|
||||
|
||||
// Adjust popup menu based on placement
|
||||
switch (this.options.placement) {
|
||||
case MENU_PLACEMENT.TOP:
|
||||
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
|
||||
eventPosY = this.options.y - menuDimensions.height;
|
||||
break;
|
||||
case MENU_PLACEMENT.BOTTOM:
|
||||
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
|
||||
break;
|
||||
case MENU_PLACEMENT.LEFT:
|
||||
eventPosX = this.options.x - menuDimensions.width;
|
||||
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
|
||||
break;
|
||||
case MENU_PLACEMENT.RIGHT:
|
||||
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
|
||||
break;
|
||||
case MENU_PLACEMENT.TOP_LEFT:
|
||||
eventPosX = this.options.x - menuDimensions.width;
|
||||
eventPosY = this.options.y - menuDimensions.height;
|
||||
break;
|
||||
case MENU_PLACEMENT.TOP_RIGHT:
|
||||
eventPosY = this.options.y - menuDimensions.height;
|
||||
break;
|
||||
case MENU_PLACEMENT.BOTTOM_LEFT:
|
||||
eventPosX = this.options.x - menuDimensions.width;
|
||||
break;
|
||||
case MENU_PLACEMENT.BOTTOM_RIGHT:
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
x: eventPosX,
|
||||
y: eventPosY
|
||||
};
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_preventMenuOverflow(menuPosition, menuDimensions) {
|
||||
let { x: eventPosX, y: eventPosY } = menuPosition;
|
||||
let overflowX = eventPosX + menuDimensions.width - document.body.clientWidth;
|
||||
let overflowY = eventPosY + menuDimensions.height - document.body.clientHeight;
|
||||
|
||||
if (overflowX > 0) {
|
||||
eventPosX = eventPosX - overflowX;
|
||||
}
|
||||
|
||||
if (overflowY > 0) {
|
||||
eventPosY = eventPosY - overflowY;
|
||||
}
|
||||
|
||||
if (eventPosX < 0) {
|
||||
eventPosX = 0;
|
||||
}
|
||||
|
||||
if (eventPosY < 0) {
|
||||
eventPosY = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
x: eventPosX,
|
||||
y: eventPosY
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
const position = this._calculatePopupPosition(this.$el);
|
||||
this.top = position.y;
|
||||
this.left = position.x;
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
top: '0px',
|
||||
left: '0px'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
styleObject() {
|
||||
return {
|
||||
top: `${this.top}px`,
|
||||
left: `${this.left}px`
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -159,6 +159,9 @@ class InMemorySearchProvider {
|
||||
return pendingQuery.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
#localQueryFallBack({ queryId, searchType, query, maxResults }) {
|
||||
if (searchType === this.searchTypes.OBJECTS) {
|
||||
return this.localSearchForObjects(queryId, query, maxResults);
|
||||
@@ -371,7 +374,7 @@ class InMemorySearchProvider {
|
||||
delete provider.pendingIndex[keyString];
|
||||
|
||||
try {
|
||||
if (domainObject) {
|
||||
if (domainObject && domainObject.identifier) {
|
||||
await provider.index(domainObject);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -96,12 +96,26 @@ class MutableDomainObject {
|
||||
//Emit events specific to properties affected
|
||||
let parentPropertiesList = path.split('.');
|
||||
for (let index = parentPropertiesList.length; index > 0; index--) {
|
||||
let pathToThisProperty = parentPropertiesList.slice(0, index);
|
||||
let parentPropertyPath = parentPropertiesList.slice(0, index).join('.');
|
||||
this._globalEventEmitter.emit(
|
||||
qualifiedEventName(this, parentPropertyPath),
|
||||
_.get(this, parentPropertyPath),
|
||||
_.get(oldModel, parentPropertyPath)
|
||||
);
|
||||
|
||||
const lastPathElement = parentPropertiesList[index - 1];
|
||||
// Also emit an event for the array whose element has changed so developers do not need to listen to every element of the array.
|
||||
if (lastPathElement.endsWith(']')) {
|
||||
const arrayPathElement = lastPathElement.substring(0, lastPathElement.lastIndexOf('['));
|
||||
pathToThisProperty[index - 1] = arrayPathElement;
|
||||
const pathToArrayString = pathToThisProperty.join('.');
|
||||
this._globalEventEmitter.emit(
|
||||
qualifiedEventName(this, pathToArrayString),
|
||||
_.get(this, pathToArrayString),
|
||||
_.get(oldModel, pathToArrayString)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Emit events for listeners of child properties when parent changes.
|
||||
|
||||
@@ -23,6 +23,9 @@ describe('The Object API', () => {
|
||||
return USERNAME;
|
||||
}
|
||||
});
|
||||
},
|
||||
getPossibleRoles() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
};
|
||||
openmct = createOpenMct();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import DialogComponent from './components/DialogComponent.vue';
|
||||
import Overlay from './Overlay';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
class Dialog extends Overlay {
|
||||
constructor({ iconClass, message, title, hint, timestamp, ...options }) {
|
||||
let component = new Vue({
|
||||
const { vNode, destroy } = mount({
|
||||
components: {
|
||||
DialogComponent: DialogComponent
|
||||
},
|
||||
@@ -16,17 +16,17 @@ class Dialog extends Overlay {
|
||||
timestamp
|
||||
},
|
||||
template: '<dialog-component></dialog-component>'
|
||||
}).$mount();
|
||||
});
|
||||
|
||||
super({
|
||||
element: component.$el,
|
||||
element: vNode.el,
|
||||
size: 'fit',
|
||||
dismissable: false,
|
||||
...options
|
||||
});
|
||||
|
||||
this.once('destroy', () => {
|
||||
component.$destroy();
|
||||
destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import OverlayComponent from './components/OverlayComponent.vue';
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
const cssClasses = {
|
||||
large: 'l-overlay-large',
|
||||
@@ -28,18 +28,25 @@ class Overlay extends EventEmitter {
|
||||
this.autoHide = autoHide;
|
||||
this.dismissable = dismissable !== false;
|
||||
|
||||
this.component = new Vue({
|
||||
components: {
|
||||
OverlayComponent: OverlayComponent
|
||||
const { destroy } = mount(
|
||||
{
|
||||
components: {
|
||||
OverlayComponent: OverlayComponent
|
||||
},
|
||||
provide: {
|
||||
dismiss: this.notifyAndDismiss.bind(this),
|
||||
element,
|
||||
buttons,
|
||||
dismissable: this.dismissable
|
||||
},
|
||||
template: '<overlay-component></overlay-component>'
|
||||
},
|
||||
provide: {
|
||||
dismiss: this.notifyAndDismiss.bind(this),
|
||||
element,
|
||||
buttons,
|
||||
dismissable: this.dismissable
|
||||
},
|
||||
template: '<overlay-component></overlay-component>'
|
||||
});
|
||||
{
|
||||
element: this.container
|
||||
}
|
||||
);
|
||||
|
||||
this.destroy = destroy;
|
||||
|
||||
if (onDestroy) {
|
||||
this.once('destroy', onDestroy);
|
||||
@@ -53,7 +60,7 @@ class Overlay extends EventEmitter {
|
||||
dismiss() {
|
||||
this.emit('destroy');
|
||||
document.body.removeChild(this.container);
|
||||
this.component.$destroy();
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
//Ensures that any callers are notified that the overlay is dismissed
|
||||
@@ -67,7 +74,6 @@ class Overlay extends EventEmitter {
|
||||
**/
|
||||
show() {
|
||||
document.body.appendChild(this.container);
|
||||
this.container.appendChild(this.component.$mount().$el);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import ProgressDialogComponent from './components/ProgressDialogComponent.vue';
|
||||
import Overlay from './Overlay';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
let component;
|
||||
|
||||
class ProgressDialog extends Overlay {
|
||||
constructor({
|
||||
progressPerc,
|
||||
@@ -15,7 +14,7 @@ class ProgressDialog extends Overlay {
|
||||
timestamp,
|
||||
...options
|
||||
}) {
|
||||
component = new Vue({
|
||||
const { vNode, destroy } = mount({
|
||||
components: {
|
||||
ProgressDialogComponent: ProgressDialogComponent
|
||||
},
|
||||
@@ -35,17 +34,18 @@ class ProgressDialog extends Overlay {
|
||||
};
|
||||
},
|
||||
template: '<progress-dialog-component :model="model"></progress-dialog-component>'
|
||||
}).$mount();
|
||||
});
|
||||
component = vNode.componentInstance;
|
||||
|
||||
super({
|
||||
element: component.$el,
|
||||
element: vNode.el,
|
||||
size: 'fit',
|
||||
dismissable: false,
|
||||
...options
|
||||
});
|
||||
|
||||
this.once('destroy', () => {
|
||||
component.$destroy();
|
||||
destroy();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
import SelectionComponent from './components/SelectionComponent.vue';
|
||||
import Overlay from './Overlay';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
class Selection extends Overlay {
|
||||
constructor({
|
||||
@@ -34,7 +34,7 @@ class Selection extends Overlay {
|
||||
currentSelection,
|
||||
...options
|
||||
}) {
|
||||
let component = new Vue({
|
||||
const { vNode, destroy } = mount({
|
||||
components: {
|
||||
SelectionComponent: SelectionComponent
|
||||
},
|
||||
@@ -47,7 +47,9 @@ class Selection extends Overlay {
|
||||
currentSelection
|
||||
},
|
||||
template: '<selection-component></selection-component>'
|
||||
}).$mount();
|
||||
});
|
||||
|
||||
const component = vNode.componentInstance;
|
||||
|
||||
super({
|
||||
element: component.$el,
|
||||
@@ -59,7 +61,7 @@ class Selection extends Overlay {
|
||||
});
|
||||
|
||||
this.once('destroy', () => {
|
||||
component.$destroy();
|
||||
destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
v-if="dismissable"
|
||||
aria-label="Close"
|
||||
class="c-click-icon c-overlay__close-button icon-x"
|
||||
@click="destroy"
|
||||
@click.stop="destroy"
|
||||
></button>
|
||||
<div
|
||||
ref="element"
|
||||
@@ -71,16 +71,16 @@ export default {
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
destroy: function () {
|
||||
destroy() {
|
||||
if (this.dismissable) {
|
||||
this.dismiss();
|
||||
}
|
||||
},
|
||||
buttonClickHandler: function (method) {
|
||||
buttonClickHandler(method) {
|
||||
method();
|
||||
this.$emit('destroy');
|
||||
},
|
||||
getElementForFocus: function () {
|
||||
getElementForFocus() {
|
||||
const defaultElement = this.$refs.element;
|
||||
if (!this.$refs.buttons) {
|
||||
return defaultElement;
|
||||
|
||||
@@ -204,27 +204,25 @@ export default class TelemetryAPI {
|
||||
*/
|
||||
standardizeRequestOptions(options = {}) {
|
||||
if (!Object.hasOwn(options, 'start')) {
|
||||
if (options.timeContext?.bounds()) {
|
||||
options.start = options.timeContext.bounds().start;
|
||||
const bounds = options.timeContext?.getBounds();
|
||||
if (bounds?.start) {
|
||||
options.start = options.timeContext.getBounds().start;
|
||||
} else {
|
||||
options.start = this.openmct.time.bounds().start;
|
||||
options.start = this.openmct.time.getBounds().start;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'end')) {
|
||||
if (options.timeContext?.bounds()) {
|
||||
options.end = options.timeContext.bounds().end;
|
||||
const bounds = options.timeContext?.getBounds();
|
||||
if (bounds?.end) {
|
||||
options.end = options.timeContext.getBounds().end;
|
||||
} else {
|
||||
options.end = this.openmct.time.bounds().end;
|
||||
options.end = this.openmct.time.getBounds().end;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'domain')) {
|
||||
options.domain = this.openmct.time.timeSystem().key;
|
||||
}
|
||||
|
||||
if (!Object.hasOwn(options, 'timeContext')) {
|
||||
options.timeContext = this.openmct.time;
|
||||
options.domain = this.openmct.time.getTimeSystem().key;
|
||||
}
|
||||
|
||||
return options;
|
||||
|
||||
@@ -29,15 +29,20 @@ describe('Telemetry API', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
openmct = {
|
||||
time: jasmine.createSpyObj('timeAPI', ['timeSystem', 'bounds']),
|
||||
time: jasmine.createSpyObj('timeAPI', ['timeSystem', 'getTimeSystem', 'bounds', 'getBounds']),
|
||||
types: jasmine.createSpyObj('typeRegistry', ['get'])
|
||||
};
|
||||
|
||||
openmct.time.timeSystem.and.returnValue({ key: 'system' });
|
||||
openmct.time.getTimeSystem.and.returnValue({ key: 'system' });
|
||||
openmct.time.bounds.and.returnValue({
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
openmct.time.getBounds.and.returnValue({
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
telemetryAPI = new TelemetryAPI(openmct);
|
||||
});
|
||||
|
||||
@@ -261,16 +266,14 @@ describe('Telemetry API', () => {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system',
|
||||
timeContext: jasmine.any(Object)
|
||||
domain: 'system'
|
||||
});
|
||||
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system',
|
||||
timeContext: jasmine.any(Object)
|
||||
domain: 'system'
|
||||
});
|
||||
|
||||
telemetryProvider.supportsRequest.calls.reset();
|
||||
@@ -281,16 +284,14 @@ describe('Telemetry API', () => {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system',
|
||||
timeContext: jasmine.any(Object)
|
||||
domain: 'system'
|
||||
});
|
||||
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
signal,
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'system',
|
||||
timeContext: jasmine.any(Object)
|
||||
domain: 'system'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -309,16 +310,14 @@ describe('Telemetry API', () => {
|
||||
start: 20,
|
||||
end: 30,
|
||||
domain: 'someDomain',
|
||||
signal,
|
||||
timeContext: jasmine.any(Object)
|
||||
signal
|
||||
});
|
||||
|
||||
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
|
||||
start: 20,
|
||||
end: 30,
|
||||
domain: 'someDomain',
|
||||
signal,
|
||||
timeContext: jasmine.any(Object)
|
||||
signal
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import _ from 'lodash';
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import { LOADED_ERROR, TIMESYSTEM_KEY_NOTIFICATION, TIMESYSTEM_KEY_WARNING } from './constants';
|
||||
import { TIME_CONTEXT_EVENTS } from '../time/constants';
|
||||
|
||||
/**
|
||||
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject
|
||||
@@ -60,13 +61,17 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this.futureBuffer = [];
|
||||
this.parseTime = undefined;
|
||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||
if (!Object.hasOwn(options, 'timeContext')) {
|
||||
options.timeContext = this.openmct.time;
|
||||
}
|
||||
this.options = options;
|
||||
this.unsubscribe = undefined;
|
||||
this.options = this.openmct.telemetry.standardizeRequestOptions(options);
|
||||
this.pageState = undefined;
|
||||
this.lastBounds = undefined;
|
||||
this.requestAbort = undefined;
|
||||
this.isStrategyLatest = this.options.strategy === 'latest';
|
||||
this.dataOutsideTimeBounds = false;
|
||||
this.modeChanged = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,11 +83,11 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this._error(LOADED_ERROR);
|
||||
}
|
||||
|
||||
this._setTimeSystem(this.options.timeContext.timeSystem());
|
||||
this.lastBounds = this.options.timeContext.bounds();
|
||||
|
||||
this._setTimeSystem(this.options.timeContext.getTimeSystem());
|
||||
this.lastBounds = this.options.timeContext.getBounds();
|
||||
this._watchBounds();
|
||||
this._watchTimeSystem();
|
||||
this._watchTimeModeChange();
|
||||
|
||||
this._requestHistoricalTelemetry();
|
||||
this._initiateSubscriptionTelemetry();
|
||||
@@ -101,6 +106,7 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
|
||||
this._unwatchBounds();
|
||||
this._unwatchTimeSystem();
|
||||
this._unwatchTimeModeChange();
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
@@ -121,7 +127,7 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
async _requestHistoricalTelemetry() {
|
||||
let options = { ...this.options };
|
||||
let options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
|
||||
const historicalProvider = this.openmct.telemetry.findRequestProvider(
|
||||
this.domainObject,
|
||||
options
|
||||
@@ -301,6 +307,12 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
_bounds(bounds, isTick) {
|
||||
if (this.modeChanged) {
|
||||
this.modeChanged = false;
|
||||
this._reset();
|
||||
return;
|
||||
}
|
||||
|
||||
let startChanged = this.lastBounds.start !== bounds.start;
|
||||
let endChanged = this.lastBounds.end !== bounds.end;
|
||||
|
||||
@@ -433,6 +445,11 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
this._reset();
|
||||
}
|
||||
|
||||
_timeModeChanged() {
|
||||
//We're need this so that when the bounds change comes in after this mode change, we can reset and request historic telemetry
|
||||
this.modeChanged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the telemetry data of the collection, and re-request
|
||||
* historical telemetry
|
||||
@@ -450,19 +467,35 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the _bounds callback to the 'bounds' timeAPI listener
|
||||
* adds the _bounds callback to the 'boundsChanged' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_watchBounds() {
|
||||
this.options.timeContext.on('bounds', this._bounds, this);
|
||||
this.options.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this._bounds, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes the _bounds callback from the 'bounds' timeAPI listener
|
||||
* removes the _bounds callback from the 'boundsChanged' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_unwatchBounds() {
|
||||
this.options.timeContext.off('bounds', this._bounds, this);
|
||||
this.options.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this._bounds, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the _timeModeChanged callback to the 'modeChanged' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_watchTimeModeChange() {
|
||||
this.options.timeContext.on(TIME_CONTEXT_EVENTS.modeChanged, this._timeModeChanged, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes the _timeModeChanged callback from the 'modeChanged' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_unwatchTimeModeChange() {
|
||||
this.options.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this._timeModeChanged, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,7 +503,11 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
_watchTimeSystem() {
|
||||
this.options.timeContext.on('timeSystem', this._setTimeSystemAndFetchData, this);
|
||||
this.options.timeContext.on(
|
||||
TIME_CONTEXT_EVENTS.timeSystemChanged,
|
||||
this._setTimeSystemAndFetchData,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -478,7 +515,11 @@ export default class TelemetryCollection extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
_unwatchTimeSystem() {
|
||||
this.options.timeContext.off('timeSystem', this._setTimeSystemAndFetchData, this);
|
||||
this.options.timeContext.off(
|
||||
TIME_CONTEXT_EVENTS.timeSystemChanged,
|
||||
this._setTimeSystemAndFetchData,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import TimeContext, { TIME_CONTEXT_EVENTS } from './TimeContext';
|
||||
import TimeContext from './TimeContext';
|
||||
import { MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants';
|
||||
|
||||
/**
|
||||
* The IndependentTimeContext handles getting and setting time of the openmct application in general.
|
||||
@@ -46,7 +47,7 @@ class IndependentTimeContext extends TimeContext {
|
||||
this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext);
|
||||
}
|
||||
|
||||
bounds(newBounds) {
|
||||
bounds() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.bounds(...arguments);
|
||||
} else {
|
||||
@@ -54,7 +55,23 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
tick(timestamp) {
|
||||
getBounds() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.getBounds();
|
||||
} else {
|
||||
return super.getBounds();
|
||||
}
|
||||
}
|
||||
|
||||
setBounds() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.setBounds(...arguments);
|
||||
} else {
|
||||
return super.setBounds(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
tick() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.tick(...arguments);
|
||||
} else {
|
||||
@@ -62,7 +79,7 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
clockOffsets(offsets) {
|
||||
clockOffsets() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.clockOffsets(...arguments);
|
||||
} else {
|
||||
@@ -70,11 +87,19 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
stopClock() {
|
||||
getClockOffsets() {
|
||||
if (this.upstreamTimeContext) {
|
||||
this.upstreamTimeContext.stopClock();
|
||||
return this.upstreamTimeContext.getClockOffsets();
|
||||
} else {
|
||||
super.stopClock();
|
||||
return super.getClockOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
setClockOffsets() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.setClockOffsets(...arguments);
|
||||
} else {
|
||||
return super.setClockOffsets(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,10 +111,19 @@ class IndependentTimeContext extends TimeContext {
|
||||
return this.globalTimeContext.timeSystem(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time system of the TimeAPI.
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method getTimeSystem
|
||||
*/
|
||||
getTimeSystem() {
|
||||
return this.globalTimeContext.getTimeSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active clock. Tick source will be immediately subscribed to
|
||||
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
||||
* can be unset by calling {@link stopClock}.
|
||||
* and ticking will begin. Offsets from 'now' must also be provided.
|
||||
*
|
||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
||||
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
||||
@@ -126,15 +160,19 @@ class IndependentTimeContext extends TimeContext {
|
||||
this.activeClock = clock;
|
||||
|
||||
/**
|
||||
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
||||
* The active clock has changed.
|
||||
* @event clock
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Clock} clock The newly activated clock, or undefined
|
||||
* if the system is no longer following a clock source
|
||||
*/
|
||||
this.emit('clock', this.activeClock);
|
||||
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
||||
|
||||
if (this.activeClock !== undefined) {
|
||||
//set the mode here or isRealtime will be false even if we're in clock mode
|
||||
this.setMode(REALTIME_MODE_KEY);
|
||||
|
||||
this.clockOffsets(offsets);
|
||||
this.activeClock.on('tick', this.tick);
|
||||
}
|
||||
@@ -145,6 +183,138 @@ class IndependentTimeContext extends TimeContext {
|
||||
return this.activeClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active clock.
|
||||
* @return {Clock} the currently active clock;
|
||||
*/
|
||||
getClock() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.getClock();
|
||||
}
|
||||
|
||||
return this.activeClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active clock. Tick source will be immediately subscribed to
|
||||
* and the currently ticking will begin.
|
||||
* Offsets from 'now', if provided, will be used to set realtime mode offsets
|
||||
*
|
||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Clock} the currently active clock;
|
||||
*/
|
||||
setClock(keyOrClock) {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.setClock(...arguments);
|
||||
}
|
||||
|
||||
let clock;
|
||||
|
||||
if (typeof keyOrClock === 'string') {
|
||||
clock = this.globalTimeContext.clocks.get(keyOrClock);
|
||||
if (clock === undefined) {
|
||||
throw `Unknown clock ${keyOrClock}. Has it been registered with 'addClock'?`;
|
||||
}
|
||||
} else if (typeof keyOrClock === 'object') {
|
||||
clock = keyOrClock;
|
||||
if (!this.globalTimeContext.clocks.has(clock.key)) {
|
||||
throw `Unknown clock ${keyOrClock.key}. Has it been registered with 'addClock'?`;
|
||||
}
|
||||
}
|
||||
|
||||
const previousClock = this.activeClock;
|
||||
if (previousClock) {
|
||||
previousClock.off('tick', this.tick);
|
||||
}
|
||||
|
||||
this.activeClock = clock;
|
||||
this.activeClock.on('tick', this.tick);
|
||||
|
||||
/**
|
||||
* The active clock has changed.
|
||||
* @event clock
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Clock} clock The newly activated clock, or undefined
|
||||
* if the system is no longer following a clock source
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
||||
|
||||
return this.activeClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current mode.
|
||||
* @return {Mode} the current mode;
|
||||
*/
|
||||
getMode() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.getMode();
|
||||
}
|
||||
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode to either fixed or realtime.
|
||||
*
|
||||
* @param {Mode} mode The mode to activate
|
||||
* @param {TimeBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Mode} the currently active mode;
|
||||
*/
|
||||
setMode(mode, offsetsOrBounds) {
|
||||
if (!mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.setMode(...arguments);
|
||||
}
|
||||
|
||||
if (mode === MODES.realtime && this.activeClock === undefined) {
|
||||
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
|
||||
}
|
||||
|
||||
if (mode !== this.mode) {
|
||||
this.mode = mode;
|
||||
/**
|
||||
* The active mode has changed.
|
||||
* @event modeChanged
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Mode} mode The newly activated mode
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode));
|
||||
}
|
||||
|
||||
//We are also going to set bounds here
|
||||
if (offsetsOrBounds !== undefined) {
|
||||
if (this.mode === REALTIME_MODE_KEY) {
|
||||
this.setClockOffsets(offsetsOrBounds);
|
||||
} else {
|
||||
this.setBounds(offsetsOrBounds);
|
||||
}
|
||||
}
|
||||
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
isRealTime() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.isRealTime(...arguments);
|
||||
} else {
|
||||
return super.isRealTime(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
now() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.now(...arguments);
|
||||
} else {
|
||||
return super.now(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes this time context to follow another time context (either the global context, or another upstream time context)
|
||||
* This allows views to have their own time context which points to the appropriate upstream context as necessary, achieving nesting.
|
||||
@@ -152,7 +322,7 @@ class IndependentTimeContext extends TimeContext {
|
||||
followTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
if (this.upstreamTimeContext) {
|
||||
TIME_CONTEXT_EVENTS.forEach((eventName) => {
|
||||
Object.values(TIME_CONTEXT_EVENTS).forEach((eventName) => {
|
||||
const thisTimeContext = this;
|
||||
this.upstreamTimeContext.on(eventName, passthrough);
|
||||
this.unlisteners.push(() => {
|
||||
@@ -197,6 +367,7 @@ class IndependentTimeContext extends TimeContext {
|
||||
|
||||
// Emit bounds so that views that are changing context get the upstream bounds
|
||||
this.emit('bounds', this.bounds());
|
||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
|
||||
}
|
||||
|
||||
hasOwnContext() {
|
||||
@@ -237,6 +408,9 @@ class IndependentTimeContext extends TimeContext {
|
||||
if (viewKey && key === viewKey) {
|
||||
//this is necessary as the upstream context gets reassigned after this
|
||||
this.stopFollowingTimeContext();
|
||||
if (this.activeClock !== undefined) {
|
||||
this.activeClock.off('tick', this.tick);
|
||||
}
|
||||
|
||||
let timeContext = this.globalTimeContext;
|
||||
|
||||
@@ -259,11 +433,16 @@ class IndependentTimeContext extends TimeContext {
|
||||
this.followTimeContext();
|
||||
|
||||
// Emit bounds so that views that are changing context get the upstream bounds
|
||||
this.emit('bounds', this.bounds());
|
||||
this.emit('bounds', this.getBounds());
|
||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
|
||||
// now that the view's context is set, tell others to check theirs in case they were following this view's context.
|
||||
this.globalTimeContext.emit('refreshContext', viewKey);
|
||||
}
|
||||
}
|
||||
|
||||
#copy(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
}
|
||||
|
||||
export default IndependentTimeContext;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import GlobalTimeContext from './GlobalTimeContext';
|
||||
import IndependentTimeContext from '@/api/time/IndependentTimeContext';
|
||||
import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '@/api/time/constants';
|
||||
|
||||
/**
|
||||
* The public API for setting and querying the temporal state of the
|
||||
@@ -134,14 +135,15 @@ class TimeAPI extends GlobalTimeContext {
|
||||
*/
|
||||
addIndependentContext(key, value, clockKey) {
|
||||
let timeContext = this.getIndependentContext(key);
|
||||
|
||||
//stop following upstream time context since the view has it's own
|
||||
timeContext.resetContext();
|
||||
|
||||
if (clockKey) {
|
||||
timeContext.clock(clockKey, value);
|
||||
timeContext.setClock(clockKey);
|
||||
timeContext.setMode(REALTIME_MODE_KEY, value);
|
||||
} else {
|
||||
timeContext.stopClock();
|
||||
timeContext.bounds(value);
|
||||
timeContext.setMode(FIXED_MODE_KEY, value);
|
||||
}
|
||||
|
||||
// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
|
||||
@@ -185,6 +187,7 @@ class TimeAPI extends GlobalTimeContext {
|
||||
}
|
||||
|
||||
let viewTimeContext = this.getIndependentContext(viewKey);
|
||||
|
||||
if (!viewTimeContext) {
|
||||
// If the context doesn't exist yet, create it.
|
||||
viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('The Time API', function () {
|
||||
expect(function () {
|
||||
api.timeSystem(timeSystem, bounds);
|
||||
}).not.toThrow();
|
||||
expect(api.timeSystem()).toBe(timeSystem);
|
||||
expect(api.timeSystem()).toEqual(timeSystem);
|
||||
});
|
||||
|
||||
it('Disallows setting of time system without bounds', function () {
|
||||
@@ -110,7 +110,7 @@ describe('The Time API', function () {
|
||||
expect(function () {
|
||||
api.timeSystem(timeSystemKey);
|
||||
}).not.toThrow();
|
||||
expect(api.timeSystem()).toBe(timeSystem);
|
||||
expect(api.timeSystem()).toEqual(timeSystem);
|
||||
});
|
||||
|
||||
it('Emits an event when time system changes', function () {
|
||||
@@ -202,12 +202,12 @@ describe('The Time API', function () {
|
||||
expect(mockTickSource.off).toHaveBeenCalledWith('tick', jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('Allows the active clock to be set and unset', function () {
|
||||
xit('Allows the active clock to be set and unset', function () {
|
||||
expect(api.clock()).toBeUndefined();
|
||||
api.clock('mts', mockOffsets);
|
||||
expect(api.clock()).toBeDefined();
|
||||
api.stopClock();
|
||||
expect(api.clock()).toBeUndefined();
|
||||
// api.stopClock();
|
||||
// expect(api.clock()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Provides a default time context', () => {
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export const TIME_CONTEXT_EVENTS = ['bounds', 'clock', 'timeSystem', 'clockOffsets'];
|
||||
import { TIME_CONTEXT_EVENTS, MODES, REALTIME_MODE_KEY, FIXED_MODE_KEY } from './constants';
|
||||
|
||||
class TimeContext extends EventEmitter {
|
||||
constructor() {
|
||||
@@ -42,6 +41,7 @@ class TimeContext extends EventEmitter {
|
||||
|
||||
this.activeClock = undefined;
|
||||
this.offsets = undefined;
|
||||
this.mode = undefined;
|
||||
|
||||
this.tick = this.tick.bind(this);
|
||||
}
|
||||
@@ -56,6 +56,8 @@ class TimeContext extends EventEmitter {
|
||||
* @method timeSystem
|
||||
*/
|
||||
timeSystem(timeSystemOrKey, bounds) {
|
||||
this.#warnMethodDeprecated('"timeSystem"', '"getTimeSystem" and "setTimeSystem"');
|
||||
|
||||
if (arguments.length >= 1) {
|
||||
if (arguments.length === 1 && !this.activeClock) {
|
||||
throw new Error('Must specify bounds when changing time system without an active clock.');
|
||||
@@ -91,7 +93,7 @@ class TimeContext extends EventEmitter {
|
||||
throw 'Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key';
|
||||
}
|
||||
|
||||
this.system = timeSystem;
|
||||
this.system = this.#copy(timeSystem);
|
||||
|
||||
/**
|
||||
* The time system used by the time
|
||||
@@ -102,7 +104,10 @@ class TimeContext extends EventEmitter {
|
||||
* @property {TimeSystem} The value of the currently applied
|
||||
* Time System
|
||||
* */
|
||||
this.emit('timeSystem', this.system);
|
||||
const system = this.#copy(this.system);
|
||||
this.emit('timeSystem', system);
|
||||
this.emit(TIME_CONTEXT_EVENTS.timeSystemChanged, system);
|
||||
|
||||
if (bounds) {
|
||||
this.bounds(bounds);
|
||||
}
|
||||
@@ -163,6 +168,8 @@ class TimeContext extends EventEmitter {
|
||||
* @method bounds
|
||||
*/
|
||||
bounds(newBounds) {
|
||||
this.#warnMethodDeprecated('"bounds"', '"getBounds" and "setBounds"');
|
||||
|
||||
if (arguments.length > 0) {
|
||||
const validationResult = this.validateBounds(newBounds);
|
||||
if (validationResult.valid !== true) {
|
||||
@@ -170,7 +177,7 @@ class TimeContext extends EventEmitter {
|
||||
}
|
||||
|
||||
//Create a copy to avoid direct mutation of conductor bounds
|
||||
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
|
||||
this.boundsVal = this.#copy(newBounds);
|
||||
/**
|
||||
* The start time, end time, or both have been updated.
|
||||
* @event bounds
|
||||
@@ -180,10 +187,11 @@ class TimeContext extends EventEmitter {
|
||||
* a "tick" event (ie. was an automatic update), false otherwise.
|
||||
*/
|
||||
this.emit('bounds', this.boundsVal, false);
|
||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, false);
|
||||
}
|
||||
|
||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
||||
return this.#copy(this.boundsVal);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,6 +256,8 @@ class TimeContext extends EventEmitter {
|
||||
* @returns {ClockOffsets}
|
||||
*/
|
||||
clockOffsets(offsets) {
|
||||
this.#warnMethodDeprecated('"clockOffsets"', '"getClockOffsets" and "setClockOffsets"');
|
||||
|
||||
if (arguments.length > 0) {
|
||||
const validationResult = this.validateOffsets(offsets);
|
||||
if (validationResult.valid !== true) {
|
||||
@@ -278,20 +288,19 @@ class TimeContext extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the currently active clock from ticking, and unset it. This will
|
||||
* Stop following the currently active clock. This will
|
||||
* revert all views to showing a static time frame defined by the current
|
||||
* bounds.
|
||||
*/
|
||||
stopClock() {
|
||||
if (this.activeClock) {
|
||||
this.clock(undefined, undefined);
|
||||
}
|
||||
this.#warnMethodDeprecated('"stopClock"');
|
||||
|
||||
this.setMode(FIXED_MODE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active clock. Tick source will be immediately subscribed to
|
||||
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
||||
* can be unset by calling {@link stopClock}.
|
||||
* and ticking will begin. Offsets from 'now' must also be provided.
|
||||
*
|
||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
||||
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
||||
@@ -301,6 +310,8 @@ class TimeContext extends EventEmitter {
|
||||
* @return {Clock} the currently active clock;
|
||||
*/
|
||||
clock(keyOrClock, offsets) {
|
||||
this.#warnMethodDeprecated('"clock"', '"getClock" and "setClock"');
|
||||
|
||||
if (arguments.length === 2) {
|
||||
let clock;
|
||||
|
||||
@@ -324,15 +335,19 @@ class TimeContext extends EventEmitter {
|
||||
this.activeClock = clock;
|
||||
|
||||
/**
|
||||
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
||||
* The active clock has changed.
|
||||
* @event clock
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Clock} clock The newly activated clock, or undefined
|
||||
* if the system is no longer following a clock source
|
||||
*/
|
||||
this.emit('clock', this.activeClock);
|
||||
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
||||
|
||||
if (this.activeClock !== undefined) {
|
||||
//set the mode or isRealtime will be false even though we're in clock mode
|
||||
this.setMode(REALTIME_MODE_KEY);
|
||||
|
||||
this.clockOffsets(offsets);
|
||||
this.activeClock.on('tick', this.tick);
|
||||
}
|
||||
@@ -340,7 +355,7 @@ class TimeContext extends EventEmitter {
|
||||
throw 'When setting the clock, clock offsets must also be provided';
|
||||
}
|
||||
|
||||
return this.activeClock;
|
||||
return this.isRealTime() ? this.activeClock : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,29 +364,304 @@ class TimeContext extends EventEmitter {
|
||||
* using current offsets.
|
||||
*/
|
||||
tick(timestamp) {
|
||||
if (!this.activeClock) {
|
||||
return;
|
||||
// always emit the timestamp
|
||||
this.emit('tick', timestamp);
|
||||
|
||||
if (this.mode === REALTIME_MODE_KEY) {
|
||||
const newBounds = {
|
||||
start: timestamp + this.offsets.start,
|
||||
end: timestamp + this.offsets.end
|
||||
};
|
||||
|
||||
this.boundsVal = newBounds;
|
||||
// "bounds" will be deprecated in a future release
|
||||
this.emit('bounds', this.boundsVal, true);
|
||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, true);
|
||||
}
|
||||
|
||||
const newBounds = {
|
||||
start: timestamp + this.offsets.start,
|
||||
end: timestamp + this.offsets.end
|
||||
};
|
||||
|
||||
this.boundsVal = newBounds;
|
||||
this.emit('bounds', this.boundsVal, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this time context is in real-time mode or not.
|
||||
* Get the timestamp of the current clock
|
||||
* @returns {number} current timestamp of current clock regardless of mode
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method now
|
||||
*/
|
||||
|
||||
now() {
|
||||
return this.activeClock.currentValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time system of the TimeAPI.
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method getTimeSystem
|
||||
*/
|
||||
getTimeSystem() {
|
||||
return this.system;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time system of the TimeAPI.
|
||||
* @param {TimeSystem | string} timeSystemOrKey
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||
* @fires module:openmct.TimeAPI~timeSystem
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method setTimeSystem
|
||||
*/
|
||||
setTimeSystem(timeSystemOrKey, bounds) {
|
||||
if (timeSystemOrKey === undefined) {
|
||||
throw 'Please provide a time system';
|
||||
}
|
||||
|
||||
let timeSystem;
|
||||
|
||||
if (typeof timeSystemOrKey === 'string') {
|
||||
timeSystem = this.timeSystems.get(timeSystemOrKey);
|
||||
|
||||
if (timeSystem === undefined) {
|
||||
throw `Unknown time system ${timeSystemOrKey}. Has it been registered with 'addTimeSystem'?`;
|
||||
}
|
||||
} else if (typeof timeSystemOrKey === 'object') {
|
||||
timeSystem = timeSystemOrKey;
|
||||
|
||||
if (!this.timeSystems.has(timeSystem.key)) {
|
||||
throw `Unknown time system ${timeSystemOrKey.key}. Has it been registered with 'addTimeSystem'?`;
|
||||
}
|
||||
} else {
|
||||
throw 'Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key';
|
||||
}
|
||||
|
||||
this.system = this.#copy(timeSystem);
|
||||
/**
|
||||
* The time system used by the time
|
||||
* conductor has changed. A change in Time System will always be
|
||||
* followed by a bounds event specifying new query bounds.
|
||||
*
|
||||
* @event module:openmct.TimeAPI~timeSystem
|
||||
* @property {TimeSystem} The value of the currently applied
|
||||
* Time System
|
||||
* */
|
||||
this.emit(TIME_CONTEXT_EVENTS.timeSystemChanged, this.#copy(this.system));
|
||||
this.emit('timeSystem', this.#copy(this.system));
|
||||
|
||||
if (bounds) {
|
||||
this.setBounds(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method bounds
|
||||
*/
|
||||
getBounds() {
|
||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||
return this.#copy(this.boundsVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
*
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||
* @throws {Error} Validation error
|
||||
* @fires module:openmct.TimeAPI~bounds
|
||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method bounds
|
||||
*/
|
||||
setBounds(newBounds) {
|
||||
const validationResult = this.validateBounds(newBounds);
|
||||
if (validationResult.valid !== true) {
|
||||
throw new Error(validationResult.message);
|
||||
}
|
||||
|
||||
//Create a copy to avoid direct mutation of conductor bounds
|
||||
this.boundsVal = this.#copy(newBounds);
|
||||
/**
|
||||
* The start time, end time, or both have been updated.
|
||||
* @event bounds
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {TimeConductorBounds} bounds The newly updated bounds
|
||||
* @property {boolean} [tick] `true` if the bounds update was due to
|
||||
* a "tick" event (i.e. was an automatic update), false otherwise.
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, false);
|
||||
this.emit('bounds', this.boundsVal, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active clock.
|
||||
* @return {Clock} the currently active clock;
|
||||
*/
|
||||
getClock() {
|
||||
return this.activeClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active clock. Tick source will be immediately subscribed to
|
||||
* and the currently ticking will begin.
|
||||
* Offsets from 'now', if provided, will be used to set realtime mode offsets
|
||||
*
|
||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Clock} the currently active clock;
|
||||
*/
|
||||
setClock(keyOrClock) {
|
||||
let clock;
|
||||
|
||||
if (typeof keyOrClock === 'string') {
|
||||
clock = this.clocks.get(keyOrClock);
|
||||
if (clock === undefined) {
|
||||
throw `Unknown clock ${keyOrClock}. Has it been registered with 'addClock'?`;
|
||||
}
|
||||
} else if (typeof keyOrClock === 'object') {
|
||||
clock = keyOrClock;
|
||||
if (!this.clocks.has(clock.key)) {
|
||||
throw `Unknown clock ${keyOrClock.key}. Has it been registered with 'addClock'?`;
|
||||
}
|
||||
}
|
||||
|
||||
const previousClock = this.activeClock;
|
||||
if (previousClock) {
|
||||
previousClock.off('tick', this.tick);
|
||||
}
|
||||
|
||||
this.activeClock = clock;
|
||||
this.activeClock.on('tick', this.tick);
|
||||
|
||||
/**
|
||||
* The active clock has changed.
|
||||
* @event clock
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Clock} clock The newly activated clock, or undefined
|
||||
* if the system is no longer following a clock source
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
||||
this.emit('clock', this.activeClock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current mode.
|
||||
* @return {Mode} the current mode;
|
||||
*/
|
||||
getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode to either fixed or realtime.
|
||||
*
|
||||
* @param {Mode} mode The mode to activate
|
||||
* @param {TimeBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Mode} the currently active mode;
|
||||
*/
|
||||
setMode(mode, offsetsOrBounds) {
|
||||
if (!mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === MODES.realtime && this.activeClock === undefined) {
|
||||
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
|
||||
}
|
||||
|
||||
if (mode !== this.mode) {
|
||||
this.mode = mode;
|
||||
/**
|
||||
* The active mode has changed.
|
||||
* @event modeChanged
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Mode} mode The newly activated mode
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode));
|
||||
}
|
||||
|
||||
if (offsetsOrBounds !== undefined) {
|
||||
if (this.isRealTime()) {
|
||||
this.setClockOffsets(offsetsOrBounds);
|
||||
} else {
|
||||
this.setBounds(offsetsOrBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this time context is in realtime mode or not.
|
||||
* @returns {boolean} true if this context is in real-time mode, false if not
|
||||
*/
|
||||
isRealTime() {
|
||||
if (this.clock()) {
|
||||
return true;
|
||||
return this.mode === MODES.realtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this time context is in fixed mode or not.
|
||||
* @returns {boolean} true if this context is in fixed mode, false if not
|
||||
*/
|
||||
isFixed() {
|
||||
return this.mode === MODES.fixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently applied clock offsets.
|
||||
* @returns {ClockOffsets}
|
||||
*/
|
||||
getClockOffsets() {
|
||||
return this.offsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the currently applied clock offsets. If no parameter is provided,
|
||||
* the current value will be returned. If provided, the new value will be
|
||||
* used as the new clock offsets.
|
||||
* @param {ClockOffsets} offsets
|
||||
* @returns {ClockOffsets}
|
||||
*/
|
||||
setClockOffsets(offsets) {
|
||||
const validationResult = this.validateOffsets(offsets);
|
||||
if (validationResult.valid !== true) {
|
||||
throw new Error(validationResult.message);
|
||||
}
|
||||
|
||||
return false;
|
||||
this.offsets = this.#copy(offsets);
|
||||
|
||||
const currentValue = this.activeClock.currentValue();
|
||||
const newBounds = {
|
||||
start: currentValue + offsets.start,
|
||||
end: currentValue + offsets.end
|
||||
};
|
||||
|
||||
this.setBounds(newBounds);
|
||||
|
||||
/**
|
||||
* Event that is triggered when clock offsets change.
|
||||
* @event clockOffsets
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {ClockOffsets} clockOffsets The newly activated clock
|
||||
* offsets.
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.#copy(offsets));
|
||||
}
|
||||
|
||||
#warnMethodDeprecated(method, newMethod) {
|
||||
let message = `[DEPRECATION WARNING]: The ${method} API method is deprecated and will be removed in a future version of Open MCT.`;
|
||||
|
||||
if (newMethod) {
|
||||
message += ` Please use the ${newMethod} API method(s) instead.`;
|
||||
}
|
||||
|
||||
// TODO: add docs and point to them in warning.
|
||||
// For more information and migration instructions, visit [link to documentation or migration guide].
|
||||
|
||||
console.warn(message);
|
||||
}
|
||||
|
||||
#copy(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
src/api/time/constants.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export const TIME_CONTEXT_EVENTS = {
|
||||
//old API events - to be deprecated
|
||||
bounds: 'bounds',
|
||||
clock: 'clock',
|
||||
timeSystem: 'timeSystem',
|
||||
clockOffsets: 'clockOffsets',
|
||||
//new API events
|
||||
tick: 'tick',
|
||||
modeChanged: 'modeChanged',
|
||||
boundsChanged: 'boundsChanged',
|
||||
clockChanged: 'clockChanged',
|
||||
timeSystemChanged: 'timeSystemChanged',
|
||||
clockOffsetsChanged: 'clockOffsetsChanged'
|
||||
};
|
||||
|
||||
export const REALTIME_MODE_KEY = 'realtime';
|
||||
export const FIXED_MODE_KEY = 'fixed';
|
||||
|
||||
export const MODES = {
|
||||
[FIXED_MODE_KEY]: FIXED_MODE_KEY,
|
||||
[REALTIME_MODE_KEY]: REALTIME_MODE_KEY
|
||||
};
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
import TooltipComponent from './components/TooltipComponent.vue';
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
class Tooltip extends EventEmitter {
|
||||
constructor(
|
||||
@@ -34,9 +34,7 @@ class Tooltip extends EventEmitter {
|
||||
) {
|
||||
super();
|
||||
|
||||
this.container = document.createElement('div');
|
||||
|
||||
this.component = new Vue({
|
||||
const { vNode, destroy } = mount({
|
||||
components: {
|
||||
TooltipComponent: TooltipComponent
|
||||
},
|
||||
@@ -48,6 +46,9 @@ class Tooltip extends EventEmitter {
|
||||
template: '<tooltip-component toolTipText="toolTipText"></tooltip-component>'
|
||||
});
|
||||
|
||||
this.component = vNode.componentInstance;
|
||||
this._destroy = destroy;
|
||||
|
||||
this.isActive = null;
|
||||
}
|
||||
|
||||
@@ -55,8 +56,7 @@ class Tooltip extends EventEmitter {
|
||||
if (!this.isActive) {
|
||||
return;
|
||||
}
|
||||
document.body.removeChild(this.container);
|
||||
this.component.$destroy();
|
||||
this._destroy();
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
@@ -64,8 +64,7 @@ class Tooltip extends EventEmitter {
|
||||
* @private
|
||||
**/
|
||||
show() {
|
||||
document.body.appendChild(this.container);
|
||||
this.container.appendChild(this.component.$mount().$el);
|
||||
document.body.appendChild(this.component.$el);
|
||||
this.isActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
.c-tooltip-wrapper {
|
||||
max-width: 200px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('The User Status API', () => {
|
||||
'setPollQuestion',
|
||||
'getPollQuestion',
|
||||
'getCurrentUser',
|
||||
'getPossibleRoles',
|
||||
'getPossibleStatuses',
|
||||
'getAllStatusRoles',
|
||||
'canSetPollQuestion',
|
||||
@@ -42,6 +43,7 @@ describe('The User Status API', () => {
|
||||
mockUser = new openmct.user.User('test-user', 'A test user');
|
||||
userProvider.getCurrentUser.and.returnValue(Promise.resolve(mockUser));
|
||||
userProvider.getPossibleStatuses.and.returnValue(Promise.resolve([]));
|
||||
userProvider.getPossibleRoles.and.returnValue(Promise.resolve([]));
|
||||
userProvider.getAllStatusRoles.and.returnValue(Promise.resolve([]));
|
||||
userProvider.canSetPollQuestion.and.returnValue(Promise.resolve(false));
|
||||
userProvider.isLoggedIn.and.returnValue(true);
|
||||
|
||||
@@ -21,13 +21,16 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
import { markRaw } from 'vue';
|
||||
export default class LADTableConfiguration extends EventEmitter {
|
||||
constructor(domainObject, openmct) {
|
||||
super();
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
|
||||
// Prevent Vue from making this a Proxy, otherwise
|
||||
// it cannot access any private methods (like #mutate()).
|
||||
this.openmct = markRaw(openmct);
|
||||
|
||||
this.objectMutated = this.objectMutated.bind(this);
|
||||
this.unlistenFromMutation = openmct.objects.observe(
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import LADTableConfigurationComponent from './components/LADTableConfiguration.vue';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
export default function LADTableConfigurationViewProvider(openmct) {
|
||||
return {
|
||||
@@ -37,28 +37,34 @@ export default function LADTableConfigurationViewProvider(openmct) {
|
||||
return object?.type === 'LadTable' || object?.type === 'LadTableSet';
|
||||
},
|
||||
view(selection) {
|
||||
let component;
|
||||
let _destroy = null;
|
||||
|
||||
return {
|
||||
show(element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
LADTableConfiguration: LADTableConfigurationComponent
|
||||
const { destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
LADTableConfiguration: LADTableConfigurationComponent
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
template: '<LADTableConfiguration />'
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
template: '<LADTableConfiguration />'
|
||||
});
|
||||
{
|
||||
app: openmct.app,
|
||||
element
|
||||
}
|
||||
);
|
||||
_destroy = destroy;
|
||||
},
|
||||
priority() {
|
||||
return 1;
|
||||
},
|
||||
destroy() {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
if (_destroy) {
|
||||
_destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,38 +22,47 @@
|
||||
|
||||
import LadTable from './components/LADTable.vue';
|
||||
import LADTableConfiguration from './LADTableConfiguration';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
export default class LADTableView {
|
||||
constructor(openmct, domainObject, objectPath) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = objectPath;
|
||||
this.component = undefined;
|
||||
this.component = null;
|
||||
this._destroy = null;
|
||||
}
|
||||
|
||||
show(element) {
|
||||
let ladTableConfiguration = new LADTableConfiguration(this.domainObject, this.openmct);
|
||||
|
||||
this.component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
LadTable
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
LadTable
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
currentView: this,
|
||||
ladTableConfiguration
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
domainObject: this.domainObject,
|
||||
objectPath: this.objectPath
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<lad-table ref="ladTable" :domain-object="domainObject" :object-path="objectPath"></lad-table>'
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
currentView: this,
|
||||
ladTableConfiguration
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
domainObject: this.domainObject,
|
||||
objectPath: this.objectPath
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<lad-table ref="ladTable" :domain-object="domainObject" :object-path="objectPath"></lad-table>'
|
||||
});
|
||||
{
|
||||
app: this.openmct.app,
|
||||
element
|
||||
}
|
||||
);
|
||||
this.component = vNode.componentInstance;
|
||||
this._destroy = destroy;
|
||||
}
|
||||
|
||||
getViewContext() {
|
||||
@@ -64,8 +73,9 @@ export default class LADTableView {
|
||||
return this.component.$refs.ladTable.getViewContext();
|
||||
}
|
||||
|
||||
destroy(element) {
|
||||
this.component.$destroy();
|
||||
this.component = undefined;
|
||||
destroy() {
|
||||
if (this._destroy) {
|
||||
this._destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,37 +22,46 @@
|
||||
|
||||
import LadTableSet from './components/LadTableSet.vue';
|
||||
import LADTableConfiguration from './LADTableConfiguration';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
export default class LadTableSetView {
|
||||
constructor(openmct, domainObject, objectPath) {
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
this.objectPath = objectPath;
|
||||
this.component = undefined;
|
||||
this._destroy = null;
|
||||
this.component = null;
|
||||
}
|
||||
|
||||
show(element) {
|
||||
let ladTableConfiguration = new LADTableConfiguration(this.domainObject, this.openmct);
|
||||
|
||||
this.component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
LadTableSet
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
LadTableSet
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
objectPath: this.objectPath,
|
||||
currentView: this,
|
||||
ladTableConfiguration
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
domainObject: this.domainObject
|
||||
};
|
||||
},
|
||||
template: '<lad-table-set ref="ladTableSet" :domain-object="domainObject"></lad-table-set>'
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
objectPath: this.objectPath,
|
||||
currentView: this,
|
||||
ladTableConfiguration
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
domainObject: this.domainObject
|
||||
};
|
||||
},
|
||||
template: '<lad-table-set ref="ladTableSet" :domain-object="domainObject"></lad-table-set>'
|
||||
});
|
||||
{
|
||||
app: this.openmct.app,
|
||||
element
|
||||
}
|
||||
);
|
||||
this._destroy = destroy;
|
||||
this.component = vNode.componentInstance;
|
||||
}
|
||||
|
||||
getViewContext() {
|
||||
@@ -63,8 +72,9 @@ export default class LadTableSetView {
|
||||
return this.component.$refs.ladTableSet.getViewContext();
|
||||
}
|
||||
|
||||
destroy(element) {
|
||||
this.component.$destroy();
|
||||
this.component = undefined;
|
||||
destroy() {
|
||||
if (this._destroy) {
|
||||
this._destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ export default {
|
||||
this.previewAction = new PreviewAction(this.openmct);
|
||||
this.previewAction.on('isVisible', this.togglePreviewState);
|
||||
},
|
||||
destroyed() {
|
||||
unmounted() {
|
||||
this.openmct.time.off('timeSystem', this.updateTimeSystem);
|
||||
this.telemetryCollection.off('add', this.setLatestValues);
|
||||
this.telemetryCollection.off('clear', this.resetValues);
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
import Vue, { toRaw } from 'vue';
|
||||
import LadRow from './LADRow.vue';
|
||||
import StalenessUtils from '@/utils/staleness';
|
||||
|
||||
@@ -139,7 +139,7 @@ export default {
|
||||
);
|
||||
this.initializeViewActions();
|
||||
},
|
||||
destroyed() {
|
||||
unmounted() {
|
||||
this.ladTableConfiguration.off('change', this.handleConfigurationChange);
|
||||
|
||||
this.composition.off('add', this.addItem);
|
||||
@@ -191,7 +191,7 @@ export default {
|
||||
reorder(reorderPlan) {
|
||||
const oldItems = this.items.slice();
|
||||
reorderPlan.forEach((reorderEvent) => {
|
||||
this.$set(this.items, reorderEvent.newIndex, oldItems[reorderEvent.oldIndex]);
|
||||
this.items[reorderEvent.newIndex] = oldItems[reorderEvent.oldIndex];
|
||||
});
|
||||
},
|
||||
metadataHasUnits(valueMetadatas) {
|
||||
@@ -230,7 +230,7 @@ export default {
|
||||
};
|
||||
},
|
||||
toggleFixedLayout() {
|
||||
const config = structuredClone(this.configuration);
|
||||
const config = structuredClone(toRaw(this.configuration));
|
||||
|
||||
config.isFixedLayout = !this.configuration.isFixedLayout;
|
||||
this.ladTableConfiguration.updateConfiguration(config);
|
||||
|
||||
@@ -87,7 +87,7 @@ export default {
|
||||
|
||||
this.composition.load();
|
||||
},
|
||||
destroyed() {
|
||||
unmounted() {
|
||||
this.ladTableConfiguration.destroy();
|
||||
this.openmct.editor.off('isEditing', this.toggleEdit);
|
||||
|
||||
@@ -126,7 +126,7 @@ export default {
|
||||
ladTable.domainObject = domainObject;
|
||||
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
|
||||
this.$set(this.ladTelemetryObjects, ladTable.key, []);
|
||||
this.ladTelemetryObjects[ladTable.key] = [];
|
||||
this.ladTableObjects.push(ladTable);
|
||||
|
||||
const composition = this.openmct.composition.get(ladTable.domainObject);
|
||||
@@ -151,7 +151,7 @@ export default {
|
||||
);
|
||||
const ladTable = this.ladTableObjects[index];
|
||||
|
||||
this.$delete(this.ladTelemetryObjects, ladTable.key);
|
||||
delete this.ladTelemetryObjects[ladTable.key];
|
||||
this.ladTableObjects.splice(index, 1);
|
||||
|
||||
this.shouldShowUnitsCheckbox();
|
||||
@@ -165,7 +165,7 @@ export default {
|
||||
const telemetryObjects = this.ladTelemetryObjects[ladTable.key];
|
||||
telemetryObjects.push(telemetryObject);
|
||||
|
||||
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
|
||||
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
|
||||
|
||||
this.shouldShowUnitsCheckbox();
|
||||
};
|
||||
@@ -179,7 +179,7 @@ export default {
|
||||
);
|
||||
|
||||
telemetryObjects.splice(index, 1);
|
||||
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
|
||||
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
|
||||
|
||||
this.shouldShowUnitsCheckbox();
|
||||
};
|
||||
@@ -220,11 +220,11 @@ export default {
|
||||
}
|
||||
|
||||
if (showUnitsCheckbox && this.headers.units === undefined) {
|
||||
this.$set(this.headers, 'units', 'Units');
|
||||
this.headers.units = 'Units';
|
||||
}
|
||||
|
||||
if (!showUnitsCheckbox && this.headers?.units) {
|
||||
this.$delete(this.headers, 'units');
|
||||
delete this.headers.units;
|
||||
}
|
||||
},
|
||||
metadataHasUnits(domainObject) {
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="ladTable in ladTableObjects">
|
||||
<tr :key="ladTable.key" class="c-table__group-header js-lad-table-set__table-headers">
|
||||
<template v-for="ladTable in ladTableObjects" :key="ladTable.key">
|
||||
<tr class="c-table__group-header js-lad-table-set__table-headers">
|
||||
<td colspan="10">
|
||||
{{ ladTable.domainObject.name }}
|
||||
</td>
|
||||
@@ -74,7 +74,6 @@ export default {
|
||||
return {
|
||||
ladTableObjects: [],
|
||||
ladTelemetryObjects: {},
|
||||
compositions: [],
|
||||
viewContext: {},
|
||||
staleObjects: [],
|
||||
configuration: this.ladTableConfiguration.getConfiguration()
|
||||
@@ -115,6 +114,9 @@ export default {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.compositions = [];
|
||||
},
|
||||
mounted() {
|
||||
this.ladTableConfiguration.on('change', this.handleConfigurationChange);
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
@@ -125,7 +127,7 @@ export default {
|
||||
|
||||
this.stalenessSubscription = {};
|
||||
},
|
||||
destroyed() {
|
||||
unmounted() {
|
||||
this.ladTableConfiguration.off('change', this.handleConfigurationChange);
|
||||
this.composition.off('add', this.addLadTable);
|
||||
this.composition.off('remove', this.removeLadTable);
|
||||
@@ -147,7 +149,7 @@ export default {
|
||||
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
ladTable.objectPath = [domainObject, ...this.objectPath];
|
||||
|
||||
this.$set(this.ladTelemetryObjects, ladTable.key, []);
|
||||
this.ladTelemetryObjects[ladTable.key] = [];
|
||||
this.ladTableObjects.push(ladTable);
|
||||
|
||||
let composition = this.openmct.composition.get(ladTable.domainObject);
|
||||
@@ -178,7 +180,7 @@ export default {
|
||||
this.unwatchStaleness(combinedKey);
|
||||
});
|
||||
|
||||
this.$delete(this.ladTelemetryObjects, ladTable.key);
|
||||
delete this.ladTelemetryObjects[ladTable.key];
|
||||
this.ladTableObjects.splice(index, 1);
|
||||
},
|
||||
reorderLadTables(reorderPlan) {
|
||||
@@ -201,7 +203,7 @@ export default {
|
||||
const telemetryObjects = this.ladTelemetryObjects[ladTable.key];
|
||||
telemetryObjects.push(telemetryObject);
|
||||
|
||||
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
|
||||
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
|
||||
|
||||
this.stalenessSubscription[combinedKey] = {};
|
||||
this.stalenessSubscription[combinedKey].stalenessUtils = new StalenessUtils(
|
||||
@@ -236,7 +238,7 @@ export default {
|
||||
this.unwatchStaleness(combinedKey);
|
||||
|
||||
telemetryObjects.splice(index, 1);
|
||||
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
|
||||
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
|
||||
};
|
||||
},
|
||||
unwatchStaleness(combinedKey) {
|
||||
|
||||
@@ -75,6 +75,7 @@ describe('The LAD Table', () => {
|
||||
child = document.createElement('div');
|
||||
parent.appendChild(child);
|
||||
|
||||
openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(false);
|
||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||
|
||||
ladPlugin = new LadPlugin();
|
||||
|
||||
@@ -20,14 +20,20 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
||||
import { FIXED_MODE_KEY, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from '../../api/time/constants';
|
||||
|
||||
const SEARCH_MODE = 'tc.mode';
|
||||
const SEARCH_TIME_SYSTEM = 'tc.timeSystem';
|
||||
const SEARCH_START_BOUND = 'tc.startBound';
|
||||
const SEARCH_END_BOUND = 'tc.endBound';
|
||||
const SEARCH_START_DELTA = 'tc.startDelta';
|
||||
const SEARCH_END_DELTA = 'tc.endDelta';
|
||||
const MODE_FIXED = 'fixed';
|
||||
const TIME_EVENTS = [
|
||||
TIME_CONTEXT_EVENTS.timeSystemChanged,
|
||||
TIME_CONTEXT_EVENTS.modeChanged,
|
||||
TIME_CONTEXT_EVENTS.clockChanged,
|
||||
TIME_CONTEXT_EVENTS.clockOffsetsChanged
|
||||
];
|
||||
|
||||
export default class URLTimeSettingsSynchronizer {
|
||||
constructor(openmct) {
|
||||
@@ -67,7 +73,7 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
updateTimeSettings() {
|
||||
let timeParameters = this.parseParametersFromUrl();
|
||||
const timeParameters = this.parseParametersFromUrl();
|
||||
|
||||
if (this.areTimeParametersValid(timeParameters)) {
|
||||
this.setTimeApiFromUrl(timeParameters);
|
||||
@@ -78,21 +84,18 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
parseParametersFromUrl() {
|
||||
let searchParams = this.openmct.router.getAllSearchParams();
|
||||
|
||||
let mode = searchParams.get(SEARCH_MODE);
|
||||
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
||||
|
||||
let startBound = parseInt(searchParams.get(SEARCH_START_BOUND), 10);
|
||||
let endBound = parseInt(searchParams.get(SEARCH_END_BOUND), 10);
|
||||
let bounds = {
|
||||
const searchParams = this.openmct.router.getAllSearchParams();
|
||||
const mode = searchParams.get(SEARCH_MODE);
|
||||
const timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
||||
const startBound = parseInt(searchParams.get(SEARCH_START_BOUND), 10);
|
||||
const endBound = parseInt(searchParams.get(SEARCH_END_BOUND), 10);
|
||||
const bounds = {
|
||||
start: startBound,
|
||||
end: endBound
|
||||
};
|
||||
|
||||
let startOffset = parseInt(searchParams.get(SEARCH_START_DELTA), 10);
|
||||
let endOffset = parseInt(searchParams.get(SEARCH_END_DELTA), 10);
|
||||
let clockOffsets = {
|
||||
const startOffset = parseInt(searchParams.get(SEARCH_START_DELTA), 10);
|
||||
const endOffset = parseInt(searchParams.get(SEARCH_END_DELTA), 10);
|
||||
const clockOffsets = {
|
||||
start: 0 - startOffset,
|
||||
end: endOffset
|
||||
};
|
||||
@@ -106,30 +109,35 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
setTimeApiFromUrl(timeParameters) {
|
||||
if (timeParameters.mode === 'fixed') {
|
||||
if (this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
|
||||
this.openmct.time.timeSystem(timeParameters.timeSystem, timeParameters.bounds);
|
||||
} else if (!this.areStartAndEndEqual(this.openmct.time.bounds(), timeParameters.bounds)) {
|
||||
this.openmct.time.bounds(timeParameters.bounds);
|
||||
}
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
|
||||
if (this.openmct.time.clock()) {
|
||||
this.openmct.time.stopClock();
|
||||
if (timeParameters.mode === FIXED_MODE_KEY) {
|
||||
// should update timesystem
|
||||
if (timeSystem.key !== timeParameters.timeSystem) {
|
||||
this.openmct.time.setTimeSystem(timeParameters.timeSystem, timeParameters.bounds);
|
||||
}
|
||||
if (!this.areStartAndEndEqual(this.openmct.time.getBounds(), timeParameters.bounds)) {
|
||||
this.openmct.time.setMode(FIXED_MODE_KEY, timeParameters.bounds);
|
||||
} else {
|
||||
this.openmct.time.setMode(FIXED_MODE_KEY);
|
||||
}
|
||||
} else {
|
||||
if (!this.openmct.time.clock() || this.openmct.time.clock().key !== timeParameters.mode) {
|
||||
this.openmct.time.clock(timeParameters.mode, timeParameters.clockOffsets);
|
||||
} else if (
|
||||
!this.areStartAndEndEqual(this.openmct.time.clockOffsets(), timeParameters.clockOffsets)
|
||||
) {
|
||||
this.openmct.time.clockOffsets(timeParameters.clockOffsets);
|
||||
const clock = this.openmct.time.getClock();
|
||||
|
||||
if (clock?.key !== timeParameters.mode) {
|
||||
this.openmct.time.setClock(timeParameters.mode);
|
||||
}
|
||||
|
||||
if (
|
||||
!this.openmct.time.timeSystem() ||
|
||||
this.openmct.time.timeSystem().key !== timeParameters.timeSystem
|
||||
!this.areStartAndEndEqual(this.openmct.time.getClockOffsets(), timeParameters.clockOffsets)
|
||||
) {
|
||||
this.openmct.time.timeSystem(timeParameters.timeSystem);
|
||||
this.openmct.time.setMode(REALTIME_MODE_KEY, timeParameters.clockOffsets);
|
||||
} else {
|
||||
this.openmct.time.setMode(REALTIME_MODE_KEY);
|
||||
}
|
||||
|
||||
if (timeSystem?.key !== timeParameters.timeSystem) {
|
||||
this.openmct.time.setTimeSystem(timeParameters.timeSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,13 +149,14 @@ export default class URLTimeSettingsSynchronizer {
|
||||
}
|
||||
|
||||
setUrlFromTimeApi() {
|
||||
let searchParams = this.openmct.router.getAllSearchParams();
|
||||
let clock = this.openmct.time.clock();
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let clockOffsets = this.openmct.time.clockOffsets();
|
||||
const searchParams = this.openmct.router.getAllSearchParams();
|
||||
const clock = this.openmct.time.getClock();
|
||||
const mode = this.openmct.time.getMode();
|
||||
const bounds = this.openmct.time.getBounds();
|
||||
const clockOffsets = this.openmct.time.getClockOffsets();
|
||||
|
||||
if (clock === undefined) {
|
||||
searchParams.set(SEARCH_MODE, MODE_FIXED);
|
||||
if (mode === FIXED_MODE_KEY) {
|
||||
searchParams.set(SEARCH_MODE, FIXED_MODE_KEY);
|
||||
searchParams.set(SEARCH_START_BOUND, bounds.start);
|
||||
searchParams.set(SEARCH_END_BOUND, bounds.end);
|
||||
|
||||
@@ -168,8 +177,8 @@ export default class URLTimeSettingsSynchronizer {
|
||||
searchParams.delete(SEARCH_END_BOUND);
|
||||
}
|
||||
|
||||
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
||||
this.openmct.router.setAllSearchParams(searchParams);
|
||||
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.getTimeSystem().key);
|
||||
this.openmct.router.updateParams(searchParams);
|
||||
}
|
||||
|
||||
areTimeParametersValid(timeParameters) {
|
||||
@@ -179,7 +188,7 @@ export default class URLTimeSettingsSynchronizer {
|
||||
this.isModeValid(timeParameters.mode) &&
|
||||
this.isTimeSystemValid(timeParameters.timeSystem)
|
||||
) {
|
||||
if (timeParameters.mode === 'fixed') {
|
||||
if (timeParameters.mode === FIXED_MODE_KEY) {
|
||||
isValid = this.areStartAndEndValid(timeParameters.bounds);
|
||||
} else {
|
||||
isValid = this.areStartAndEndValid(timeParameters.clockOffsets);
|
||||
@@ -203,8 +212,9 @@ export default class URLTimeSettingsSynchronizer {
|
||||
|
||||
isTimeSystemValid(timeSystem) {
|
||||
let isValid = timeSystem !== undefined;
|
||||
|
||||
if (isValid) {
|
||||
let timeSystemObject = this.openmct.time.timeSystems.get(timeSystem);
|
||||
const timeSystemObject = this.openmct.time.timeSystems.get(timeSystem);
|
||||
isValid = timeSystemObject !== undefined;
|
||||
}
|
||||
|
||||
@@ -218,18 +228,17 @@ export default class URLTimeSettingsSynchronizer {
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
if (mode.toLowerCase() === MODE_FIXED) {
|
||||
isValid = true;
|
||||
} else {
|
||||
isValid = this.openmct.time.clocks.get(mode) !== undefined;
|
||||
}
|
||||
if (
|
||||
isValid &&
|
||||
(mode.toLowerCase() === FIXED_MODE_KEY || this.openmct.time.clocks.get(mode) !== undefined)
|
||||
) {
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
areStartAndEndEqual(firstBounds, secondBounds) {
|
||||
return firstBounds.start === secondBounds.start && firstBounds.end === secondBounds.end;
|
||||
return firstBounds?.start === secondBounds.start && firstBounds?.end === secondBounds.end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
|
||||
describe('The URLTimeSettingsSynchronizer', () => {
|
||||
xdescribe('The URLTimeSettingsSynchronizer', () => {
|
||||
let appHolder;
|
||||
let openmct;
|
||||
let resolveFunction;
|
||||
@@ -40,7 +40,6 @@ describe('The URLTimeSettingsSynchronizer', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.time.stopClock();
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
|
||||
appHolder = undefined;
|
||||
|
||||
@@ -171,7 +171,7 @@ xdescribe('AutoflowTabularPlugin', () => {
|
||||
return [{ hint: hints[0] }];
|
||||
});
|
||||
|
||||
view = provider.view(testObject);
|
||||
view = provider.view(testObject, [testObject]);
|
||||
view.show(testContainer);
|
||||
|
||||
return Vue.nextTick();
|
||||
|
||||
@@ -84,7 +84,7 @@ define([
|
||||
rowCount: 'reflow'
|
||||
},
|
||||
template: autoflowTemplate,
|
||||
destroyed: function () {
|
||||
unmounted: function () {
|
||||
controller.destroy();
|
||||
|
||||
if (interval) {
|
||||
@@ -109,7 +109,7 @@ define([
|
||||
});
|
||||
}
|
||||
|
||||
AutoflowTabularView.prototype = Object.create(VueView.prototype);
|
||||
AutoflowTabularView.prototype = Object.create(VueView.default.prototype);
|
||||
|
||||
return AutoflowTabularView;
|
||||
});
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['vue'], function (Vue) {
|
||||
function VueView(options) {
|
||||
const vm = new Vue(options);
|
||||
import mount from 'utils/mount';
|
||||
export default function () {
|
||||
return function VueView(options) {
|
||||
const { vNode, destroy } = mount(options);
|
||||
|
||||
this.show = function (container) {
|
||||
container.appendChild(vm.$mount().$el);
|
||||
container.appendChild(vNode.el);
|
||||
};
|
||||
|
||||
this.destroy = vm.$destroy.bind(vm);
|
||||
}
|
||||
|
||||
return VueView;
|
||||
});
|
||||
this.destroy = destroy;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
});
|
||||
this.registerListeners();
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
if (this.plotResizeObserver) {
|
||||
this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
|
||||
clearTimeout(this.resizeTimer);
|
||||
|
||||
@@ -83,7 +83,7 @@ export default {
|
||||
this.refreshData
|
||||
);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
this.removeAllSubscriptions();
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
import BarGraphView from './BarGraphView.vue';
|
||||
import { BAR_GRAPH_KEY, BAR_GRAPH_VIEW } from './BarGraphConstants';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
export default function BarGraphViewProvider(openmct) {
|
||||
function isCompactView(objectPath) {
|
||||
@@ -44,34 +44,43 @@ export default function BarGraphViewProvider(openmct) {
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
let _destroy = null;
|
||||
let component = null;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
let isCompact = isCompactView(objectPath);
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
BarGraphView
|
||||
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
BarGraphView
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
compact: isCompact
|
||||
}
|
||||
};
|
||||
},
|
||||
template: '<bar-graph-view ref="graphComponent" :options="options"></bar-graph-view>'
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
compact: isCompact
|
||||
}
|
||||
};
|
||||
},
|
||||
template: '<bar-graph-view ref="graphComponent" :options="options"></bar-graph-view>'
|
||||
});
|
||||
{
|
||||
app: openmct.app,
|
||||
element
|
||||
}
|
||||
);
|
||||
_destroy = destroy;
|
||||
component = vNode.componentInstance;
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
_destroy();
|
||||
},
|
||||
onClearData() {
|
||||
component.$refs.graphComponent.refreshData();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BAR_GRAPH_INSPECTOR_KEY, BAR_GRAPH_KEY } from '../BarGraphConstants';
|
||||
import Vue from 'vue';
|
||||
import BarGraphOptions from './BarGraphOptions.vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
export default function BarGraphInspectorViewProvider(openmct) {
|
||||
return {
|
||||
@@ -16,29 +16,35 @@ export default function BarGraphInspectorViewProvider(openmct) {
|
||||
return object && object.type === BAR_GRAPH_KEY;
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
let _destroy = null;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
BarGraphOptions
|
||||
const { destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
BarGraphOptions
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: selection[0][0].context.item
|
||||
},
|
||||
template: '<bar-graph-options></bar-graph-options>'
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: selection[0][0].context.item
|
||||
},
|
||||
template: '<bar-graph-options></bar-graph-options>'
|
||||
});
|
||||
{
|
||||
app: openmct.app,
|
||||
element
|
||||
}
|
||||
);
|
||||
_destroy = destroy;
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
if (_destroy) {
|
||||
_destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -167,7 +167,7 @@ export default {
|
||||
this.registerListeners();
|
||||
this.composition.load();
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
this.stopListening();
|
||||
},
|
||||
@@ -192,7 +192,7 @@ export default {
|
||||
}
|
||||
},
|
||||
addSeries(series, index) {
|
||||
this.$set(this.plotSeries, this.plotSeries.length, series);
|
||||
this.plotSeries.push(series);
|
||||
this.setupOptions();
|
||||
},
|
||||
removeSeries(seriesIdentifier) {
|
||||
@@ -200,7 +200,7 @@ export default {
|
||||
this.openmct.objects.areIdsEqual(seriesIdentifier, plotSeries.identifier)
|
||||
);
|
||||
if (index >= 0) {
|
||||
this.$delete(this.plotSeries, index);
|
||||
this.plotSeries.splice(index, 1);
|
||||
this.setupOptions();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -115,7 +115,7 @@ export default {
|
||||
this.initColorAndName
|
||||
);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
if (this.removeBarStylesListener) {
|
||||
this.removeBarStylesListener();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
import Vue from 'vue';
|
||||
import BarGraphPlugin from './plugin';
|
||||
import BarGraph from './BarGraphPlot.vue';
|
||||
// import BarGraph from './BarGraphPlot.vue';
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import { BAR_GRAPH_VIEW, BAR_GRAPH_KEY } from './BarGraphConstants';
|
||||
|
||||
@@ -125,7 +125,6 @@ describe('the plugin', function () {
|
||||
describe('The bar graph view', () => {
|
||||
let barGraphObject;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let component;
|
||||
let mockComposition;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -153,21 +152,6 @@ describe('the plugin', function () {
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
let viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
BarGraph
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: barGraphObject,
|
||||
composition: openmct.composition.get(barGraphObject)
|
||||
},
|
||||
template: '<BarGraph></BarGraph>'
|
||||
});
|
||||
|
||||
await Vue.nextTick();
|
||||
});
|
||||
|
||||
@@ -179,7 +163,7 @@ describe('the plugin', function () {
|
||||
expect(plotViewProvider).toBeDefined();
|
||||
});
|
||||
|
||||
it('Renders plotly bar graph', () => {
|
||||
xit('Renders plotly bar graph', () => {
|
||||
let barChartElement = element.querySelectorAll('.plotly');
|
||||
expect(barChartElement.length).toBe(1);
|
||||
});
|
||||
@@ -236,10 +220,9 @@ describe('the plugin', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('The spectral plot view for telemetry objects with array values', () => {
|
||||
xdescribe('The spectral plot view for telemetry objects with array values', () => {
|
||||
let barGraphObject;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let component;
|
||||
let mockComposition;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -270,21 +253,6 @@ describe('the plugin', function () {
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
let viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
BarGraph
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: barGraphObject,
|
||||
composition: openmct.composition.get(barGraphObject)
|
||||
},
|
||||
template: '<BarGraph></BarGraph>'
|
||||
});
|
||||
|
||||
await Vue.nextTick();
|
||||
});
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ export default {
|
||||
this.reloadTelemetry
|
||||
);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
if (!this.composition) {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
import ScatterPlotView from './ScatterPlotView.vue';
|
||||
import { SCATTER_PLOT_KEY, SCATTER_PLOT_VIEW, TIME_STRIP_KEY } from './scatterPlotConstants.js';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
export default function ScatterPlotViewProvider(openmct) {
|
||||
function isCompactView(objectPath) {
|
||||
@@ -44,34 +44,42 @@ export default function ScatterPlotViewProvider(openmct) {
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
||||
let component;
|
||||
let _destroy = null;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
let isCompact = isCompactView(objectPath);
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
ScatterPlotView
|
||||
const isCompact = isCompactView(objectPath);
|
||||
const { destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
ScatterPlotView
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
compact: isCompact
|
||||
}
|
||||
};
|
||||
},
|
||||
template: '<scatter-plot-view :options="options"></scatter-plot-view>'
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
compact: isCompact
|
||||
}
|
||||
};
|
||||
},
|
||||
template: '<scatter-plot-view :options="options"></scatter-plot-view>'
|
||||
});
|
||||
{
|
||||
app: openmct.app,
|
||||
element
|
||||
}
|
||||
);
|
||||
_destroy = destroy;
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
if (_destroy) {
|
||||
_destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -87,7 +87,10 @@ export default {
|
||||
watch: {
|
||||
data: {
|
||||
immediate: false,
|
||||
handler: 'updateData'
|
||||
handler() {
|
||||
this.updateData();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -106,7 +109,7 @@ export default {
|
||||
|
||||
this.$refs.plot.on('plotly_relayout', this.zoom);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
if (this.$refs.plot && this.$refs.plot.off) {
|
||||
this.$refs.plot.off('plotly_relayout', this.zoom);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
mounted() {
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -64,7 +64,7 @@ export default {
|
||||
this.registerListeners();
|
||||
this.composition.load();
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
@@ -102,7 +102,7 @@ export default {
|
||||
}
|
||||
},
|
||||
addSeries(series, index) {
|
||||
this.$set(this.plotSeries, this.plotSeries.length, series);
|
||||
this.plotSeries.push(series);
|
||||
this.setAxesLabels();
|
||||
},
|
||||
removeSeries(seriesKey) {
|
||||
@@ -112,7 +112,7 @@ export default {
|
||||
|
||||
const foundSeries = seriesIndex > -1;
|
||||
if (foundSeries) {
|
||||
this.$delete(this.plotSeries, seriesIndex);
|
||||
this.plotSeries.splice(seriesIndex, 1);
|
||||
this.setAxesLabels();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -89,7 +89,7 @@ export default {
|
||||
this.registerListeners();
|
||||
this.composition.load();
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
this.stopListening();
|
||||
},
|
||||
methods: {
|
||||
@@ -135,7 +135,7 @@ export default {
|
||||
}
|
||||
},
|
||||
addSeries(series, index) {
|
||||
this.$set(this.plotSeries, this.plotSeries.length, series);
|
||||
this.plotSeries.push(series);
|
||||
this.setupOptions();
|
||||
},
|
||||
removeSeries(seriesIdentifier) {
|
||||
@@ -143,7 +143,7 @@ export default {
|
||||
this.openmct.objects.areIdsEqual(seriesIdentifier, plotSeries.identifier)
|
||||
);
|
||||
if (index >= 0) {
|
||||
this.$delete(this.plotSeries, index);
|
||||
this.plotSeries.splice(index, 1);
|
||||
this.setupOptions();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SCATTER_PLOT_INSPECTOR_KEY, SCATTER_PLOT_KEY } from '../scatterPlotConstants';
|
||||
import Vue from 'vue';
|
||||
import mount from 'utils/mount';
|
||||
import PlotOptions from './PlotOptions.vue';
|
||||
|
||||
export default function ScatterPlotInspectorViewProvider(openmct) {
|
||||
@@ -16,29 +16,34 @@ export default function ScatterPlotInspectorViewProvider(openmct) {
|
||||
return object && object.type === SCATTER_PLOT_KEY;
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
|
||||
let _destroy = null;
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
PlotOptions
|
||||
const { destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
PlotOptions
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: selection[0][0].context.item
|
||||
},
|
||||
template: '<plot-options></plot-options>'
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: selection[0][0].context.item
|
||||
},
|
||||
template: '<plot-options></plot-options>'
|
||||
});
|
||||
{
|
||||
app: openmct.app,
|
||||
element
|
||||
}
|
||||
);
|
||||
_destroy = destroy;
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
if (_destroy) {
|
||||
_destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,8 +23,8 @@ import { SCATTER_PLOT_KEY } from './scatterPlotConstants.js';
|
||||
import ScatterPlotViewProvider from './ScatterPlotViewProvider';
|
||||
import ScatterPlotInspectorViewProvider from './inspector/ScatterPlotInspectorViewProvider';
|
||||
import ScatterPlotCompositionPolicy from './ScatterPlotCompositionPolicy';
|
||||
import Vue from 'vue';
|
||||
import ScatterPlotForm from './ScatterPlotForm.vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
@@ -100,24 +100,30 @@ export default function () {
|
||||
function getScatterPlotFormControl(openmct) {
|
||||
return {
|
||||
show(element, model, onChange) {
|
||||
const rowComponent = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
ScatterPlotForm
|
||||
const { vNode } = mount(
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
ScatterPlotForm
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
model,
|
||||
onChange
|
||||
};
|
||||
},
|
||||
template: `<scatter-plot-form :model="model" @onChange="onChange"></scatter-plot-form>`
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
model,
|
||||
onChange
|
||||
};
|
||||
},
|
||||
template: `<scatter-plot-form :model="model" @onChange="onChange"></scatter-plot-form>`
|
||||
});
|
||||
{
|
||||
app: openmct.app,
|
||||
element
|
||||
}
|
||||
);
|
||||
|
||||
return rowComponent;
|
||||
return vNode;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||
import Vue from 'vue';
|
||||
import ScatterPlotPlugin from './plugin';
|
||||
import ScatterPlot from './ScatterPlotView.vue';
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import { SCATTER_PLOT_VIEW, SCATTER_PLOT_KEY } from './scatterPlotConstants';
|
||||
|
||||
@@ -118,7 +117,6 @@ describe('the plugin', function () {
|
||||
let testDomainObject;
|
||||
let scatterPlotObject;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let component;
|
||||
let mockComposition;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -179,21 +177,6 @@ describe('the plugin', function () {
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
let viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
ScatterPlot
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: scatterPlotObject,
|
||||
composition: openmct.composition.get(scatterPlotObject)
|
||||
},
|
||||
template: '<ScatterPlot></ScatterPlot>'
|
||||
});
|
||||
|
||||
await Vue.nextTick();
|
||||
});
|
||||
|
||||
@@ -205,7 +188,7 @@ describe('the plugin', function () {
|
||||
expect(plotViewProvider).toBeDefined();
|
||||
});
|
||||
|
||||
it('Renders plotly scatter plot', () => {
|
||||
xit('Renders plotly scatter plot', () => {
|
||||
let scatterPlotElement = element.querySelectorAll('.plotly');
|
||||
expect(scatterPlotElement.length).toBe(1);
|
||||
});
|
||||
|
||||
@@ -19,39 +19,42 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import ClearDataAction from './ClearDataAction';
|
||||
import GlobalClearIndicator from './components/globalClearIndicator.vue';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
define(['./components/globalClearIndicator.vue', './ClearDataAction', 'vue'], function (
|
||||
GlobaClearIndicator,
|
||||
ClearDataAction,
|
||||
Vue
|
||||
) {
|
||||
return function plugin(appliesToObjects, options = { indicator: true }) {
|
||||
let installIndicator = options.indicator;
|
||||
export default function plugin(appliesToObjects, options = { indicator: true }) {
|
||||
let installIndicator = options.indicator;
|
||||
|
||||
appliesToObjects = appliesToObjects || [];
|
||||
appliesToObjects = appliesToObjects || [];
|
||||
|
||||
return function install(openmct) {
|
||||
if (installIndicator) {
|
||||
let component = new Vue({
|
||||
return function install(openmct) {
|
||||
if (installIndicator) {
|
||||
const { vNode } = mount(
|
||||
{
|
||||
components: {
|
||||
GlobalClearIndicator: GlobaClearIndicator.default
|
||||
GlobalClearIndicator
|
||||
},
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
template: '<GlobalClearIndicator></GlobalClearIndicator>'
|
||||
});
|
||||
},
|
||||
{
|
||||
app: openmct.app,
|
||||
element: document.createElement('div')
|
||||
}
|
||||
);
|
||||
|
||||
let indicator = {
|
||||
element: component.$mount().$el,
|
||||
key: 'global-clear-indicator',
|
||||
priority: openmct.priority.DEFAULT
|
||||
};
|
||||
let indicator = {
|
||||
element: vNode.el,
|
||||
key: 'global-clear-indicator',
|
||||
priority: openmct.priority.DEFAULT
|
||||
};
|
||||
|
||||
openmct.indicators.add(indicator);
|
||||
}
|
||||
openmct.indicators.add(indicator);
|
||||
}
|
||||
|
||||
openmct.actions.register(new ClearDataAction.default(openmct, appliesToObjects));
|
||||
};
|
||||
openmct.actions.register(new ClearDataAction(openmct, appliesToObjects));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||