mirror of
https://github.com/nasa/openmct.git
synced 2024-12-21 22:17:49 +00:00
* Change the mount utility to use Vue's createApp and defineComponent methods * Fix display layout memory leaks caused by `getSelectionContext` * fix some display layout leaks due to use of slots * Fix imagery memory leak (removed span tag). NOTE: CompassRose svg leaks memory - must test on firefox to see if this is a Chrome leak. * Fix ActionsAPI action collection and applicable actions leak. * Fix flexible layout memory leaks - remove listeners on unmount. NOTE: One type of overlay plot (Rover Yaw) is still leaking. * pass in the el on mount * e2e test config and spec changes * Remove mounting of limit lines. Use components directly * test: remove `.only()` * Fix display layout memory leaks * Enable passing tests * e2e README and appActions should be what master has. * lint: add word to cspell list * lint: fixes * lint:fix * fix: revert `el` change * fix: remove empty span * fix: creating shapes in displayLayout * fix: avoid `splice` as it loses reactivity * test: reduce timeout time * quick fixes * add prod mode and convert the test config to select the correct mode * Fix webpack prod config * Add launch flag for exposing window.gc * never worked * explicit naming * rename * We don't need to destroy view providers * test: increase timeout time * test: unskip all mem tests * fix(vue-loader): disable static hoisting * chore: run `test:perf:memory` * Don't destroy view providers * Move context menu once listener to beforeUnmount instead. * Disconnect all resize observers on unmount * Delete Test vue component * Use beforeUnmount and remove splice(0) in favor of [] for emptying arrays * re-structure * fix: unregister listener in pane.vue * test: tweak timeouts * chore: lint:fix * test: unskip perf tests * fix: unregister events properly * fix: unregister listener * fix: unregister listener * fix: unregister listener * fix: use `unmounted()` * fix: unregister listeners * fix: unregister listener properly * chore: lint:fix * test: fix imagery layer toggle test * test: increase timeout * Don't use anonymous functions for listeners * Destroy objects and event listeners properly * Delete config stores that are created by components * Use the right unmount hook. Destroy mounted view on unmount. * Use unmounted, not beforeUnmounted * Lint fixes * Fix time strip memory leak * Undo unneeded change for memory leaks. * chore: combine common webpack configs --------- Co-authored-by: Shefali Joshi <simplyrender@gmail.com> Co-authored-by: John Hill <john.c.hill@nasa.gov>
This commit is contained in:
parent
4f559fdccf
commit
97deec2c92
@ -197,7 +197,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- build_and_install:
|
- build_and_install:
|
||||||
node-version: <<parameters.node-version>>
|
node-version: <<parameters.node-version>>
|
||||||
- run: npm run test:perf
|
- run: npm run test:perf:memory
|
||||||
|
- run: npm run test:perf:localhost
|
||||||
|
- run: npm run test:perf:contract
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: test-results/results.xml
|
path: test-results/results.xml
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
|
@ -480,7 +480,10 @@
|
|||||||
"sinonjs",
|
"sinonjs",
|
||||||
"generatedata",
|
"generatedata",
|
||||||
"grandsearch",
|
"grandsearch",
|
||||||
"websockets"
|
"websockets",
|
||||||
|
"swgs",
|
||||||
|
"memlab",
|
||||||
|
"devmode"
|
||||||
],
|
],
|
||||||
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US"],
|
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US"],
|
||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
|
@ -33,6 +33,16 @@ const projectRootDir = path.resolve(__dirname, '..');
|
|||||||
/** @type {import('webpack').Configuration} */
|
/** @type {import('webpack').Configuration} */
|
||||||
const config = {
|
const config = {
|
||||||
context: projectRootDir,
|
context: projectRootDir,
|
||||||
|
devServer: {
|
||||||
|
client: {
|
||||||
|
progress: true,
|
||||||
|
overlay: {
|
||||||
|
// Disable overlay for runtime errors.
|
||||||
|
// See: https://github.com/webpack/webpack-dev-server/issues/4771
|
||||||
|
runtimeErrors: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
entry: {
|
entry: {
|
||||||
openmct: './openmct.js',
|
openmct: './openmct.js',
|
||||||
generatorWorker: './example/generator/generatorWorker.js',
|
generatorWorker: './example/generator/generatorWorker.js',
|
||||||
@ -125,6 +135,7 @@ const config = {
|
|||||||
loader: 'vue-loader',
|
loader: 'vue-loader',
|
||||||
options: {
|
options: {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
|
hoistStatic: false,
|
||||||
whitespace: 'preserve',
|
whitespace: 'preserve',
|
||||||
compatConfig: {
|
compatConfig: {
|
||||||
MODE: 2
|
MODE: 2
|
||||||
|
@ -45,14 +45,6 @@ module.exports = merge(common, {
|
|||||||
directory: path.join(__dirname, '..', '/dist'),
|
directory: path.join(__dirname, '..', '/dist'),
|
||||||
publicPath: '/dist',
|
publicPath: '/dist',
|
||||||
watch: false
|
watch: false
|
||||||
},
|
|
||||||
client: {
|
|
||||||
progress: true,
|
|
||||||
overlay: {
|
|
||||||
// Disable overlay for runtime errors.
|
|
||||||
// See: https://github.com/webpack/webpack-dev-server/issues/4771
|
|
||||||
runtimeErrors: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -134,11 +134,11 @@ npm run test:e2e:updatesnapshots
|
|||||||
|
|
||||||
## Performance Testing
|
## Performance Testing
|
||||||
|
|
||||||
The open source performance tests function mostly as a contract for the locator logic, functionality, and assumptions will work in our downstream, closed source test suites.
|
The open source performance tests function in three ways which match their naming and folder structure:
|
||||||
|
|
||||||
They're found under `./e2e/tests/performance` and are to be executed with the following npm script:
|
`./e2e/tests/performance` - The tests at the root of this folder path detect functional changes which are mostly apparent with large performance regressions like [this](https://github.com/nasa/openmct/issues/6879). These tests run against openmct webpack in `production-mode` with the `npm run test:perf:localhost` script.
|
||||||
|
`./e2e/tests/performance/contract/` - These tests serve as [contracts](https://martinfowler.com/bliki/ContractTest.html) for the locator logic, functionality, and assumptions will work in our downstream, closed source test suites. These tests run against openmct webpack in `dev-mode` with the `npm run test:perf:contract` script.
|
||||||
`npm run test:perf`
|
`./e2e/tests/performance/memory/` - These tests execute memory leak detection checks in various ways. This is expected to evolve as we move to the `memlab` project. These tests run against openmct webpack in `production-mode` with the `npm run test:perf:memory` script.
|
||||||
|
|
||||||
These tests are expected to become blocking and gating with assertions as we extend the capabilities of Playwright.
|
These tests are expected to become blocking and gating with assertions as we extend the capabilities of Playwright.
|
||||||
|
|
||||||
@ -158,8 +158,11 @@ Our file structure follows the type of type of testing being excercised at the e
|
|||||||
|`./tests/functional/example/` | Tests which specifically verify the example plugins (e.g.: Sine Wave Generator).|
|
|`./tests/functional/example/` | Tests which specifically verify the example plugins (e.g.: Sine Wave Generator).|
|
||||||
|`./tests/functional/plugins/` | Tests which loosely test each plugin. This folder is the most likely to change. Note: some `@snapshot` tests are still contained within this structure.|
|
|`./tests/functional/plugins/` | Tests which loosely test each plugin. This folder is the most likely to change. Note: some `@snapshot` tests are still contained within this structure.|
|
||||||
|`./tests/framework/` | Tests which verify that our testing framework's functionality and assumptions will continue to work based on further refactoring or Playwright version changes (e.g.: verifying custom fixtures and appActions).|
|
|`./tests/framework/` | Tests which verify that our testing framework's functionality and assumptions will continue to work based on further refactoring or Playwright version changes (e.g.: verifying custom fixtures and appActions).|
|
||||||
|`./tests/performance/` | Performance tests.|
|
|`./tests/performance/` | Performance tests which should be run on every commit.|
|
||||||
|
|`./tests/performance/contract/` | A subset of performance tests which are designed to provide a contract between the open source tests which are run on every commit and the downstream tests which are run post merge and with other frameworks.|
|
||||||
|
|`./tests/performance/memory` | A subset of performance tests which are designed to test for memory leaks.|
|
||||||
|`./tests/visual/` | Visual tests.|
|
|`./tests/visual/` | Visual tests.|
|
||||||
|
|`./tests/visual/component/` | Visual tests which are only run against a single component.|
|
||||||
|`./appActions.js` | Contains common methods which can be leveraged by test case authors to quickly move through the application when writing new tests.|
|
|`./appActions.js` | Contains common methods which can be leveraged by test case authors to quickly move through the application when writing new tests.|
|
||||||
|`./baseFixture.js` | Contains base fixtures which only extend default `@playwright/test` functionality. The expectation is that these fixtures will be removed as the native Playwright API improves|
|
|`./baseFixture.js` | Contains base fixtures which only extend default `@playwright/test` functionality. The expectation is that these fixtures will be removed as the native Playwright API improves|
|
||||||
|
|
||||||
@ -176,6 +179,7 @@ Open MCT is leveraging the [config file](https://playwright.dev/docs/test-config
|
|||||||
|`./playwright-ci.config.js` | Used when running in CI or to debug CI issues locally|
|
|`./playwright-ci.config.js` | Used when running in CI or to debug CI issues locally|
|
||||||
|`./playwright-local.config.js` | Used when running locally|
|
|`./playwright-local.config.js` | Used when running locally|
|
||||||
|`./playwright-performance.config.js` | Used when running performance tests in CI or locally|
|
|`./playwright-performance.config.js` | Used when running performance tests in CI or locally|
|
||||||
|
|`./playwright-performance-devmode.config.js` | Used when running performance tests in CI or locally|
|
||||||
|`./playwright-visual.config.js` | Used to run the visual tests in CI or locally|
|
|`./playwright-visual.config.js` | Used to run the visual tests in CI or locally|
|
||||||
|
|
||||||
#### Test Tags
|
#### Test Tags
|
||||||
|
@ -2,25 +2,24 @@
|
|||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
const CI = process.env.CI === 'true';
|
|
||||||
|
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
retries: 1, //Only for debugging purposes for trace: 'on-first-retry'
|
retries: 1, //Only for debugging purposes for trace: 'on-first-retry'
|
||||||
testDir: 'tests/performance/',
|
testDir: 'tests/performance/',
|
||||||
|
testMatch: '*.contract.perf.spec.js', //Run everything except contract tests which require marks in dev mode
|
||||||
timeout: 60 * 1000,
|
timeout: 60 * 1000,
|
||||||
workers: 1, //Only run in serial with 1 worker
|
workers: 1, //Only run in serial with 1 worker
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start', //coverage not generated
|
command: 'npm run start', //need development mode for performance.marks and others
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: !CI
|
reuseExistingServer: false
|
||||||
},
|
},
|
||||||
use: {
|
use: {
|
||||||
browserName: 'chromium',
|
browserName: 'chromium',
|
||||||
baseURL: 'http://localhost:8080/',
|
baseURL: 'http://localhost:8080/',
|
||||||
headless: CI, //Only if running locally
|
headless: true,
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: false, //HTTP performance varies!
|
||||||
screenshot: 'off',
|
screenshot: 'off',
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
video: 'off'
|
video: 'off'
|
||||||
@ -28,6 +27,7 @@ const config = {
|
|||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chrome',
|
name: 'chrome',
|
||||||
|
testIgnore: '*.memory.perf.spec.js', //Do not run memory tests without proper flags. Shouldn't get here
|
||||||
use: {
|
use: {
|
||||||
browserName: 'chromium'
|
browserName: 'chromium'
|
||||||
}
|
}
|
60
e2e/playwright-performance-prod.config.js
Normal file
60
e2e/playwright-performance-prod.config.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/* eslint-disable no-undef */
|
||||||
|
// playwright.config.js
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
|
const config = {
|
||||||
|
retries: 0, //Only for debugging purposes for trace: 'on-first-retry'
|
||||||
|
testDir: 'tests/performance/',
|
||||||
|
testIgnore: '*.contract.perf.spec.js', //Run everything except contract tests which require marks in dev mode
|
||||||
|
timeout: 60 * 1000,
|
||||||
|
workers: 1, //Only run in serial with 1 worker
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run start:prod', //Production mode
|
||||||
|
url: 'http://localhost:8080/#',
|
||||||
|
timeout: 200 * 1000,
|
||||||
|
reuseExistingServer: false //Must be run with this option to prevent dev mode
|
||||||
|
},
|
||||||
|
use: {
|
||||||
|
baseURL: 'http://localhost:8080/',
|
||||||
|
headless: true,
|
||||||
|
ignoreHTTPSErrors: false, //HTTP performance varies!
|
||||||
|
screenshot: 'off',
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
video: 'off'
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chrome-memory',
|
||||||
|
testMatch: '*.memory.perf.spec.js', //Only run memory tests
|
||||||
|
use: {
|
||||||
|
browserName: 'chromium',
|
||||||
|
launchOptions: {
|
||||||
|
args: [
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-notifications',
|
||||||
|
'--use-fake-ui-for-media-stream',
|
||||||
|
'--use-fake-device-for-media-stream',
|
||||||
|
'--js-flags=--no-move-object-start --expose-gc',
|
||||||
|
'--enable-precise-memory-info',
|
||||||
|
'--display=:100'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'chrome',
|
||||||
|
testIgnore: '*.memory.perf.spec.js', //Do not run memory tests without proper flags
|
||||||
|
use: {
|
||||||
|
browserName: 'chromium'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
reporter: [
|
||||||
|
['list'],
|
||||||
|
['junit', { outputFile: '../test-results/results.xml' }],
|
||||||
|
['json', { outputFile: '../test-results/results.json' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
1
e2e/test-data/memory-leak-detection.json
Normal file
1
e2e/test-data/memory-leak-detection.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,121 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2023, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/*
|
|
||||||
This test suite is an initial example for memory leak testing using performance. This configuration and execution must
|
|
||||||
be kept separate from the traditional performance measurements to avoid any "observer" effects associated with tracing
|
|
||||||
or profiling playwright and/or the browser.
|
|
||||||
|
|
||||||
Based on a pattern identified in https://github.com/trentmwillis/devtools-protocol-demos/blob/master/testing-demos/memory-leak-by-heap.js
|
|
||||||
and https://github.com/paulirish/automated-chrome-profiling/issues/3
|
|
||||||
|
|
||||||
Best path forward: https://github.com/cowchimp/headless-devtools/blob/master/src/Memory/example.js
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { test, expect } = require('@playwright/test');
|
|
||||||
|
|
||||||
const filePath = 'e2e/test-data/PerformanceDisplayLayout.json';
|
|
||||||
|
|
||||||
// eslint-disable-next-line playwright/no-skipped-test
|
|
||||||
test.describe.skip('Memory Performance tests', () => {
|
|
||||||
test.beforeEach(async ({ page, browser }, testInfo) => {
|
|
||||||
// Go to baseURL
|
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
|
||||||
|
|
||||||
// Click a:has-text("My Items")
|
|
||||||
await page.locator('a:has-text("My Items")').click({
|
|
||||||
button: 'right'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Click text=Import from JSON
|
|
||||||
await page.locator('text=Import from JSON').click();
|
|
||||||
|
|
||||||
// Upload Performance Display Layout.json
|
|
||||||
await page.setInputFiles('#fileElem', filePath);
|
|
||||||
|
|
||||||
// Click text=OK
|
|
||||||
await page.locator('text=OK').click();
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.locator('a:has-text("Performance Display Layout Display Layout")')
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => {
|
|
||||||
await page.goto('./', { waitUntil: 'networkidle' });
|
|
||||||
|
|
||||||
// To to Search Available after Launch
|
|
||||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
|
||||||
// Fill Search input
|
|
||||||
await page
|
|
||||||
.locator('[aria-label="OpenMCT Search"] input[type="search"]')
|
|
||||||
.fill('Performance Display Layout');
|
|
||||||
//Search Result Appears and is clicked
|
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation(),
|
|
||||||
page.locator('a:has-text("Performance Display Layout")').first().click()
|
|
||||||
]);
|
|
||||||
|
|
||||||
//Time to Example Imagery Frame loads within Display Layout
|
|
||||||
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
|
|
||||||
//Time to Example Imagery object loads
|
|
||||||
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
|
|
||||||
|
|
||||||
const client = await page.context().newCDPSession(page);
|
|
||||||
await client.send('HeapProfiler.enable');
|
|
||||||
await client.send('HeapProfiler.startSampling');
|
|
||||||
// await client.send('HeapProfiler.collectGarbage');
|
|
||||||
await client.send('Performance.enable');
|
|
||||||
|
|
||||||
let performanceMetricsBefore = await client.send('Performance.getMetrics');
|
|
||||||
console.log(performanceMetricsBefore.metrics);
|
|
||||||
|
|
||||||
//await client.send('Performance.disable');
|
|
||||||
|
|
||||||
//Open Large view
|
|
||||||
await page.locator('button:has-text("Large View")').click();
|
|
||||||
await client.send('HeapProfiler.takeHeapSnapshot');
|
|
||||||
|
|
||||||
//Time to Imagery Rendered in Large Frame
|
|
||||||
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
|
|
||||||
|
|
||||||
//Time to Example Imagery object loads
|
|
||||||
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
|
|
||||||
|
|
||||||
// Click Close Icon
|
|
||||||
await page.locator('.c-click-icon').click();
|
|
||||||
|
|
||||||
//Time to Example Imagery Frame loads within Display Layout
|
|
||||||
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
|
|
||||||
//Time to Example Imagery object loads
|
|
||||||
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
|
|
||||||
|
|
||||||
await client.send('HeapProfiler.collectGarbage');
|
|
||||||
//await client.send('Performance.enable');
|
|
||||||
|
|
||||||
let performanceMetricsAfter = await client.send('Performance.getMetrics');
|
|
||||||
console.log(performanceMetricsAfter.metrics);
|
|
||||||
|
|
||||||
//await client.send('Performance.disable');
|
|
||||||
});
|
|
||||||
});
|
|
299
e2e/tests/performance/memory/navigation.memory.perf.spec.js
Normal file
299
e2e/tests/performance/memory/navigation.memory.perf.spec.js
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
const memoryLeakFilePath = 'e2e/test-data/memory-leak-detection.json';
|
||||||
|
/**
|
||||||
|
* Executes tests to verify that views are not leaking memory on navigation away. This sort of
|
||||||
|
* memory leak is generally caused by a failure to clean up registered listeners.
|
||||||
|
*
|
||||||
|
* These tests are executed on a set of pre-built displays loaded from ../test-data/memory-leak-detection.json.
|
||||||
|
*
|
||||||
|
* In order to modify the test data set:
|
||||||
|
* 1. Run Open MCT locally (npm start)
|
||||||
|
* 2. Right click on a folder in the tree, and select "Import From JSON"
|
||||||
|
* 3. In the subsequent dialog, select the file ../test-data/memory-leak-detection.json
|
||||||
|
* 4. Click "OK"
|
||||||
|
* 5. Modify test objects as desired
|
||||||
|
* 6. Right click on the "Memory Leak Detection" folder, and select "Export to JSON"
|
||||||
|
* 7. Copy the exported file to ../test-data/memory-leak-detection.json
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NAV_LEAK_TIMEOUT = 10 * 1000; // 10s
|
||||||
|
test.describe('Navigation memory leak is not detected in', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
await page.locator('a:has-text("My Items")').click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.locator('text=Import from JSON').click();
|
||||||
|
|
||||||
|
// Upload memory-leak-detection.json
|
||||||
|
await page.setInputFiles('#fileElem', memoryLeakFilePath);
|
||||||
|
|
||||||
|
await page.locator('text=OK').click();
|
||||||
|
|
||||||
|
await expect(page.locator('a:has-text("Memory Leak Detection")')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('plot view', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(page, 'overlay-plot-single-1hz-swg', {
|
||||||
|
timeout: NAV_LEAK_TIMEOUT
|
||||||
|
});
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stacked plot view', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(page, 'stacked-plot-single-1hz-swg', {
|
||||||
|
timeout: NAV_LEAK_TIMEOUT
|
||||||
|
});
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('LAD table view', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(page, 'lad-table-single-1hz-swg', {
|
||||||
|
timeout: NAV_LEAK_TIMEOUT
|
||||||
|
});
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('LAD table set', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(page, 'lad-table-set-single-1hz-swg', {
|
||||||
|
timeout: NAV_LEAK_TIMEOUT
|
||||||
|
});
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO: Figure out why using the `table-row` component inside the `table` component leaks TelemetryTableRow objects
|
||||||
|
test('telemetry table view', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'telemetry-table-single-1hz-swg',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO: Figure out why using the `SideBar` component inside the leaks Notebook objects
|
||||||
|
test('notebook view', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'notebook-memory-leak-detection-test',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('display layout of a single SWG alphanumeric', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'display-layout-single-1hz-swg',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('display layout of a single SWG plot', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'display-layout-single-overlay-plot',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO: Figure out why `svg` in the CompassRose component leaks imagery
|
||||||
|
test('example imagery view', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'example-imagery-memory-leak-test',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('display layout of example imagery views', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'display-layout-images-memory-leak-test',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('display layout with plots of swgs, alphanumerics, and condition sets, ', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'display-layout-simple-telemetry',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flexible layout with plots of swgs', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'flexible-layout-plots-memory-leak-test',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flexible layout of example imagery views', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'flexible-layout-images-memory-leak-test',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tabbed view of display layouts and time strips', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'tab-view-simple-memory-leak-test',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT * 6 * 2 // 2 min
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('time strip view of telemetry', async ({ page }) => {
|
||||||
|
const result = await navigateToObjectAndDetectMemoryLeak(
|
||||||
|
page,
|
||||||
|
'time-strip-telemetry-memory-leak-test',
|
||||||
|
{
|
||||||
|
timeout: NAV_LEAK_TIMEOUT * 6 // 1 min
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we got here without timing out, then the root view object was garbage collected and no memory leak was detected.
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {*} objectName
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async function navigateToObjectAndDetectMemoryLeak(page, objectName) {
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
|
||||||
|
// Fill Search input
|
||||||
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill(objectName);
|
||||||
|
|
||||||
|
//Search Result Appears and is clicked
|
||||||
|
await Promise.all([
|
||||||
|
page.locator(`div.c-gsearch-result__title:has-text("${objectName}")`).first().click(),
|
||||||
|
page.waitForNavigation()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Register a finalization listener on the root node for the view. This tends to be the last thing to be
|
||||||
|
// garbage collected since it has either direct or indirect references to all resources used by the view. Therefore it's a pretty good proxy
|
||||||
|
// for detecting memory leaks.
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.gcPromise = new Promise((resolve) => {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
window.fr = new FinalizationRegistry(resolve);
|
||||||
|
window.fr.register(
|
||||||
|
window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild,
|
||||||
|
'navigatedObject',
|
||||||
|
window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nav back to folder
|
||||||
|
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
|
||||||
|
await page.waitForNavigation();
|
||||||
|
|
||||||
|
// This next code block blocks until the finalization listener is called and the gcPromise resolved. This means that the root node for the view has been garbage collected.
|
||||||
|
// In the event that the root node is not garbage collected, the gcPromise will never resolve and the test will time out.
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const gcPromise = window.gcPromise;
|
||||||
|
window.gcPromise = null;
|
||||||
|
|
||||||
|
// Manually invoke the garbage collector once all references are removed.
|
||||||
|
window.gc();
|
||||||
|
|
||||||
|
return gcPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up the finalization registry since we don't need it any more.
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.fr = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we get here without timing out, it means the garbage collection promise resolved and the test passed.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
@ -79,6 +79,7 @@
|
|||||||
"clean": "rm -rf ./dist ./node_modules ./package-lock.json ./coverage ./html-test-results ./test-results ./.nyc_output ",
|
"clean": "rm -rf ./dist ./node_modules ./package-lock.json ./coverage ./html-test-results ./test-results ./.nyc_output ",
|
||||||
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
|
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
|
||||||
"start": "npx webpack serve --config ./.webpack/webpack.dev.js",
|
"start": "npx webpack serve --config ./.webpack/webpack.dev.js",
|
||||||
|
"start:prod": "npx webpack serve --config ./.webpack/webpack.prod.js",
|
||||||
"start:coverage": "npx webpack serve --config ./.webpack/webpack.coverage.js",
|
"start:coverage": "npx webpack serve --config ./.webpack/webpack.coverage.js",
|
||||||
"lint": "eslint example src e2e --ext .js openmct.js --max-warnings=0 && eslint example src --ext .vue",
|
"lint": "eslint example src e2e --ext .js openmct.js --max-warnings=0 && eslint example src --ext .vue",
|
||||||
"lint:spelling": "cspell \"**/*.{js,md,vue}\" --show-context --gitignore",
|
"lint:spelling": "cspell \"**/*.{js,md,vue}\" --show-context --gitignore",
|
||||||
@ -101,7 +102,9 @@
|
|||||||
"test:e2e:visual:full": "percy exec --config ./e2e/.percy.nightly.yml -- npx playwright test --config=e2e/playwright-visual.config.js --grep-invert @unstable",
|
"test:e2e:visual:full": "percy exec --config ./e2e/.percy.nightly.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: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:e2e:watch": "npx playwright test --ui --config=e2e/playwright-ci.config.js",
|
||||||
"test:perf": "npx playwright test --config=e2e/playwright-performance.config.js",
|
"test:perf:contract": "npx playwright test --config=e2e/playwright-performance-dev.config.js",
|
||||||
|
"test:perf:localhost": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome",
|
||||||
|
"test:perf:memory": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome-memory",
|
||||||
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2023/gm' ./src/ui/layout/AboutDialog.vue",
|
"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'",
|
"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'",
|
||||||
"cov:e2e:report": "nyc report --reporter=lcovonly --report-dir=./coverage/e2e",
|
"cov:e2e:report": "nyc report --reporter=lcovonly --report-dir=./coverage/e2e",
|
||||||
|
@ -92,7 +92,7 @@ class ActionsAPI extends EventEmitter {
|
|||||||
if (this._actionCollections.has(key)) {
|
if (this._actionCollections.has(key)) {
|
||||||
let actionCollection = this._actionCollections.get(key);
|
let actionCollection = this._actionCollections.get(key);
|
||||||
actionCollection.off('destroy', this._updateCachedActionCollections);
|
actionCollection.off('destroy', this._updateCachedActionCollections);
|
||||||
|
delete actionCollection.applicableActions;
|
||||||
this._actionCollections.delete(key);
|
this._actionCollections.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,9 +185,10 @@ export default class CompositionProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#publicAPI.objects.eventEmitter.on('mutation', this.#onMutation.bind(this));
|
const onMutation = this.#onMutation.bind(this);
|
||||||
|
this.#publicAPI.objects.eventEmitter.on('mutation', onMutation);
|
||||||
this.topicListener = () => {
|
this.topicListener = () => {
|
||||||
this.#publicAPI.objects.eventEmitter.off('mutation', this.#onMutation.bind(this));
|
this.#publicAPI.objects.eventEmitter.off('mutation', onMutation);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ export default {
|
|||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
if (this.plotResizeObserver) {
|
if (this.plotResizeObserver) {
|
||||||
this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
|
this.plotResizeObserver.disconnect();
|
||||||
clearTimeout(this.resizeTimer);
|
clearTimeout(this.resizeTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.plotResizeObserver) {
|
if (this.plotResizeObserver) {
|
||||||
this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
|
this.plotResizeObserver.disconnect();
|
||||||
clearTimeout(this.resizeTimer);
|
clearTimeout(this.resizeTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +65,9 @@ export default class Condition extends EventEmitter {
|
|||||||
|
|
||||||
this.trigger = conditionConfiguration.configuration.trigger;
|
this.trigger = conditionConfiguration.configuration.trigger;
|
||||||
this.summary = '';
|
this.summary = '';
|
||||||
|
this.handleCriterionUpdated = this.handleCriterionUpdated.bind(this);
|
||||||
|
this.handleOldTelemetryCriterion = this.handleOldTelemetryCriterion.bind(this);
|
||||||
|
this.handleTelemetryStaleness = this.handleTelemetryStaleness.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateResult(datum) {
|
updateResult(datum) {
|
||||||
@ -195,15 +198,15 @@ export default class Condition extends EventEmitter {
|
|||||||
if (found) {
|
if (found) {
|
||||||
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
|
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
|
||||||
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
|
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
|
||||||
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
newCriterion.on('criterionUpdated', this.handleCriterionUpdated);
|
||||||
newCriterion.on('telemetryIsOld', (obj) => this.handleOldTelemetryCriterion(obj));
|
newCriterion.on('telemetryIsOld', this.handleOldTelemetryCriterion);
|
||||||
newCriterion.on('telemetryStaleness', () => this.handleTelemetryStaleness());
|
newCriterion.on('telemetryStaleness', this.handleTelemetryStaleness);
|
||||||
|
|
||||||
let criterion = found.item;
|
let criterion = found.item;
|
||||||
criterion.unsubscribe();
|
criterion.unsubscribe();
|
||||||
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
criterion.off('criterionUpdated', this.handleCriterionUpdated);
|
||||||
criterion.off('telemetryIsOld', (obj) => this.handleOldTelemetryCriterion(obj));
|
criterion.off('telemetryIsOld', this.handleOldTelemetryCriterion);
|
||||||
newCriterion.off('telemetryStaleness', () => this.handleTelemetryStaleness());
|
newCriterion.off('telemetryStaleness', this.handleTelemetryStaleness);
|
||||||
this.criteria.splice(found.index, 1, newCriterion);
|
this.criteria.splice(found.index, 1, newCriterion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,9 +215,9 @@ export default class Condition extends EventEmitter {
|
|||||||
let found = this.findCriterion(id);
|
let found = this.findCriterion(id);
|
||||||
if (found) {
|
if (found) {
|
||||||
let criterion = found.item;
|
let criterion = found.item;
|
||||||
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
criterion.off('criterionUpdated', this.handleCriterionUpdated);
|
||||||
criterion.off('telemetryIsOld', (obj) => this.handleOldTelemetryCriterion(obj));
|
criterion.off('telemetryIsOld', this.handleOldTelemetryCriterion);
|
||||||
criterion.off('telemetryStaleness', () => this.handleTelemetryStaleness());
|
criterion.off('telemetryStaleness', this.handleTelemetryStaleness);
|
||||||
criterion.destroy();
|
criterion.destroy();
|
||||||
this.criteria.splice(found.index, 1);
|
this.criteria.splice(found.index, 1);
|
||||||
|
|
||||||
|
@ -127,6 +127,7 @@ export default {
|
|||||||
this.composition.off('remove', this.removeTelemetryObject);
|
this.composition.off('remove', this.removeTelemetryObject);
|
||||||
if (this.conditionManager) {
|
if (this.conditionManager) {
|
||||||
this.conditionManager.off('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
|
this.conditionManager.off('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
|
||||||
|
this.conditionManager.off('noTelemetryObjects', this.emitNoTelemetryObjectEvent);
|
||||||
this.conditionManager.destroy();
|
this.conditionManager.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ export default {
|
|||||||
this.listenToConditionSetChanges();
|
this.listenToConditionSetChanges();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
unmounted() {
|
||||||
this.stopListeningToConditionSetChanges();
|
this.stopListeningToConditionSetChanges();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -170,7 +170,7 @@ define(['lodash'], function (_) {
|
|||||||
if (form) {
|
if (form) {
|
||||||
showForm(form, name, selectionPath);
|
showForm(form, name, selectionPath);
|
||||||
} else {
|
} else {
|
||||||
selectionPath[0].context.addElement(name);
|
openmct.objectViews.emit('contextAction', 'addElement', name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'add',
|
key: 'add',
|
||||||
@ -236,7 +236,6 @@ define(['lodash'], function (_) {
|
|||||||
icon: 'icon-trash',
|
icon: 'icon-trash',
|
||||||
title: 'Delete the selected object',
|
title: 'Delete the selected object',
|
||||||
method: function () {
|
method: function () {
|
||||||
let removeItem = selectionPath[1].context.removeItem;
|
|
||||||
let prompt = openmct.overlays.dialog({
|
let prompt = openmct.overlays.dialog({
|
||||||
iconClass: 'alert',
|
iconClass: 'alert',
|
||||||
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
|
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
|
||||||
@ -245,7 +244,11 @@ define(['lodash'], function (_) {
|
|||||||
label: 'OK',
|
label: 'OK',
|
||||||
emphasis: 'true',
|
emphasis: 'true',
|
||||||
callback: function () {
|
callback: function () {
|
||||||
removeItem(getAllTypes(selection));
|
openmct.objectViews.emit(
|
||||||
|
'contextAction',
|
||||||
|
'removeItem',
|
||||||
|
getAllTypes(selection)
|
||||||
|
);
|
||||||
prompt.dismiss();
|
prompt.dismiss();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -290,7 +293,12 @@ define(['lodash'], function (_) {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
method: function (option) {
|
method: function (option) {
|
||||||
selectionPath[1].context.orderItem(option.value, getAllTypes(selectedObjects));
|
openmct.objectViews.emit(
|
||||||
|
'contextAction',
|
||||||
|
'orderItem',
|
||||||
|
option.value,
|
||||||
|
getAllTypes(selectedObjects)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -474,9 +482,7 @@ define(['lodash'], function (_) {
|
|||||||
icon: 'icon-duplicate',
|
icon: 'icon-duplicate',
|
||||||
title: 'Duplicate the selected object',
|
title: 'Duplicate the selected object',
|
||||||
method: function () {
|
method: function () {
|
||||||
let duplicateItem = selectionPath[1].context.duplicateItem;
|
openmct.objectViews.emit('contextAction', 'duplicateItem', selection);
|
||||||
|
|
||||||
duplicateItem(selection);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -555,6 +561,7 @@ define(['lodash'], function (_) {
|
|||||||
|
|
||||||
function getViewSwitcherMenu(selectedParent, selectionPath, selection) {
|
function getViewSwitcherMenu(selectedParent, selectionPath, selection) {
|
||||||
if (selection.length === 1) {
|
if (selection.length === 1) {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
let displayLayoutContext = selectionPath[1].context;
|
let displayLayoutContext = selectionPath[1].context;
|
||||||
let selectedItemContext = selectionPath[0].context;
|
let selectedItemContext = selectionPath[0].context;
|
||||||
let selectedItemType = selectedItemContext.item.type;
|
let selectedItemType = selectedItemContext.item.type;
|
||||||
@ -574,14 +581,18 @@ define(['lodash'], function (_) {
|
|||||||
label: 'View type',
|
label: 'View type',
|
||||||
options: viewOptions,
|
options: viewOptions,
|
||||||
method: function (option) {
|
method: function (option) {
|
||||||
displayLayoutContext.switchViewType(selectedItemContext, option.value, selection);
|
openmct.objectViews.emit(
|
||||||
|
'contextAction',
|
||||||
|
'switchViewType',
|
||||||
|
selectedItemContext,
|
||||||
|
option.value,
|
||||||
|
selection
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (selection.length > 1) {
|
} else if (selection.length > 1) {
|
||||||
if (areAllViews('telemetry-view', 'layoutItem.type', selection)) {
|
if (areAllViews('telemetry-view', 'layoutItem.type', selection)) {
|
||||||
let displayLayoutContext = selectionPath[1].context;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
control: 'menu',
|
control: 'menu',
|
||||||
domainObject: selectedParent,
|
domainObject: selectedParent,
|
||||||
@ -590,12 +601,15 @@ define(['lodash'], function (_) {
|
|||||||
label: 'View type',
|
label: 'View type',
|
||||||
options: APPLICABLE_VIEWS['telemetry-view-multi'],
|
options: APPLICABLE_VIEWS['telemetry-view-multi'],
|
||||||
method: function (option) {
|
method: function (option) {
|
||||||
displayLayoutContext.mergeMultipleTelemetryViews(selection, option.value);
|
openmct.objectViews.emit(
|
||||||
|
'contextAction',
|
||||||
|
'mergeMultipleTelemetryViews',
|
||||||
|
selection,
|
||||||
|
option.value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (areAllViews('telemetry.plot.overlay', 'item.type', selection)) {
|
} else if (areAllViews('telemetry.plot.overlay', 'item.type', selection)) {
|
||||||
let displayLayoutContext = selectionPath[1].context;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
control: 'menu',
|
control: 'menu',
|
||||||
domainObject: selectedParent,
|
domainObject: selectedParent,
|
||||||
@ -603,7 +617,12 @@ define(['lodash'], function (_) {
|
|||||||
title: 'Merge into a stacked plot',
|
title: 'Merge into a stacked plot',
|
||||||
options: APPLICABLE_VIEWS['telemetry.plot.overlay-multi'],
|
options: APPLICABLE_VIEWS['telemetry.plot.overlay-multi'],
|
||||||
method: function (option) {
|
method: function (option) {
|
||||||
displayLayoutContext.mergeMultipleOverlayPlots(selection, option.value);
|
openmct.objectViews.emit(
|
||||||
|
'contextAction',
|
||||||
|
'mergeMultipleOverlayPlots',
|
||||||
|
selection,
|
||||||
|
option.value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -627,7 +646,7 @@ define(['lodash'], function (_) {
|
|||||||
domainObject: displayLayoutContext.item,
|
domainObject: displayLayoutContext.item,
|
||||||
icon: ICON_GRID_SHOW,
|
icon: ICON_GRID_SHOW,
|
||||||
method: function () {
|
method: function () {
|
||||||
displayLayoutContext.toggleGrid();
|
openmct.objectViews.emit('contextAction', 'toggleGrid');
|
||||||
|
|
||||||
this.icon = this.icon === ICON_GRID_SHOW ? ICON_GRID_HIDE : ICON_GRID_SHOW;
|
this.icon = this.icon === ICON_GRID_SHOW ? ICON_GRID_HIDE : ICON_GRID_SHOW;
|
||||||
},
|
},
|
||||||
@ -653,7 +672,7 @@ define(['lodash'], function (_) {
|
|||||||
|
|
||||||
function showForm(formStructure, name, selectionPath) {
|
function showForm(formStructure, name, selectionPath) {
|
||||||
openmct.forms.showForm(formStructure).then((changes) => {
|
openmct.forms.showForm(formStructure).then((changes) => {
|
||||||
selectionPath[0].context.addElement(name, changes);
|
openmct.objectViews.emit('contextAction', 'addElement', name, changes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,14 +25,16 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
:grid-size="gridSize"
|
:grid-size="gridSize"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
@move="move"
|
||||||
@endMove="() => $emit('endMove')"
|
@endMove="endMove"
|
||||||
>
|
>
|
||||||
<div
|
<template #content>
|
||||||
class="c-box-view u-style-receiver js-style-receiver"
|
<div
|
||||||
:class="[styleClass]"
|
class="c-box-view u-style-receiver js-style-receiver"
|
||||||
:style="style"
|
:class="[styleClass]"
|
||||||
></div>
|
:style="style"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
</layout-frame>
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -115,10 +117,18 @@ export default {
|
|||||||
this.initSelect
|
this.initSelect
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
unmounted() {
|
beforeUnmount() {
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
move(gridDelta) {
|
||||||
|
this.$emit('move', gridDelta);
|
||||||
|
},
|
||||||
|
endMove() {
|
||||||
|
this.$emit('endMove');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -144,7 +144,7 @@ function getItemDefinition(itemType, ...options) {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: components,
|
components: components,
|
||||||
inject: ['openmct', 'objectPath', 'options', 'objectUtils', 'currentView'],
|
inject: ['openmct', 'objectPath', 'options', 'currentView'],
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -221,13 +221,21 @@ export default {
|
|||||||
this.composition.load();
|
this.composition.load();
|
||||||
this.gridDimensions = [this.$el.offsetWidth, this.$el.scrollHeight];
|
this.gridDimensions = [this.$el.offsetWidth, this.$el.scrollHeight];
|
||||||
|
|
||||||
this.openmct.objects.observe(this.domainObject, 'configuration.items', (items) => {
|
this.unObserveItems = this.openmct.objects.observe(
|
||||||
this.layoutItems = items;
|
this.domainObject,
|
||||||
});
|
'configuration.items',
|
||||||
|
(items) => {
|
||||||
|
this.layoutItems = [...items];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.watchDisplayResize();
|
this.watchDisplayResize();
|
||||||
},
|
},
|
||||||
unmounted() {
|
beforeUnmount() {
|
||||||
|
if (this.unObserveItems) {
|
||||||
|
this.unObserveItems();
|
||||||
|
}
|
||||||
|
this.unwatchDisplayResize();
|
||||||
this.openmct.selection.off('change', this.setSelection);
|
this.openmct.selection.off('change', this.setSelection);
|
||||||
this.composition.off('add', this.addChild);
|
this.composition.off('add', this.addChild);
|
||||||
this.composition.off('remove', this.removeChild);
|
this.composition.off('remove', this.removeChild);
|
||||||
@ -251,9 +259,15 @@ export default {
|
|||||||
this.$el.click();
|
this.$el.click();
|
||||||
},
|
},
|
||||||
watchDisplayResize() {
|
watchDisplayResize() {
|
||||||
const resizeObserver = new ResizeObserver(() => this.updateGrid());
|
this.unwatchDisplayResize();
|
||||||
|
this.resizeObserver = new ResizeObserver(this.updateGrid);
|
||||||
|
|
||||||
resizeObserver.observe(this.$el);
|
this.resizeObserver.observe(this.$el);
|
||||||
|
},
|
||||||
|
unwatchDisplayResize() {
|
||||||
|
if (this.resizeObserver) {
|
||||||
|
this.resizeObserver.disconnect();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
addElement(itemType, element) {
|
addElement(itemType, element) {
|
||||||
this.addItem(itemType + '-view', element);
|
this.addItem(itemType + '-view', element);
|
||||||
@ -623,7 +637,7 @@ export default {
|
|||||||
return this.openmct.objects.makeKeyString(item.identifier) !== keyString;
|
return this.openmct.objects.makeKeyString(item.identifier) !== keyString;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.layoutItems = layoutItems;
|
this.layoutItems = [...layoutItems];
|
||||||
this.mutate('configuration.items', layoutItems);
|
this.mutate('configuration.items', layoutItems);
|
||||||
this.clearSelection();
|
this.clearSelection();
|
||||||
},
|
},
|
||||||
|
@ -25,14 +25,16 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
:grid-size="gridSize"
|
:grid-size="gridSize"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
@move="move"
|
||||||
@endMove="() => $emit('endMove')"
|
@endMove="endMove"
|
||||||
>
|
>
|
||||||
<div
|
<template #content>
|
||||||
class="c-ellipse-view u-style-receiver js-style-receiver"
|
<div
|
||||||
:class="[styleClass]"
|
class="c-ellipse-view u-style-receiver js-style-receiver"
|
||||||
:style="style"
|
:class="[styleClass]"
|
||||||
></div>
|
:style="style"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
</layout-frame>
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -115,10 +117,18 @@ export default {
|
|||||||
this.initSelect
|
this.initSelect
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
unmounted() {
|
beforeUnmount() {
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
move(gridDelta) {
|
||||||
|
this.$emit('move', gridDelta);
|
||||||
|
},
|
||||||
|
endMove() {
|
||||||
|
this.$emit('endMove');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -25,10 +25,12 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
:grid-size="gridSize"
|
:grid-size="gridSize"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
@move="move"
|
||||||
@endMove="() => $emit('endMove')"
|
@endMove="endMove"
|
||||||
>
|
>
|
||||||
<div class="c-image-view" :class="[styleClass]" :style="style"></div>
|
<template #content>
|
||||||
|
<div class="c-image-view" :style="style"></div>
|
||||||
|
</template>
|
||||||
</layout-frame>
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -118,10 +120,18 @@ export default {
|
|||||||
this.initSelect
|
this.initSelect
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
unmounted() {
|
beforeUnmount() {
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
move(gridDelta) {
|
||||||
|
this.$emit('move', gridDelta);
|
||||||
|
},
|
||||||
|
endMove() {
|
||||||
|
this.$emit('endMove');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
}"
|
}"
|
||||||
:style="style"
|
:style="style"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot name="content"></slot>
|
||||||
<div class="c-frame__move-bar" @mousedown.left="startMove($event)"></div>
|
<div class="c-frame__move-bar" @mousedown.left="startMove($event)"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -20,24 +20,26 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<LayoutFrame
|
<layout-frame
|
||||||
:item="item"
|
:item="item"
|
||||||
:grid-size="gridSize"
|
:grid-size="gridSize"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
@move="move"
|
||||||
@endMove="() => $emit('endMove')"
|
@endMove="endMove"
|
||||||
>
|
>
|
||||||
<ObjectFrame
|
<template #content>
|
||||||
v-if="domainObject"
|
<ObjectFrame
|
||||||
ref="objectFrame"
|
v-if="domainObject"
|
||||||
:domain-object="domainObject"
|
ref="objectFrame"
|
||||||
:object-path="currentObjectPath"
|
:domain-object="domainObject"
|
||||||
:has-frame="item.hasFrame"
|
:object-path="currentObjectPath"
|
||||||
:show-edit-view="false"
|
:has-frame="item.hasFrame"
|
||||||
:layout-font-size="item.fontSize"
|
:show-edit-view="false"
|
||||||
:layout-font="item.font"
|
:layout-font-size="item.fontSize"
|
||||||
/>
|
:layout-font="item.font"
|
||||||
</LayoutFrame>
|
/>
|
||||||
|
</template>
|
||||||
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -104,8 +106,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
domainObject: undefined,
|
domainObject: undefined,
|
||||||
currentObjectPath: [],
|
currentObjectPath: []
|
||||||
mutablePromise: undefined
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -168,6 +169,12 @@ export default {
|
|||||||
delete this.immediatelySelect;
|
delete this.immediatelySelect;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
move(gridDelta) {
|
||||||
|
this.$emit('move', gridDelta);
|
||||||
|
},
|
||||||
|
endMove() {
|
||||||
|
this.$emit('endMove');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -25,42 +25,44 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
:grid-size="gridSize"
|
:grid-size="gridSize"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
@move="move"
|
||||||
@endMove="() => $emit('endMove')"
|
@endMove="endMove"
|
||||||
>
|
>
|
||||||
<div
|
<template #content>
|
||||||
v-if="domainObject"
|
|
||||||
ref="telemetryViewWrapper"
|
|
||||||
class="c-telemetry-view u-style-receiver"
|
|
||||||
:class="[itemClasses]"
|
|
||||||
:style="styleObject"
|
|
||||||
:data-font-size="item.fontSize"
|
|
||||||
:data-font="item.font"
|
|
||||||
@contextmenu.prevent="showContextMenu"
|
|
||||||
@mouseover.ctrl="showToolTip"
|
|
||||||
@mouseleave="hideToolTip"
|
|
||||||
>
|
|
||||||
<div class="is-status__indicator" :title="`This item is ${status}`"></div>
|
|
||||||
<div v-if="showLabel" class="c-telemetry-view__label">
|
|
||||||
<div class="c-telemetry-view__label-text">
|
|
||||||
{{ domainObject.name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="showValue"
|
v-if="domainObject"
|
||||||
:title="fieldName"
|
ref="telemetryViewWrapper"
|
||||||
class="c-telemetry-view__value"
|
class="c-telemetry-view u-style-receiver"
|
||||||
:class="[telemetryClass]"
|
:class="[itemClasses]"
|
||||||
|
:style="styleObject"
|
||||||
|
:data-font-size="item.fontSize"
|
||||||
|
:data-font="item.font"
|
||||||
|
@contextmenu.prevent="showContextMenu"
|
||||||
|
@mouseover.ctrl="showToolTip"
|
||||||
|
@mouseleave="hideToolTip"
|
||||||
>
|
>
|
||||||
<div class="c-telemetry-view__value-text">
|
<div class="is-status__indicator" :title="`This item is ${status}`"></div>
|
||||||
{{ telemetryValue }}
|
<div v-if="showLabel" class="c-telemetry-view__label">
|
||||||
<span v-if="unit && item.showUnits" class="c-telemetry-view__value-text__unit">
|
<div class="c-telemetry-view__label-text">
|
||||||
{{ unit }}
|
{{ domainObject.name }}
|
||||||
</span>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="showValue"
|
||||||
|
:title="fieldName"
|
||||||
|
class="c-telemetry-view__value"
|
||||||
|
:class="[telemetryClass]"
|
||||||
|
>
|
||||||
|
<div class="c-telemetry-view__value-text">
|
||||||
|
{{ telemetryValue }}
|
||||||
|
<span v-if="unit && item.showUnits" class="c-telemetry-view__value-text__unit">
|
||||||
|
{{ unit }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</layout-frame>
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -387,6 +389,12 @@ export default {
|
|||||||
async showToolTip() {
|
async showToolTip() {
|
||||||
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
const { BELOW } = this.openmct.tooltips.TOOLTIP_LOCATIONS;
|
||||||
this.buildToolTip(await this.getObjectPath(), BELOW, 'telemetryViewWrapper');
|
this.buildToolTip(await this.getObjectPath(), BELOW, 'telemetryViewWrapper');
|
||||||
|
},
|
||||||
|
move(gridDelta) {
|
||||||
|
this.$emit('move', gridDelta);
|
||||||
|
},
|
||||||
|
endMove() {
|
||||||
|
this.$emit('endMove');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -25,18 +25,20 @@
|
|||||||
:item="item"
|
:item="item"
|
||||||
:grid-size="gridSize"
|
:grid-size="gridSize"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
@move="move"
|
||||||
@endMove="() => $emit('endMove')"
|
@endMove="endMove"
|
||||||
>
|
>
|
||||||
<div
|
<template #content>
|
||||||
class="c-text-view u-style-receiver js-style-receiver"
|
<div
|
||||||
:data-font-size="item.fontSize"
|
class="c-text-view u-style-receiver js-style-receiver"
|
||||||
:data-font="item.font"
|
:data-font-size="item.fontSize"
|
||||||
:class="[styleClass]"
|
:data-font="item.font"
|
||||||
:style="style"
|
:class="[styleClass]"
|
||||||
>
|
:style="style"
|
||||||
<div class="c-text-view__text">{{ item.text }}</div>
|
>
|
||||||
</div>
|
<div class="c-text-view__text">{{ item.text }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</layout-frame>
|
</layout-frame>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -127,10 +129,18 @@ export default {
|
|||||||
this.initSelect
|
this.initSelect
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
unmounted() {
|
beforeUnmount() {
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
move(gridDelta) {
|
||||||
|
this.$emit('move', gridDelta);
|
||||||
|
},
|
||||||
|
endMove() {
|
||||||
|
this.$emit('endMove');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -20,14 +20,14 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
|
import mount from 'utils/mount';
|
||||||
|
|
||||||
import CopyToClipboardAction from './actions/CopyToClipboardAction';
|
import CopyToClipboardAction from './actions/CopyToClipboardAction';
|
||||||
|
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
|
||||||
import DisplayLayout from './components/DisplayLayout.vue';
|
import DisplayLayout from './components/DisplayLayout.vue';
|
||||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
|
||||||
import DisplayLayoutType from './DisplayLayoutType.js';
|
import DisplayLayoutType from './DisplayLayoutType.js';
|
||||||
import DisplayLayoutDrawingObjectTypes from './DrawingObjectTypes.js';
|
import DisplayLayoutDrawingObjectTypes from './DrawingObjectTypes.js';
|
||||||
import objectUtils from 'objectUtils';
|
|
||||||
import mount from 'utils/mount';
|
|
||||||
|
|
||||||
class DisplayLayoutView {
|
class DisplayLayoutView {
|
||||||
constructor(openmct, domainObject, objectPath, options) {
|
constructor(openmct, domainObject, objectPath, options) {
|
||||||
@ -37,7 +37,6 @@ class DisplayLayoutView {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
this.component = null;
|
this.component = null;
|
||||||
this.app = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show(container, isEditing) {
|
show(container, isEditing) {
|
||||||
@ -51,7 +50,6 @@ class DisplayLayoutView {
|
|||||||
openmct: this.openmct,
|
openmct: this.openmct,
|
||||||
objectPath: this.objectPath,
|
objectPath: this.objectPath,
|
||||||
options: this.options,
|
options: this.options,
|
||||||
objectUtils,
|
|
||||||
currentView: this
|
currentView: this
|
||||||
},
|
},
|
||||||
data: () => {
|
data: () => {
|
||||||
@ -83,20 +81,17 @@ class DisplayLayoutView {
|
|||||||
getSelectionContext() {
|
getSelectionContext() {
|
||||||
return {
|
return {
|
||||||
item: this.domainObject,
|
item: this.domainObject,
|
||||||
supportsMultiSelect: true,
|
supportsMultiSelect: true
|
||||||
addElement: this.component && this.component.$refs.displayLayout.addElement,
|
|
||||||
removeItem: this.component && this.component.$refs.displayLayout.removeItem,
|
|
||||||
orderItem: this.component && this.component.$refs.displayLayout.orderItem,
|
|
||||||
duplicateItem: this.component && this.component.$refs.displayLayout.duplicateItem,
|
|
||||||
switchViewType: this.component && this.component.$refs.displayLayout.switchViewType,
|
|
||||||
mergeMultipleTelemetryViews:
|
|
||||||
this.component && this.component.$refs.displayLayout.mergeMultipleTelemetryViews,
|
|
||||||
mergeMultipleOverlayPlots:
|
|
||||||
this.component && this.component.$refs.displayLayout.mergeMultipleOverlayPlots,
|
|
||||||
toggleGrid: this.component && this.component.$refs.displayLayout.toggleGrid
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contextAction() {
|
||||||
|
const action = arguments[0];
|
||||||
|
if (this.component && this.component.$refs.displayLayout[action]) {
|
||||||
|
this.component.$refs.displayLayout[action](...Array.from(arguments).splice(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onEditModeChange(isEditing) {
|
onEditModeChange(isEditing) {
|
||||||
this.component.isEditing = isEditing;
|
this.component.isEditing = isEditing;
|
||||||
}
|
}
|
||||||
@ -104,6 +99,7 @@ class DisplayLayoutView {
|
|||||||
destroy() {
|
destroy() {
|
||||||
if (this._destroy) {
|
if (this._destroy) {
|
||||||
this._destroy();
|
this._destroy();
|
||||||
|
this.component = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,6 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
let context = {
|
let context = {
|
||||||
item: this.$parent.domainObject,
|
item: this.$parent.domainObject,
|
||||||
addContainer: this.addContainer,
|
|
||||||
type: 'container',
|
type: 'container',
|
||||||
containerId: this.container.id
|
containerId: this.container.id
|
||||||
};
|
};
|
||||||
|
@ -164,16 +164,32 @@ export default {
|
|||||||
this.composition.on('remove', this.removeChildObject);
|
this.composition.on('remove', this.removeChildObject);
|
||||||
this.composition.on('add', this.addFrame);
|
this.composition.on('add', this.addFrame);
|
||||||
this.composition.load();
|
this.composition.load();
|
||||||
this.openmct.objects.observe(this.domainObject, 'configuration.containers', (containers) => {
|
this.unObserveContainers = this.openmct.objects.observe(
|
||||||
this.containers = containers;
|
this.domainObject,
|
||||||
});
|
'configuration.containers',
|
||||||
this.openmct.objects.observe(this.domainObject, 'configuration.rowsLayout', (rowsLayout) => {
|
(containers) => {
|
||||||
this.rowsLayout = rowsLayout;
|
this.containers = containers;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
this.unObserveRowsLayout = this.openmct.objects.observe(
|
||||||
|
this.domainObject,
|
||||||
|
'configuration.rowsLayout',
|
||||||
|
(rowsLayout) => {
|
||||||
|
this.rowsLayout = rowsLayout;
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.composition.off('remove', this.removeChildObject);
|
this.composition.off('remove', this.removeChildObject);
|
||||||
this.composition.off('add', this.addFrame);
|
this.composition.off('add', this.addFrame);
|
||||||
|
|
||||||
|
if (this.unObserveContainers) {
|
||||||
|
this.unObserveContainers();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.unObserveRowsLayout) {
|
||||||
|
this.unObserveRowsLayout();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
containsObject(identifier) {
|
containsObject(identifier) {
|
||||||
|
@ -142,6 +142,9 @@ export default {
|
|||||||
childContext.item = this.domainObject;
|
childContext.item = this.domainObject;
|
||||||
childContext.type = 'frame';
|
childContext.type = 'frame';
|
||||||
childContext.frameId = this.frame.id;
|
childContext.frameId = this.frame.id;
|
||||||
|
if (this.unsubscribeSelection) {
|
||||||
|
this.unsubscribeSelection();
|
||||||
|
}
|
||||||
this.unsubscribeSelection = this.openmct.selection.selectable(
|
this.unsubscribeSelection = this.openmct.selection.selectable(
|
||||||
this.$refs.frame,
|
this.$refs.frame,
|
||||||
childContext,
|
childContext,
|
||||||
|
@ -56,7 +56,7 @@ export default {
|
|||||||
document.addEventListener('dragend', this.unsetDragging);
|
document.addEventListener('dragend', this.unsetDragging);
|
||||||
document.addEventListener('drop', this.unsetDragging);
|
document.addEventListener('drop', this.unsetDragging);
|
||||||
},
|
},
|
||||||
unmounted() {
|
beforeUnmount() {
|
||||||
document.removeEventListener('dragstart', this.setDragging);
|
document.removeEventListener('dragstart', this.setDragging);
|
||||||
document.removeEventListener('dragend', this.unsetDragging);
|
document.removeEventListener('dragend', this.unsetDragging);
|
||||||
document.removeEventListener('drop', this.unsetDragging);
|
document.removeEventListener('drop', this.unsetDragging);
|
||||||
|
@ -77,12 +77,15 @@ export default class FlexibleLayoutViewProvider {
|
|||||||
getSelectionContext() {
|
getSelectionContext() {
|
||||||
return {
|
return {
|
||||||
item: domainObject,
|
item: domainObject,
|
||||||
addContainer: component.$refs.flexibleLayout.addContainer,
|
|
||||||
deleteContainer: component.$refs.flexibleLayout.deleteContainer,
|
|
||||||
deleteFrame: component.$refs.flexibleLayout.deleteFrame,
|
|
||||||
type: 'flexible-layout'
|
type: 'flexible-layout'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
contextAction() {
|
||||||
|
const action = arguments[0];
|
||||||
|
if (component && component.$refs.flexibleLayout[action]) {
|
||||||
|
component.$refs.flexibleLayout[action](...Array.from(arguments).splice(1));
|
||||||
|
}
|
||||||
|
},
|
||||||
onEditModeChange(isEditing) {
|
onEditModeChange(isEditing) {
|
||||||
component.isEditing = isEditing;
|
component.isEditing = isEditing;
|
||||||
},
|
},
|
||||||
|
@ -89,8 +89,6 @@ function ToolbarProvider(openmct) {
|
|||||||
control: 'button',
|
control: 'button',
|
||||||
domainObject: primary.context.item,
|
domainObject: primary.context.item,
|
||||||
method: function () {
|
method: function () {
|
||||||
let deleteFrameAction = tertiary.context.deleteFrame;
|
|
||||||
|
|
||||||
let prompt = openmct.overlays.dialog({
|
let prompt = openmct.overlays.dialog({
|
||||||
iconClass: 'alert',
|
iconClass: 'alert',
|
||||||
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
|
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
|
||||||
@ -99,7 +97,11 @@ function ToolbarProvider(openmct) {
|
|||||||
label: 'OK',
|
label: 'OK',
|
||||||
emphasis: 'true',
|
emphasis: 'true',
|
||||||
callback: function () {
|
callback: function () {
|
||||||
deleteFrameAction(primary.context.frameId);
|
openmct.objectViews.emit(
|
||||||
|
'contextAction',
|
||||||
|
'deleteFrame',
|
||||||
|
primary.context.frameId
|
||||||
|
);
|
||||||
prompt.dismiss();
|
prompt.dismiss();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -136,7 +138,9 @@ function ToolbarProvider(openmct) {
|
|||||||
addContainer = {
|
addContainer = {
|
||||||
control: 'button',
|
control: 'button',
|
||||||
domainObject: tertiary.context.item,
|
domainObject: tertiary.context.item,
|
||||||
method: tertiary.context.addContainer,
|
method: function () {
|
||||||
|
openmct.objectViews.emit('contextAction', 'addContainer', ...arguments);
|
||||||
|
},
|
||||||
key: 'add',
|
key: 'add',
|
||||||
icon: 'icon-plus-in-rect',
|
icon: 'icon-plus-in-rect',
|
||||||
title: 'Add Container'
|
title: 'Add Container'
|
||||||
@ -152,7 +156,6 @@ function ToolbarProvider(openmct) {
|
|||||||
control: 'button',
|
control: 'button',
|
||||||
domainObject: primary.context.item,
|
domainObject: primary.context.item,
|
||||||
method: function () {
|
method: function () {
|
||||||
let removeContainer = secondary.context.deleteContainer;
|
|
||||||
let containerId = primary.context.containerId;
|
let containerId = primary.context.containerId;
|
||||||
|
|
||||||
let prompt = openmct.overlays.dialog({
|
let prompt = openmct.overlays.dialog({
|
||||||
@ -164,7 +167,7 @@ function ToolbarProvider(openmct) {
|
|||||||
label: 'OK',
|
label: 'OK',
|
||||||
emphasis: 'true',
|
emphasis: 'true',
|
||||||
callback: function () {
|
callback: function () {
|
||||||
removeContainer(containerId);
|
openmct.objectViews.emit('contextAction', 'deleteContainer', containerId);
|
||||||
prompt.dismiss();
|
prompt.dismiss();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -185,7 +188,9 @@ function ToolbarProvider(openmct) {
|
|||||||
addContainer = {
|
addContainer = {
|
||||||
control: 'button',
|
control: 'button',
|
||||||
domainObject: secondary.context.item,
|
domainObject: secondary.context.item,
|
||||||
method: secondary.context.addContainer,
|
method: function () {
|
||||||
|
openmct.objectViews.emit('contextAction', 'addContainer', ...arguments);
|
||||||
|
},
|
||||||
key: 'add',
|
key: 'add',
|
||||||
icon: 'icon-plus-in-rect',
|
icon: 'icon-plus-in-rect',
|
||||||
title: 'Add Container'
|
title: 'Add Container'
|
||||||
@ -198,7 +203,9 @@ function ToolbarProvider(openmct) {
|
|||||||
addContainer = {
|
addContainer = {
|
||||||
control: 'button',
|
control: 'button',
|
||||||
domainObject: primary.context.item,
|
domainObject: primary.context.item,
|
||||||
method: primary.context.addContainer,
|
method: function () {
|
||||||
|
openmct.objectViews.emit('contextAction', 'addContainer', ...arguments);
|
||||||
|
},
|
||||||
key: 'add',
|
key: 'add',
|
||||||
icon: 'icon-plus-in-rect',
|
icon: 'icon-plus-in-rect',
|
||||||
title: 'Add Container'
|
title: 'Add Container'
|
||||||
|
@ -727,7 +727,8 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stopListening(this.focusedImageWrapper, 'wheel', this.wheelZoom, this);
|
// remove all eventListeners
|
||||||
|
this.stopListening();
|
||||||
|
|
||||||
Object.keys(this.imageryAnnotations).forEach((time) => {
|
Object.keys(this.imageryAnnotations).forEach((time) => {
|
||||||
const imageAnnotationsForTime = this.imageryAnnotations[time];
|
const imageAnnotationsForTime = this.imageryAnnotations[time];
|
||||||
@ -1276,6 +1277,9 @@ export default {
|
|||||||
this.scrollHandler();
|
this.scrollHandler();
|
||||||
},
|
},
|
||||||
setSizedImageDimensions() {
|
setSizedImageDimensions() {
|
||||||
|
if (!this.$refs.focusedImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.focusedImageNaturalAspectRatio =
|
this.focusedImageNaturalAspectRatio =
|
||||||
this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
|
this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
|
||||||
if (
|
if (
|
||||||
|
@ -27,9 +27,7 @@
|
|||||||
:class="iconClass"
|
:class="iconClass"
|
||||||
:title="title"
|
:title="title"
|
||||||
@click="toggleMenu"
|
@click="toggleMenu"
|
||||||
>
|
/>
|
||||||
<span class="c-button__label"></span>
|
|
||||||
</button>
|
|
||||||
<div v-show="showMenu" class="c-switcher-menu__content">
|
<div v-show="showMenu" class="c-switcher-menu__content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<select class="c-inspector__data-pivot-range-selector" v-model="selectedDataRangeIndex">
|
<select v-model="selectedDataRangeIndex" class="c-inspector__data-pivot-range-selector">
|
||||||
<option
|
<option
|
||||||
v-for="(dataRange, index) in descendingDataRanges"
|
v-for="(dataRange, index) in descendingDataRanges"
|
||||||
:key="index"
|
:key="index"
|
||||||
@ -78,8 +78,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import NumericData from './NumericData.vue';
|
|
||||||
import Imagery from './Imagery.vue';
|
import Imagery from './Imagery.vue';
|
||||||
|
import NumericData from './NumericData.vue';
|
||||||
|
|
||||||
const TIMESTAMP_VIEW_BUFFER = 30 * 1000;
|
const TIMESTAMP_VIEW_BUFFER = 30 * 1000;
|
||||||
const timestampBufferText = `${TIMESTAMP_VIEW_BUFFER / 1000} seconds`;
|
const timestampBufferText = `${TIMESTAMP_VIEW_BUFFER / 1000} seconds`;
|
||||||
|
@ -34,8 +34,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import mount from 'utils/mount';
|
import mount from 'utils/mount';
|
||||||
import TelemetryFrame from './TelemetryFrame.vue';
|
|
||||||
import Plot from '../plot/Plot.vue';
|
import Plot from '../plot/Plot.vue';
|
||||||
|
import TelemetryFrame from './TelemetryFrame.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject', 'timeFormatter'],
|
inject: ['openmct', 'domainObject', 'timeFormatter'],
|
||||||
|
@ -42,6 +42,10 @@ export default {
|
|||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
this.openmct.selection.off('change', this.updateSelection);
|
this.openmct.selection.off('change', this.updateSelection);
|
||||||
|
if (this.destroy) {
|
||||||
|
this.destroy();
|
||||||
|
this.$el.innerHTML = '';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateSelection(selection) {
|
updateSelection(selection) {
|
||||||
|
@ -55,7 +55,7 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.openmct.editor.on('isEditing', this.setEditMode);
|
this.openmct.editor.on('isEditing', this.setEditMode);
|
||||||
},
|
},
|
||||||
beforeUnmounted() {
|
beforeUnmount() {
|
||||||
this.openmct.editor.off('isEditing', this.setEditMode);
|
this.openmct.editor.off('isEditing', this.setEditMode);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -293,7 +293,7 @@ export default {
|
|||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
if (this.embedsWrapperResizeObserver) {
|
if (this.embedsWrapperResizeObserver) {
|
||||||
this.embedsWrapperResizeObserver.unobserve(this.$refs.embedsWrapper);
|
this.embedsWrapperResizeObserver.disconnect();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -395,11 +395,7 @@ export default {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.openmct.objectViews.on('clearData', this.clearData);
|
this.openmct.objectViews.on('clearData', this.clearData);
|
||||||
this.$on('loadingComplete', () => {
|
this.$on('loadingComplete', this.loadAnnotationsIfAllowed);
|
||||||
if (this.annotationViewingAndEditingAllowed) {
|
|
||||||
this.loadAnnotations();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.openmct.selection.on('change', this.updateSelection);
|
this.openmct.selection.on('change', this.updateSelection);
|
||||||
this.yAxisListWithRange = [this.config.yAxis, ...this.config.additionalYAxes];
|
this.yAxisListWithRange = [this.config.yAxis, ...this.config.additionalYAxes];
|
||||||
|
|
||||||
@ -414,6 +410,7 @@ export default {
|
|||||||
document.removeEventListener('keydown', this.handleKeyDown);
|
document.removeEventListener('keydown', this.handleKeyDown);
|
||||||
document.removeEventListener('keyup', this.handleKeyUp);
|
document.removeEventListener('keyup', this.handleKeyUp);
|
||||||
document.body.removeEventListener('click', this.cancelSelection);
|
document.body.removeEventListener('click', this.cancelSelection);
|
||||||
|
this.$off('loadingComplete', this.loadAnnotationsIfAllowed);
|
||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -468,9 +465,7 @@ export default {
|
|||||||
const currentXaxis = this.config.xAxis.get('displayRange');
|
const currentXaxis = this.config.xAxis.get('displayRange');
|
||||||
const currentYaxis = this.config.yAxis.get('displayRange');
|
const currentYaxis = this.config.yAxis.get('displayRange');
|
||||||
if (!currentXaxis || !currentYaxis) {
|
if (!currentXaxis || !currentYaxis) {
|
||||||
this.$once('loadingComplete', () => {
|
this.$once('loadingComplete', resolve);
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@ -566,31 +561,10 @@ export default {
|
|||||||
const yAxisId = series.get('yAxisId');
|
const yAxisId = series.get('yAxisId');
|
||||||
this.updateAxisUsageCount(yAxisId, 1);
|
this.updateAxisUsageCount(yAxisId, 1);
|
||||||
this.seriesModels[index] = series;
|
this.seriesModels[index] = series;
|
||||||
this.listenTo(
|
this.listenTo(series, 'change:xKey', this.setDisplayRange.bind(this, series), this);
|
||||||
series,
|
this.listenTo(series, 'change:yKey', this.loadSeriesData.bind(this, series), this);
|
||||||
'change:xKey',
|
|
||||||
(xKey) => {
|
|
||||||
this.setDisplayRange(series, xKey);
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
this.listenTo(
|
|
||||||
series,
|
|
||||||
'change:yKey',
|
|
||||||
() => {
|
|
||||||
this.loadSeriesData(series);
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
this.listenTo(
|
this.listenTo(series, 'change:interpolate', this.loadSeriesData.bind(this, series), this);
|
||||||
series,
|
|
||||||
'change:interpolate',
|
|
||||||
() => {
|
|
||||||
this.loadSeriesData(series);
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
this.listenTo(series, 'change:yAxisId', this.updateTicksAndSeriesForYAxis, this);
|
this.listenTo(series, 'change:yAxisId', this.updateTicksAndSeriesForYAxis, this);
|
||||||
|
|
||||||
this.loadSeriesData(series);
|
this.loadSeriesData(series);
|
||||||
@ -622,6 +596,11 @@ export default {
|
|||||||
foundYAxis.seriesCount = foundYAxis.seriesCount + updateCountBy;
|
foundYAxis.seriesCount = foundYAxis.seriesCount + updateCountBy;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
loadAnnotationsIfAllowed() {
|
||||||
|
if (this.annotationViewingAndEditingAllowed) {
|
||||||
|
this.loadAnnotations();
|
||||||
|
}
|
||||||
|
},
|
||||||
async loadAnnotations() {
|
async loadAnnotations() {
|
||||||
if (!this.openmct.annotation.getAvailableTags().length) {
|
if (!this.openmct.annotation.getAvailableTags().length) {
|
||||||
// don't bother loading annotations if there are no tags
|
// don't bother loading annotations if there are no tags
|
||||||
@ -993,9 +972,6 @@ export default {
|
|||||||
|
|
||||||
this.config.yAxisLabel = this.config.yAxis.get('label');
|
this.config.yAxisLabel = this.config.yAxis.get('label');
|
||||||
|
|
||||||
this.cursorGuideVertical = this.$refs.cursorGuideVertical;
|
|
||||||
this.cursorGuideHorizontal = this.$refs.cursorGuideHorizontal;
|
|
||||||
|
|
||||||
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
|
||||||
this.yAxisListWithRange.forEach((yAxis) => {
|
this.yAxisListWithRange.forEach((yAxis) => {
|
||||||
this.listenTo(yAxis, 'change:displayRange', this.onYAxisChange.bind(this, yAxis.id), this);
|
this.listenTo(yAxis, 'change:displayRange', this.onYAxisChange.bind(this, yAxis.id), this);
|
||||||
@ -1123,8 +1099,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateCrosshairs(event) {
|
updateCrosshairs(event) {
|
||||||
this.cursorGuideVertical.style.left = event.clientX - this.chartElementBounds.x + 'px';
|
this.$refs.cursorGuideVertical.style.left = event.clientX - this.chartElementBounds.x + 'px';
|
||||||
this.cursorGuideHorizontal.style.top = event.clientY - this.chartElementBounds.y + 'px';
|
this.$refs.cursorGuideHorizontal.style.top = event.clientY - this.chartElementBounds.y + 'px';
|
||||||
},
|
},
|
||||||
|
|
||||||
trackChartElementBounds(event) {
|
trackChartElementBounds(event) {
|
||||||
@ -1891,6 +1867,10 @@ export default {
|
|||||||
configStore.deleteStore(this.config.id);
|
configStore.deleteStore(this.config.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.config = {};
|
||||||
|
this.canvas = undefined;
|
||||||
|
this.abortController = undefined;
|
||||||
|
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
|
|
||||||
if (this.checkForSize) {
|
if (this.checkForSize) {
|
||||||
|
@ -178,7 +178,7 @@ export default {
|
|||||||
this.stalenessSubscription = {};
|
this.stalenessSubscription = {};
|
||||||
this.loadComposition();
|
this.loadComposition();
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
unmounted() {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -258,6 +258,7 @@ export default {
|
|||||||
this.compositionCollection.off('remove', this.removeItem);
|
this.compositionCollection.off('remove', this.removeItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.imageExporter = null;
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
},
|
},
|
||||||
exportJPG() {
|
exportJPG() {
|
||||||
|
@ -26,7 +26,20 @@
|
|||||||
<div class="gl-plot-chart-area">
|
<div class="gl-plot-chart-area">
|
||||||
<span v-html="canvasTemplate"></span>
|
<span v-html="canvasTemplate"></span>
|
||||||
<span v-html="canvasTemplate"></span>
|
<span v-html="canvasTemplate"></span>
|
||||||
<div ref="limitArea" class="js-limit-area"></div>
|
<div ref="limitArea" class="js-limit-area">
|
||||||
|
<limit-label
|
||||||
|
v-for="(limitLabel, index) in visibleLimitLabels"
|
||||||
|
:key="index"
|
||||||
|
:point="limitLabel.point"
|
||||||
|
:limit="limitLabel.limit"
|
||||||
|
></limit-label>
|
||||||
|
<limit-line
|
||||||
|
v-for="(limitLine, index) in visibleLimitLines"
|
||||||
|
:key="index"
|
||||||
|
:point="limitLine.point"
|
||||||
|
:limit="limitLine.limit"
|
||||||
|
></limit-line>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -86,6 +99,7 @@ const HANDLED_ATTRIBUTES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { LimitLine, LimitLabel },
|
||||||
inject: ['openmct', 'domainObject', 'path'],
|
inject: ['openmct', 'domainObject', 'path'],
|
||||||
props: {
|
props: {
|
||||||
rectangles: {
|
rectangles: {
|
||||||
@ -132,7 +146,9 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
canvasTemplate:
|
canvasTemplate:
|
||||||
'<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>'
|
'<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>',
|
||||||
|
visibleLimitLabels: [],
|
||||||
|
visibleLimitLines: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -392,6 +408,8 @@ export default {
|
|||||||
this.stopListening();
|
this.stopListening();
|
||||||
this.lines.forEach((line) => line.destroy());
|
this.lines.forEach((line) => line.destroy());
|
||||||
this.limitLines.forEach((line) => line.destroy());
|
this.limitLines.forEach((line) => line.destroy());
|
||||||
|
this.pointSets.forEach((pointSet) => pointSet.destroy());
|
||||||
|
this.alarmSets.forEach((alarmSet) => alarmSet.destroy());
|
||||||
DrawLoader.releaseDrawAPI(this.drawAPI);
|
DrawLoader.releaseDrawAPI(this.drawAPI);
|
||||||
},
|
},
|
||||||
resetYOffsetAndSeriesDataForYAxis(yAxisId) {
|
resetYOffsetAndSeriesDataForYAxis(yAxisId) {
|
||||||
@ -699,7 +717,6 @@ export default {
|
|||||||
alarmSets.forEach(this.drawAlarmPoints, this);
|
alarmSets.forEach(this.drawAlarmPoints, this);
|
||||||
},
|
},
|
||||||
updateLimitLines() {
|
updateLimitLines() {
|
||||||
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
|
|
||||||
this.config.series.models.forEach((series) => {
|
this.config.series.models.forEach((series) => {
|
||||||
const yAxisId = series.get('yAxisId');
|
const yAxisId = series.get('yAxisId');
|
||||||
|
|
||||||
@ -720,8 +737,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let limitPointOverlap = [];
|
let limitPointOverlap = [];
|
||||||
|
//reset
|
||||||
|
this.visibleLimitLabels = [];
|
||||||
|
this.visibleLimitLines = [];
|
||||||
|
|
||||||
this.limitLines.forEach((limitLine) => {
|
this.limitLines.forEach((limitLine) => {
|
||||||
let limitContainerEl = this.$refs.limitArea;
|
|
||||||
limitLine.limits.forEach((limit) => {
|
limitLine.limits.forEach((limit) => {
|
||||||
if (series.keyString !== limit.seriesKey) {
|
if (series.keyString !== limit.seriesKey) {
|
||||||
return;
|
return;
|
||||||
@ -731,31 +751,43 @@ export default {
|
|||||||
if (showLabels) {
|
if (showLabels) {
|
||||||
const overlap = this.getLimitOverlap(limit, limitPointOverlap);
|
const overlap = this.getLimitOverlap(limit, limitPointOverlap);
|
||||||
limitPointOverlap.push(overlap);
|
limitPointOverlap.push(overlap);
|
||||||
let limitLabelEl = this.getLimitLabel(limit, overlap);
|
this.visibleLimitLabels.push(this.getLimitProps(limit, overlap));
|
||||||
limitContainerEl.appendChild(limitLabelEl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let limitEl = this.getLimitElement(limit);
|
this.visibleLimitLines.push(this.getLimitElementProps(limit));
|
||||||
limitContainerEl.appendChild(limitEl);
|
|
||||||
}, this);
|
}, this);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
showLabels(seriesKey) {
|
showLabels(seriesKey) {
|
||||||
return this.showLimitLineLabels?.seriesKey === seriesKey;
|
return this.showLimitLineLabels?.seriesKey === seriesKey;
|
||||||
},
|
},
|
||||||
|
getLimitElementProps(limit) {
|
||||||
|
let point = {
|
||||||
|
left: 0,
|
||||||
|
top: this.drawAPI.y(limit.point.y)
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
point,
|
||||||
|
limit
|
||||||
|
};
|
||||||
|
},
|
||||||
getLimitElement(limit) {
|
getLimitElement(limit) {
|
||||||
let point = {
|
let point = {
|
||||||
left: 0,
|
left: 0,
|
||||||
top: this.drawAPI.y(limit.point.y)
|
top: this.drawAPI.y(limit.point.y)
|
||||||
};
|
};
|
||||||
const { vNode } = mount(LimitLine, {
|
const { vNode, destroy } = mount(LimitLine, {
|
||||||
props: {
|
props: {
|
||||||
point,
|
point,
|
||||||
limit
|
limit
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return vNode.el;
|
return {
|
||||||
|
el: vNode.el,
|
||||||
|
destroy
|
||||||
|
};
|
||||||
},
|
},
|
||||||
getLimitOverlap(limit, overlapMap) {
|
getLimitOverlap(limit, overlapMap) {
|
||||||
//calculate if limit lines are too close to each other
|
//calculate if limit lines are too close to each other
|
||||||
@ -786,19 +818,32 @@ export default {
|
|||||||
overlapTop: limitTop
|
overlapTop: limitTop
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getLimitProps(limit, overlap) {
|
||||||
|
let point = {
|
||||||
|
left: 0,
|
||||||
|
top: this.drawAPI.y(limit.point.y)
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
limit: Object.assign({}, overlap, limit),
|
||||||
|
point
|
||||||
|
};
|
||||||
|
},
|
||||||
getLimitLabel(limit, overlap) {
|
getLimitLabel(limit, overlap) {
|
||||||
let point = {
|
let point = {
|
||||||
left: 0,
|
left: 0,
|
||||||
top: this.drawAPI.y(limit.point.y)
|
top: this.drawAPI.y(limit.point.y)
|
||||||
};
|
};
|
||||||
const { vNode } = mount(LimitLabel, {
|
const { vNode, destroy } = mount(LimitLabel, {
|
||||||
props: {
|
props: {
|
||||||
limit: Object.assign({}, overlap, limit),
|
limit: Object.assign({}, overlap, limit),
|
||||||
point
|
point
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return vNode.el;
|
return {
|
||||||
|
el: vNode.el,
|
||||||
|
destroy
|
||||||
|
};
|
||||||
},
|
},
|
||||||
drawAlarmPoints(alarmSet) {
|
drawAlarmPoints(alarmSet) {
|
||||||
this.drawAPI.drawLimitPoints(
|
this.drawAPI.drawLimitPoints(
|
||||||
|
@ -59,4 +59,8 @@ export default class LegendModel extends Model {
|
|||||||
showLegendsForChildren: true
|
showLegendsForChildren: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.stopListening();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,8 +168,10 @@ export default class PlotConfigurationModel extends Model {
|
|||||||
onDestroy() {
|
onDestroy() {
|
||||||
this.xAxis.destroy();
|
this.xAxis.destroy();
|
||||||
this.yAxis.destroy();
|
this.yAxis.destroy();
|
||||||
|
this.additionalYAxes.forEach((additionalYAxis) => additionalYAxis.destroy());
|
||||||
this.series.destroy();
|
this.series.destroy();
|
||||||
this.legend.destroy();
|
this.legend.destroy();
|
||||||
|
this.stopListening();
|
||||||
if (this.removeMutationListener) {
|
if (this.removeMutationListener) {
|
||||||
this.removeMutationListener();
|
this.removeMutationListener();
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,9 @@ export default class PlotSeries extends Model {
|
|||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
|
//this triggers Model.destroy which in turn triggers destroy methods for other classes.
|
||||||
super.destroy();
|
super.destroy();
|
||||||
|
this.stopListening();
|
||||||
this.openmct.time.off('bounds', this.updateLimits);
|
this.openmct.time.off('bounds', this.updateLimits);
|
||||||
|
|
||||||
if (this.unsubscribe) {
|
if (this.unsubscribe) {
|
||||||
@ -148,6 +150,8 @@ export default class PlotSeries extends Model {
|
|||||||
if (this.removeMutationListener) {
|
if (this.removeMutationListener) {
|
||||||
this.removeMutationListener();
|
this.removeMutationListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configStore.deleteStore(this.dataStoreId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,6 +186,12 @@ export default class SeriesCollection extends Collection {
|
|||||||
);
|
);
|
||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
super.destroy();
|
||||||
|
this.plot = undefined;
|
||||||
|
this.stopListening();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,6 +111,11 @@ export default class XAxisModel extends Model {
|
|||||||
|
|
||||||
return defaultModel;
|
return defaultModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.plot = undefined;
|
||||||
|
this.stopListening();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @typedef {any} TODO */
|
/** @typedef {any} TODO */
|
||||||
|
@ -384,6 +384,11 @@ export default class YAxisModel extends Model {
|
|||||||
range: options.model?.range
|
range: options.model?.range
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.plot = undefined;
|
||||||
|
this.stopListening();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @typedef {any} TODO */
|
/** @typedef {any} TODO */
|
||||||
|
@ -151,6 +151,8 @@ DrawWebGL.prototype.initContext = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
DrawWebGL.prototype.destroy = function () {
|
DrawWebGL.prototype.destroy = function () {
|
||||||
|
this.canvas = undefined;
|
||||||
|
this.overlay = undefined;
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -212,6 +212,8 @@ export default {
|
|||||||
this.composition.off('reorder', this.compositionReorder);
|
this.composition.off('reorder', this.compositionReorder);
|
||||||
|
|
||||||
this.stopListening();
|
this.stopListening();
|
||||||
|
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
configStore.deleteStore(configId);
|
||||||
},
|
},
|
||||||
|
|
||||||
addChild(child) {
|
addChild(child) {
|
||||||
|
@ -133,6 +133,9 @@ export default {
|
|||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const configId = this.openmct.objects.makeKeyString(this.childObject.identifier);
|
||||||
|
configStore.deleteStore(configId);
|
||||||
|
|
||||||
if (this._destroy) {
|
if (this._destroy) {
|
||||||
this._destroy();
|
this._destroy();
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,24 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2023, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
define([
|
define([
|
||||||
'../res/conditionTemplate.html',
|
'../res/conditionTemplate.html',
|
||||||
'./input/ObjectSelect',
|
'./input/ObjectSelect',
|
||||||
@ -42,9 +63,6 @@ define([
|
|||||||
this.selects = {};
|
this.selects = {};
|
||||||
this.valueInputs = [];
|
this.valueInputs = [];
|
||||||
|
|
||||||
this.remove = this.remove.bind(this);
|
|
||||||
this.duplicate = this.duplicate.bind(this);
|
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,6 +83,9 @@ define([
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.handleObjectChange = (value) => onSelectChange(value, 'object');
|
||||||
|
this.handleKeyChange = (value) => onSelectChange(value, 'key');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for this conditions value inputs
|
* Event handler for this conditions value inputs
|
||||||
* @param {Event} event The oninput event that triggered this callback
|
* @param {Event} event The oninput event that triggered this callback
|
||||||
@ -99,12 +120,8 @@ define([
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.selects.object.on('change', function (value) {
|
this.selects.object.on('change', this.handleObjectChange);
|
||||||
onSelectChange(value, 'object');
|
this.selects.key.on('change', this.handleKeyChange);
|
||||||
});
|
|
||||||
this.selects.key.on('change', function (value) {
|
|
||||||
onSelectChange(value, 'key');
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.values(this.selects).forEach(function (select) {
|
Object.values(this.selects).forEach(function (select) {
|
||||||
self.domElement.querySelector('.t-configuration').append(select.getDOM());
|
self.domElement.querySelector('.t-configuration').append(select.getDOM());
|
||||||
@ -143,6 +160,8 @@ define([
|
|||||||
* remove callbacks
|
* remove callbacks
|
||||||
*/
|
*/
|
||||||
Condition.prototype.remove = function () {
|
Condition.prototype.remove = function () {
|
||||||
|
this.selects.object.off('change', this.handleObjectChange);
|
||||||
|
this.selects.key.off('change', this.handleKeyChange);
|
||||||
this.eventEmitter.emit('remove', this.index);
|
this.eventEmitter.emit('remove', this.index);
|
||||||
this.destroy();
|
this.destroy();
|
||||||
};
|
};
|
||||||
|
@ -61,6 +61,20 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a callback from this select.
|
||||||
|
* @param {string} event The key for the event to stop listening to
|
||||||
|
* @param {function} callback The function to unregister
|
||||||
|
* @param {Object} context A reference to a scope to use as the context for the callback function
|
||||||
|
*/
|
||||||
|
Select.prototype.off = function (event, callback, context) {
|
||||||
|
if (this.supportedCallbacks.includes(event)) {
|
||||||
|
this.eventEmitter.off(event, callback, context || this);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported event type: ' + event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the select element in the view from the current state of the data
|
* Update the select element in the view from the current state of the data
|
||||||
* model
|
* model
|
||||||
|
@ -160,7 +160,8 @@ export default {
|
|||||||
this.updateInternalDomainObject
|
this.updateInternalDomainObject
|
||||||
);
|
);
|
||||||
|
|
||||||
this.openmct.router.on('change:params', this.updateCurrentTab.bind(this));
|
this.updateCurrentTab = this.updateCurrentTab.bind(this);
|
||||||
|
this.openmct.router.on('change:params', this.updateCurrentTab);
|
||||||
|
|
||||||
this.RemoveAction = new RemoveAction(this.openmct);
|
this.RemoveAction = new RemoveAction(this.openmct);
|
||||||
document.addEventListener('dragstart', this.dragstart);
|
document.addEventListener('dragstart', this.dragstart);
|
||||||
@ -183,7 +184,7 @@ export default {
|
|||||||
this.unsubscribe();
|
this.unsubscribe();
|
||||||
this.clearCurrentTabIndexFromURL();
|
this.clearCurrentTabIndexFromURL();
|
||||||
|
|
||||||
this.openmct.router.off('change:params', this.updateCurrentTab.bind(this));
|
this.openmct.router.off('change:params', this.updateCurrentTab);
|
||||||
|
|
||||||
document.removeEventListener('dragstart', this.dragstart);
|
document.removeEventListener('dragstart', this.dragstart);
|
||||||
document.removeEventListener('dragend', this.dragend);
|
document.removeEventListener('dragend', this.dragend);
|
||||||
|
@ -90,6 +90,10 @@ export default {
|
|||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.removeStatusListener) {
|
||||||
|
this.removeStatusListener();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.mutablePromise) {
|
if (this.mutablePromise) {
|
||||||
this.mutablePromise.then(() => {
|
this.mutablePromise.then(() => {
|
||||||
this.openmct.objects.destroyMutable(this.domainObject);
|
this.openmct.objects.destroyMutable(this.domainObject);
|
||||||
|
@ -208,13 +208,13 @@ export default {
|
|||||||
|
|
||||||
this.actionCollection = actionCollection;
|
this.actionCollection = actionCollection;
|
||||||
this.actionCollection.on('update', this.updateActionItems);
|
this.actionCollection.on('update', this.updateActionItems);
|
||||||
this.updateActionItems(this.actionCollection.applicableActions);
|
this.updateActionItems();
|
||||||
},
|
},
|
||||||
unlistenToActionCollection() {
|
unlistenToActionCollection() {
|
||||||
this.actionCollection.off('update', this.updateActionItems);
|
this.actionCollection.off('update', this.updateActionItems);
|
||||||
delete this.actionCollection;
|
delete this.actionCollection;
|
||||||
},
|
},
|
||||||
updateActionItems(actionItems) {
|
updateActionItems() {
|
||||||
const statusBarItems = this.actionCollection.getStatusBarActions();
|
const statusBarItems = this.actionCollection.getStatusBarActions();
|
||||||
this.statusBarItems = this.openmct.menus.actionsToMenuItems(
|
this.statusBarItems = this.openmct.menus.actionsToMenuItems(
|
||||||
statusBarItems,
|
statusBarItems,
|
||||||
|
@ -90,7 +90,7 @@ export default {
|
|||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unmounted() {
|
beforeUnmount() {
|
||||||
this.clear();
|
this.clear();
|
||||||
if (this.releaseEditModeHandler) {
|
if (this.releaseEditModeHandler) {
|
||||||
this.releaseEditModeHandler();
|
this.releaseEditModeHandler();
|
||||||
@ -113,6 +113,13 @@ export default {
|
|||||||
this.actionCollection.destroy();
|
this.actionCollection.destroy();
|
||||||
delete this.actionCollection;
|
delete this.actionCollection;
|
||||||
}
|
}
|
||||||
|
this.$refs.objectViewWrapper.removeEventListener('dragover', this.onDragOver, {
|
||||||
|
capture: true
|
||||||
|
});
|
||||||
|
this.$refs.objectViewWrapper.removeEventListener('drop', this.editIfEditable, {
|
||||||
|
capture: true
|
||||||
|
});
|
||||||
|
this.$refs.objectViewWrapper.removeEventListener('drop', this.addObjectToParent);
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.debounceUpdateView = _.debounce(this.updateView, 10);
|
this.debounceUpdateView = _.debounce(this.updateView, 10);
|
||||||
@ -136,6 +143,7 @@ export default {
|
|||||||
clear() {
|
clear() {
|
||||||
if (this.currentView) {
|
if (this.currentView) {
|
||||||
this.currentView.destroy();
|
this.currentView.destroy();
|
||||||
|
|
||||||
if (this.$refs.objectViewWrapper) {
|
if (this.$refs.objectViewWrapper) {
|
||||||
this.$refs.objectViewWrapper.innerHTML = '';
|
this.$refs.objectViewWrapper.innerHTML = '';
|
||||||
}
|
}
|
||||||
@ -167,6 +175,7 @@ export default {
|
|||||||
this.triggerUnsubscribeFromStaleness();
|
this.triggerUnsubscribeFromStaleness();
|
||||||
|
|
||||||
this.openmct.objectViews.off('clearData', this.clearData);
|
this.openmct.objectViews.off('clearData', this.clearData);
|
||||||
|
this.openmct.objectViews.off('contextAction', this.performContextAction);
|
||||||
},
|
},
|
||||||
getStyleReceiver() {
|
getStyleReceiver() {
|
||||||
let styleReceiver;
|
let styleReceiver;
|
||||||
@ -287,6 +296,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.objectViews.on('clearData', this.clearData);
|
this.openmct.objectViews.on('clearData', this.clearData);
|
||||||
|
this.openmct.objectViews.on('contextAction', this.performContextAction);
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.updateStyle(this.styleRuleManager?.currentStyle);
|
this.updateStyle(this.styleRuleManager?.currentStyle);
|
||||||
@ -309,10 +319,6 @@ export default {
|
|||||||
show(object, viewKey, immediatelySelect, currentObjectPath) {
|
show(object, viewKey, immediatelySelect, currentObjectPath) {
|
||||||
this.updateStyle();
|
this.updateStyle();
|
||||||
|
|
||||||
if (this.unlisten) {
|
|
||||||
this.unlisten();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
delete this.removeSelectable;
|
delete this.removeSelectable;
|
||||||
@ -461,6 +467,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
performContextAction() {
|
||||||
|
if (this.currentView.contextAction) {
|
||||||
|
this.currentView.contextAction(...arguments);
|
||||||
|
}
|
||||||
|
},
|
||||||
isEditingAllowed() {
|
isEditingAllowed() {
|
||||||
let browseObject = this.openmct.layout.$refs.browseObject.domainObject;
|
let browseObject = this.openmct.layout.$refs.browseObject.domainObject;
|
||||||
let objectPath = this.currentObjectPath || this.objectPath;
|
let objectPath = this.currentObjectPath || this.objectPath;
|
||||||
|
@ -105,8 +105,9 @@ export default {
|
|||||||
this.styleProp = this.type === 'horizontal' ? 'width' : 'height';
|
this.styleProp = this.type === 'horizontal' ? 'width' : 'height';
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
this.handleHideUrl = this.handleHideUrl.bind(this);
|
||||||
// Hide tree and/or inspector pane if specified in URL
|
// Hide tree and/or inspector pane if specified in URL
|
||||||
this.openmct.router.on('change:params', this.handleHideUrl.bind(this));
|
this.openmct.router.on('change:params', this.handleHideUrl);
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (this.persistPosition) {
|
if (this.persistPosition) {
|
||||||
@ -121,6 +122,9 @@ export default {
|
|||||||
this.handleHideUrl();
|
this.handleHideUrl();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
this.openmct.router.off('change:params', this.handleHideUrl);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addHideParam(target) {
|
addHideParam(target) {
|
||||||
this.openmct.router.setSearchParam(target, 'true');
|
this.openmct.router.setSearchParam(target, 'true');
|
||||||
|
@ -16,6 +16,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.unobserveObjects = {};
|
||||||
//TODO: touch support
|
//TODO: touch support
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.root.addEventListener('contextmenu', this.showContextMenu);
|
this.$refs.root.addEventListener('contextmenu', this.showContextMenu);
|
||||||
@ -29,19 +30,24 @@ export default {
|
|||||||
|
|
||||||
this.objectPath.forEach((object) => {
|
this.objectPath.forEach((object) => {
|
||||||
if (object) {
|
if (object) {
|
||||||
const unobserve = this.openmct.objects.observe(
|
const key = this.openmct.objects.makeKeyString(object.identifier);
|
||||||
|
this.unobserveObjects[key] = this.openmct.objects.observe(
|
||||||
object,
|
object,
|
||||||
'*',
|
'*',
|
||||||
updateObject.bind(this, object)
|
updateObject.bind(this, object)
|
||||||
);
|
);
|
||||||
this.$once('hook:unmounted', unobserve);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
this.removeListeners();
|
||||||
this.$refs.root.removeEventListener('contextMenu', this.showContextMenu);
|
this.$refs.root.removeEventListener('contextMenu', this.showContextMenu);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
removeListeners() {
|
||||||
|
Object.values(this.unobserveObjects).forEach((unobserve) => unobserve());
|
||||||
|
this.unobserveObjects = {};
|
||||||
|
},
|
||||||
showContextMenu(event) {
|
showContextMenu(event) {
|
||||||
if (this.readOnly) {
|
if (this.readOnly) {
|
||||||
return;
|
return;
|
||||||
|
@ -89,7 +89,7 @@ export default {
|
|||||||
// for edit mode changes and update toolbars if necessary.
|
// for edit mode changes and update toolbars if necessary.
|
||||||
this.openmct.editor.on('isEditing', this.handleEditing);
|
this.openmct.editor.on('isEditing', this.handleEditing);
|
||||||
},
|
},
|
||||||
unmounted() {
|
beforeUnmount() {
|
||||||
this.openmct.selection.off('change', this.handleSelection);
|
this.openmct.selection.off('change', this.handleSelection);
|
||||||
this.openmct.editor.off('isEditing', this.handleEditing);
|
this.openmct.editor.off('isEditing', this.handleEditing);
|
||||||
this.removeListeners();
|
this.removeListeners();
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
import { h, render } from 'vue';
|
import { createApp, defineComponent } from 'vue';
|
||||||
|
|
||||||
export default function mount(component, { props, children, element, app } = {}) {
|
export default function mount(component, { props, children, element } = {}) {
|
||||||
let el = element;
|
let el = element;
|
||||||
|
if (!el) {
|
||||||
let vNode = h(component, props, children);
|
el = document.createElement('div');
|
||||||
if (app && app._context) {
|
|
||||||
vNode.appContext = app._context;
|
|
||||||
}
|
|
||||||
if (el) {
|
|
||||||
render(vNode, el);
|
|
||||||
} else if (typeof document !== 'undefined') {
|
|
||||||
render(vNode, (el = document.createElement('div')));
|
|
||||||
}
|
}
|
||||||
|
let vueComponent = defineComponent(component);
|
||||||
|
let app = createApp(vueComponent);
|
||||||
|
let mountedComponentInstance = app.mount(el);
|
||||||
|
|
||||||
// eslint-disable-next-line func-style
|
// eslint-disable-next-line func-style
|
||||||
const destroy = () => {
|
const destroy = () => {
|
||||||
if (el) {
|
app.unmount();
|
||||||
render(null, el);
|
|
||||||
}
|
|
||||||
el = null;
|
|
||||||
vNode = null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return { vNode, destroy, el };
|
return {
|
||||||
|
vNode: {
|
||||||
|
componentInstance: mountedComponentInstance,
|
||||||
|
el: mountedComponentInstance.$el
|
||||||
|
},
|
||||||
|
destroy,
|
||||||
|
el
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user