Compare commits
11 Commits
watch-plot
...
docs-relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45cbf514f2 | ||
|
|
8090e71110 | ||
|
|
dc9bd8bcb1 | ||
|
|
29d83e9c6d | ||
|
|
6393a77c19 | ||
|
|
6bbabf9c45 | ||
|
|
f18d1d2a51 | ||
|
|
5894c66df1 | ||
|
|
6ff8c42041 | ||
|
|
9870a6bc9c | ||
|
|
317ea8c275 |
@@ -160,6 +160,31 @@ jobs:
|
||||
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
|
||||
steps:
|
||||
- generate_and_store_version_and_filesystem_artifacts
|
||||
e2e-mobile:
|
||||
executor: pw-focal-development
|
||||
steps:
|
||||
- build_and_install:
|
||||
node-version: lts/hydrogen
|
||||
- run: npm run test:e2e:mobile
|
||||
- when:
|
||||
condition:
|
||||
equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
|
||||
steps:
|
||||
- generate_e2e_code_cov_report:
|
||||
suite: full
|
||||
- store_test_results:
|
||||
path: test-results/results.xml
|
||||
- store_artifacts:
|
||||
path: test-results
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
- store_artifacts:
|
||||
path: html-test-results
|
||||
- when:
|
||||
condition:
|
||||
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
|
||||
steps:
|
||||
- generate_and_store_version_and_filesystem_artifacts
|
||||
e2e-couchdb:
|
||||
executor: ubuntu
|
||||
steps:
|
||||
@@ -260,8 +285,9 @@ workflows:
|
||||
- e2e-test:
|
||||
name: e2e-stable
|
||||
suite: stable
|
||||
- e2e-mobile
|
||||
- visual-a11y-tests:
|
||||
name: visual-test-ci
|
||||
name: visual-a11y-test-ci
|
||||
suite: ci
|
||||
|
||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||
@@ -277,10 +303,11 @@ workflows:
|
||||
- e2e-test:
|
||||
name: e2e-full-nightly
|
||||
suite: full
|
||||
- mem-test
|
||||
- e2e-mobile
|
||||
- perf-test
|
||||
- mem-test
|
||||
- visual-a11y-tests:
|
||||
name: visual-test-nightly
|
||||
name: visual-a11y-test-nightly
|
||||
suite: full
|
||||
- e2e-couchdb
|
||||
triggers:
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -8,7 +8,7 @@ Closes <!--- Insert Issue Number(s) this PR addresses. Start by typing # will op
|
||||
|
||||
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
|
||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
|
||||
* [ ] Is this change backwards compatible? For example, developers won't need to change how they are calling the API or how they've extended core plugins such as Tables or Plots.
|
||||
* [ ] Is this a notable change that will require a special callout in the release notes [Notable Change](../docs/src/process/release.md) ? For example, will this break compatibility with existing APIs or projects which source these plugins?
|
||||
|
||||
### Author Checklist
|
||||
|
||||
|
||||
5
.github/release.yml
vendored
5
.github/release.yml
vendored
@@ -1,5 +1,8 @@
|
||||
changelog:
|
||||
categories:
|
||||
- title: 💥 Notable Changes
|
||||
labels:
|
||||
- notable_change
|
||||
- title: 🏕 Features
|
||||
labels:
|
||||
- type:feature
|
||||
@@ -20,4 +23,4 @@ changelog:
|
||||
- dependencies
|
||||
- title: 🐛 Bug Fixes
|
||||
labels:
|
||||
- '*'
|
||||
- "*"
|
||||
|
||||
126
docs/src/process/release.md
Normal file
126
docs/src/process/release.md
Normal file
@@ -0,0 +1,126 @@
|
||||
|
||||
# Release of NASA Open MCT NPM Package
|
||||
|
||||
This document outlines the process and key considerations for releasing a new version of the NASA Open MCT project as an NPM (Node Package Manager) package.
|
||||
|
||||
## FAQ
|
||||
|
||||
1. When do we publish a new version of Open MCT?
|
||||
- At the end of a working sprint (typically) after all blocking issues have been resolved.
|
||||
2. Where do we publish?
|
||||
- [NPM](https://www.npmjs.com/package/openmct)
|
||||
- [Github Releases](https://github.com/nasa/openmct/releases)
|
||||
2. What do we publish?
|
||||
- What constitutes a "stable" release?
|
||||
- TODO
|
||||
- What constitutes a "latest" release?
|
||||
- The most recently published release.
|
||||
- What constitutes a "nightly" release?
|
||||
- TODO
|
||||
4. What necessitates a patch release?
|
||||
-
|
||||
|
||||
## 1. Pre-requisites
|
||||
|
||||
Before releasing a new version of Open MCT, ensure that all dependencies are updated, and
|
||||
comprehensive testing is performed.
|
||||
|
||||
## 2. Versioning
|
||||
|
||||
Open MCT follows [Semantic Versioning 2.0.0 (SemVer)](https://semver.org) that consists of three
|
||||
major components: `MAJOR.MINOR.PATCH` (i.e. `1.2.3`).
|
||||
|
||||
Major releases are necessitated by fundamental framework changes that are expected to be incompatible
|
||||
with previous releases.
|
||||
|
||||
Minor releases are necessitated by non-backwards-compatible application, API changes, or new
|
||||
features or enhancements.
|
||||
|
||||
Patch releases are created for backporting fixes to blocking bugs that were discovered _after_
|
||||
the release of a major or minor version. They are not to introduce new features, enhancements, or
|
||||
dependency changes.
|
||||
|
||||
## 3. Changelog Maintenance
|
||||
|
||||
Changelogs can be found in the GitHub releases section of the repository and are auto-generated
|
||||
using [GitHub's feature](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes).
|
||||
|
||||
## 4. Pull Request Labeling
|
||||
|
||||
Generation of release notes is automated by the use of labels on pull requests. The following
|
||||
labels are used to categorize pull requests:
|
||||
|
||||
### `type:bug`
|
||||
|
||||
Pull requests are to be labeled with `type:bug` if they contain changes that intend to fix a bug.
|
||||
|
||||
### `type:enhancement`
|
||||
|
||||
Pull requests are to be labeled with `type:enhancement` if they contain changes that intend to
|
||||
enhance existing functionality of Open MCT.
|
||||
|
||||
### `type:feature`
|
||||
|
||||
Pull requests are to be labeled with `type:feature` if they contain changes that intend to introduce
|
||||
new functionality to Open MCT.
|
||||
|
||||
### `type:maintenance`
|
||||
|
||||
Pull requests are to be labeled with `type:maintenance` if they contain changes that introduce
|
||||
new tests, documentation, or other maintenance-related changes.
|
||||
|
||||
### `performance`
|
||||
|
||||
Pull requests are to be labeled with `performance` if they contain changes that are intended to
|
||||
improve the performance of Open MCT.
|
||||
|
||||
### `notable_change`
|
||||
|
||||
Pull requests are to be labeled with `notable_change` if they contain changes that fit any of the
|
||||
following criteria:
|
||||
|
||||
- **Breaking Change**
|
||||
- Highlights the integration of changes that are suspected to break, or without a doubt will
|
||||
break, backwards compatibility. These should signal to users the upgrade might be seamless only
|
||||
if dependency and integration factors are properly managed, if not, one should expect to manage
|
||||
atypical technical snags.
|
||||
- **API Change**
|
||||
- Signifies any change to the Open MCT API such as the addition of new methods, or the
|
||||
modification or deprecation of existing methods. API changes may or may not constitute a
|
||||
breaking change.
|
||||
- **Default Behavior Change**
|
||||
- Any change to the default behavior of Open MCT, such as the default configuration of a plugin,
|
||||
or the default behavior of a user interface component or feature (i.e.: autoscale being enabled
|
||||
by default on plots).
|
||||
|
||||
## 5. Community & Contributions
|
||||
|
||||
Open MCT is an open-source project and contributions are welcome. As such, it is important to
|
||||
acknowledge the contributions of the community and contributors. Pull requests by contributors
|
||||
will be labeled with `source:community` to signify that the contribution was made by a member of
|
||||
the community.
|
||||
|
||||
## 6. Release Process
|
||||
|
||||
Currently, the release process is manual and requires the following steps:
|
||||
|
||||
1. Clone a fresh copy of the repository.
|
||||
- `git clone git@github.com:nasa/openmct.git`
|
||||
2. Check out the appropriate release branch.
|
||||
- `git checkout release/1.2.3`
|
||||
3. Ensure that the `package.json` file is updated with the correct version number and does not
|
||||
contain the `-next` suffix (which implies a pre-release).
|
||||
4. Create a tag for the release if it does not already exist.
|
||||
- `git tag v1.2.3`
|
||||
5. Push the tag to the repository.
|
||||
- `git push origin v1.2.3`
|
||||
6. Run `npm install` to install dependencies.
|
||||
7. Publish the release to NPM (You will need to be logged in to an NPM account with the appropriate permissions).
|
||||
- `npm publish`
|
||||
8. Create a release on GitHub.
|
||||
- Navigate to the Releases page on the Open MCT repository.
|
||||
- Click [draft a new release.](https://github.com/nasa/openmct/releases/new)
|
||||
- Choose the tag that was just created for the release.
|
||||
- For "Previous tag", choose the tag that was most recently released.
|
||||
- Click "Generate release notes" to auto-generate release notes.
|
||||
- Click "Publish release" to publish the release.
|
||||
@@ -229,7 +229,7 @@ Current list of test tags:
|
||||
|
||||
|Test Tag|Description|
|
||||
|:-:|-|
|
||||
|`@ipad` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).|
|
||||
|`@mobile` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).|
|
||||
|`@a11y` | Test case or test suite to execute playwright-axe accessibility checks and generate a11y reports.|
|
||||
|`@gds` | Denotes a GDS Test Case used in the VIPER Mission.|
|
||||
|`@addInit` | Initializes the browser with an injected and artificial state. Useful for loading non-default plugins. Likely will not work outside of `npm start`.|
|
||||
@@ -329,9 +329,15 @@ In terms of operating system testing, we're only limited by what the CI provider
|
||||
|
||||
#### **Mobile**
|
||||
|
||||
We have the Mission-need to support iPad. To run our iPad suite, please see our `playwright-*.config.js` with the 'iPad' project.
|
||||
We have a Mission-need to support iPad and mobile devices. To run our test suites with mobile devices, please see our `playwright-mobile.config.js` projects.
|
||||
|
||||
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button) and so this will likely turn into a separate suite.
|
||||
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button). To bypass the object creation, we leverage the `storageState` properties for starting the mobile tests with localstorage.
|
||||
|
||||
For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
|
||||
```sh
|
||||
npm run test:e2e:mobile
|
||||
```
|
||||
command.
|
||||
|
||||
#### **Skipping or executing tests based on browser, os, and/os browser version:**
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const NUM_WORKERS = 2;
|
||||
const config = {
|
||||
retries: 2, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
|
||||
testDir: 'tests',
|
||||
grepInvert: /@mobile/, //Ignore mobile tests
|
||||
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
|
||||
timeout: 60 * 1000,
|
||||
webServer: {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { devices } from '@playwright/test';
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 0,
|
||||
testDir: 'tests',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
testIgnore: '**/*.perf.spec.js',
|
||||
timeout: 30 * 1000,
|
||||
webServer: {
|
||||
@@ -35,7 +33,6 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'MMOC',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
@@ -47,8 +44,6 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'safari',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grep: /@ipad/, // only run ipad tests due to this bug https://github.com/microsoft/playwright/issues/8340
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
@@ -56,7 +51,6 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
@@ -64,7 +58,6 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'canary',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
@@ -73,22 +66,11 @@ const config = {
|
||||
},
|
||||
{
|
||||
name: 'chrome-beta',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome-beta'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ipad',
|
||||
testMatch: '**/*.e2e.spec.js', // only run e2e tests
|
||||
grep: /@ipad/,
|
||||
grepInvert: /@snapshot/,
|
||||
use: {
|
||||
browserName: 'webkit',
|
||||
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: [
|
||||
|
||||
69
e2e/playwright-mobile.config.js
Normal file
69
e2e/playwright-mobile.config.js
Normal file
@@ -0,0 +1,69 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
import { devices } from '@playwright/test';
|
||||
const MAX_FAILURES = 5;
|
||||
const NUM_WORKERS = 2;
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 1, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
|
||||
testDir: 'tests',
|
||||
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
|
||||
timeout: 30 * 1000,
|
||||
webServer: {
|
||||
command: 'npm run start:coverage',
|
||||
url: 'http://localhost:8080/#',
|
||||
timeout: 200 * 1000,
|
||||
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
||||
},
|
||||
maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste
|
||||
workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent
|
||||
use: {
|
||||
baseURL: 'http://localhost:8080/',
|
||||
headless: true,
|
||||
ignoreHTTPSErrors: true,
|
||||
screenshot: 'only-on-failure',
|
||||
trace: 'on-first-retry',
|
||||
video: 'off'
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'ipad',
|
||||
grep: /@mobile/,
|
||||
use: {
|
||||
storageState: fileURLToPath(
|
||||
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
|
||||
),
|
||||
browserName: 'webkit',
|
||||
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'iphone',
|
||||
grep: /@mobile/,
|
||||
use: {
|
||||
storageState: fileURLToPath(
|
||||
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
|
||||
),
|
||||
browserName: 'webkit',
|
||||
...devices['iPhone 14 Pro'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: [
|
||||
['list'],
|
||||
[
|
||||
'html',
|
||||
{
|
||||
open: 'never',
|
||||
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
|
||||
}
|
||||
],
|
||||
['junit', { outputFile: '../test-results/results.xml' }]
|
||||
]
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,6 +1,9 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
import { devices } from '@playwright/test';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 0, //Retries are not needed with watch mode
|
||||
@@ -28,6 +31,28 @@ const config = {
|
||||
use: {
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ipad',
|
||||
grep: /@mobile/,
|
||||
use: {
|
||||
storageState: fileURLToPath(
|
||||
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
|
||||
),
|
||||
browserName: 'webkit',
|
||||
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'iphone',
|
||||
grep: /@mobile/,
|
||||
use: {
|
||||
storageState: fileURLToPath(
|
||||
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
|
||||
),
|
||||
browserName: 'webkit',
|
||||
...devices['iPhone 14 Pro'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: [
|
||||
|
||||
@@ -363,7 +363,7 @@ test.describe('Example Imagery in Display Layout', () => {
|
||||
await page.locator('li[title="View Large"]').click();
|
||||
await expect(pausePlayButton).toHaveClass(/is-paused/);
|
||||
|
||||
await page.locator('[aria-label="Close"]').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
|
||||
});
|
||||
|
||||
@@ -386,7 +386,7 @@ test.describe('Example Imagery in Display Layout', () => {
|
||||
await page.locator('li[title="View Large"]').click();
|
||||
await expect(pausePlayButton).toHaveClass(/is-paused/);
|
||||
|
||||
await page.locator('[aria-label="Close"]').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
await expect.soft(pausePlayButton).toHaveClass(/is-paused/);
|
||||
});
|
||||
|
||||
@@ -509,7 +509,7 @@ test.describe('Example Imagery in Flexible layout', () => {
|
||||
await page.getByRole('button', { name: 'Background Image', state: 'visible' });
|
||||
|
||||
// Close the large view
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
|
||||
@@ -68,7 +68,7 @@ test.describe('Snapshot image tests', () => {
|
||||
// expect large image to be displayed
|
||||
await expect(page.getByRole('dialog').getByText('favicon-96x96.png')).toBeVisible();
|
||||
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
// drop another image onto the entry
|
||||
await page.dispatchEvent('.c-snapshots', 'drop', { dataTransfer: dropTransfer });
|
||||
|
||||
@@ -33,15 +33,15 @@ import {
|
||||
import { expect, test } from '../../../../pluginFixtures.js';
|
||||
|
||||
test.describe('Overlay Plot', () => {
|
||||
let overlayPlot;
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
});
|
||||
|
||||
test('Plot legend color is in sync with plot series color', async ({ page }) => {
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: overlayPlot.uuid
|
||||
@@ -68,9 +68,6 @@ test.describe('Overlay Plot', () => {
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/7403'
|
||||
});
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
@@ -130,10 +127,6 @@ test.describe('Overlay Plot', () => {
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/6338'
|
||||
});
|
||||
// Create an Overlay Plot with a default SWG
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
const swgA = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
@@ -199,10 +192,6 @@ test.describe('Overlay Plot', () => {
|
||||
test('The elements pool supports dragging series into multiple y-axis buckets', async ({
|
||||
page
|
||||
}) => {
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
const swgA = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: overlayPlot.uuid
|
||||
@@ -292,10 +281,6 @@ test.describe('Overlay Plot', () => {
|
||||
description: 'https://github.com/nasa/openmct/issues/7421'
|
||||
});
|
||||
|
||||
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Overlay Plot'
|
||||
});
|
||||
|
||||
const swgA = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: overlayPlot.uuid
|
||||
@@ -309,12 +294,32 @@ test.describe('Overlay Plot', () => {
|
||||
await page.getByRole('tab', { name: 'Elements' }).click();
|
||||
|
||||
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click();
|
||||
|
||||
const plotPixels = await getCanvasPixels(page, '.js-overlay canvas');
|
||||
const plotPixelSize = plotPixels.length;
|
||||
expect(plotPixelSize).toBeGreaterThan(0);
|
||||
}
|
||||
);
|
||||
|
||||
test('Can remove an item via the elements pool action menu', async ({ page }) => {
|
||||
const swgA = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Sine Wave Generator',
|
||||
parent: overlayPlot.uuid
|
||||
});
|
||||
|
||||
await page.goto(overlayPlot.url);
|
||||
// Wait for plot series data to load and be drawn
|
||||
await waitForPlotsToRender(page);
|
||||
await page.getByLabel('Edit Object').click();
|
||||
|
||||
await page.getByRole('tab', { name: 'Elements' }).click();
|
||||
|
||||
const swgAElementsPoolItem = page.getByLabel(`Preview ${swgA.name}`);
|
||||
await expect(swgAElementsPoolItem).toBeVisible();
|
||||
await swgAElementsPoolItem.click({ button: 'right' });
|
||||
await page.getByRole('menuitem', { name: 'Remove' }).click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
await expect(swgAElementsPoolItem).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,7 +57,7 @@ test.describe('Plots work in Previews', () => {
|
||||
await page.getByLabel('Sine', { exact: true }).click({ button: 'right' });
|
||||
await page.getByLabel('View Historical Data').click();
|
||||
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
await page.getByLabel('Expand Test Display Layout layout').click();
|
||||
|
||||
// change to a plot and ensure embiggen works
|
||||
@@ -73,7 +73,7 @@ test.describe('Plots work in Previews', () => {
|
||||
await expect(page.getByLabel('Preview Container')).toBeHidden();
|
||||
await page.getByLabel('Large View').click();
|
||||
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
// get last sinewave tree item (in the display layout)
|
||||
await page
|
||||
@@ -83,6 +83,6 @@ test.describe('Plots work in Previews', () => {
|
||||
.click({ button: 'right' });
|
||||
await page.getByLabel('View', { exact: true }).click();
|
||||
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ test.describe('Preview mode', () => {
|
||||
await expect(page.getByLabel('Export Table Data')).toBeVisible();
|
||||
await expect(page.getByLabel('Export Marked Rows')).toBeVisible();
|
||||
await page.getByRole('menuitem', { name: 'Pause' }).click();
|
||||
await page.getByLabel('Close').click();
|
||||
await page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
await expandEntireTree(page);
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ test.describe('Recent Objects', () => {
|
||||
// Assert that the list is empty
|
||||
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
|
||||
});
|
||||
test('Ensure clear recent objects button is active or inactive', async ({ page }) => {
|
||||
test('Verify functionality of "clear" and "collapse pane" buttons', async ({ page }) => {
|
||||
// Assert that the list initially contains 3 objects (clock, folder, my items)
|
||||
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3);
|
||||
|
||||
@@ -331,6 +331,24 @@ test.describe('Recent Objects', () => {
|
||||
expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe(
|
||||
true
|
||||
);
|
||||
|
||||
// Assert initial state of pane and collapse the Recent Objects panel
|
||||
await expect(page.getByLabel('Expand Recently Viewed Pane')).toBeHidden();
|
||||
await expect(page.getByLabel('Collapse Recently Viewed Pane')).toBeVisible();
|
||||
await page.getByLabel('Collapse Recently Viewed Pane').click();
|
||||
|
||||
// Assert that the "Expand Recently Viewed Pane" button is visible
|
||||
// and that the "Collapse Recently Viewed Pane" button is hidden
|
||||
await expect(page.getByLabel('Expand Recently Viewed Pane')).toBeVisible();
|
||||
await expect(page.getByLabel('Collapse Recently Viewed Pane')).toBeHidden();
|
||||
|
||||
// Expand the Recent Objects panel by clicking on the "Expand Recently Viewed Pane" button
|
||||
await page.getByLabel('Expand Recently Viewed Pane').click();
|
||||
|
||||
// Assert that the "Expand Recently Viewed Pane" button is hidden
|
||||
// and that the "Collapse Recently Viewed Pane" button is visible
|
||||
await expect(page.getByLabel('Expand Recently Viewed Pane')).toBeHidden();
|
||||
await expect(page.getByLabel('Collapse Recently Viewed Pane')).toBeVisible();
|
||||
});
|
||||
|
||||
function assertInitialRecentObjectsListState() {
|
||||
|
||||
@@ -48,7 +48,7 @@ test('Verify that the create button appears and that the Folder Domain Object is
|
||||
await expect(page.locator(':nth-match(:text("Folder"), 2)')).toBeEnabled();
|
||||
});
|
||||
|
||||
test('Verify that My Items Tree appears @ipad', async ({ page, openmctConfig }) => {
|
||||
test('Verify that My Items Tree appears', async ({ page, openmctConfig }) => {
|
||||
const { myItemsFolderName } = openmctConfig;
|
||||
//Go to baseURL
|
||||
await page.goto('./');
|
||||
|
||||
59
e2e/tests/mobile/smoke.e2e.spec.js
Normal file
59
e2e/tests/mobile/smoke.e2e.spec.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2024, 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 dedicated to tests which can quickly verify that any openmct installation is
|
||||
operable and that any type of testing can proceed.
|
||||
|
||||
Ideally, smoke tests should make zero assumptions about how and where they are run. This makes them
|
||||
more resilient to change and therefor a better indicator of failure. Smoke tests will also run quickly
|
||||
as they cover a very "thin surface" of functionality.
|
||||
|
||||
When deciding between authoring new smoke tests or functional tests, ask yourself "would I feel
|
||||
comfortable running this test during a live mission?" Avoid creating or deleting Domain Objects.
|
||||
Make no assumptions about the order that elements appear in the DOM.
|
||||
*/
|
||||
|
||||
import { expect, test } from '../../pluginFixtures.js';
|
||||
|
||||
test('Verify that My Items Tree appears @mobile', async ({ page, openmctConfig }) => {
|
||||
const { myItemsFolderName } = openmctConfig;
|
||||
//Go to baseURL
|
||||
await page.goto('./');
|
||||
|
||||
//My Items to be visible
|
||||
await expect(page.getByRole('treeitem', { name: `${myItemsFolderName}` })).toBeVisible();
|
||||
});
|
||||
|
||||
test('Verify that user can search @mobile', async ({ page }) => {
|
||||
//For now, this test is going to be hardcoded against './test-data/display_layout_with_child_layouts.json'
|
||||
await page.goto('./');
|
||||
|
||||
await page.getByRole('searchbox', { name: 'Search Input' }).click();
|
||||
await page.getByRole('searchbox', { name: 'Search Input' }).fill('Parent Display Layout');
|
||||
//Search Results appear in search modal
|
||||
await expect(page.getByLabel('Object Results').getByText('Parent Display Layout')).toBeVisible();
|
||||
//Clicking on the search result takes you to the object
|
||||
await page.getByLabel('Object Results').getByText('Parent Display Layout').click();
|
||||
await page.getByTitle('Collapse Browse Pane').click();
|
||||
await expect(page.getByRole('main').getByText('Parent Display Layout')).toBeVisible();
|
||||
});
|
||||
@@ -47,7 +47,7 @@ if (document.currentScript) {
|
||||
* @property {*} inspectorViews
|
||||
* @property {*} propertyEditors
|
||||
* @property {*} toolbars
|
||||
* @property {*} types
|
||||
* @property {import('./src/api/types/TypeRegistry').default} types
|
||||
* @property {import('./src/api/objects/ObjectAPI').default} objects
|
||||
* @property {import('./src/api/telemetry/TelemetryAPI').default} telemetry
|
||||
* @property {import('./src/api/indicators/IndicatorAPI').default} indicators
|
||||
@@ -67,6 +67,7 @@ if (document.currentScript) {
|
||||
* @property {import('./src/api/annotation/AnnotationAPI').default} annotation
|
||||
* @property {{(plugin: OpenMCTPlugin) => void}} install
|
||||
* @property {{() => string}} getAssetPath
|
||||
* @property {{(assetPath: string) => void}} setAssetPath
|
||||
* @property {{(domElement: HTMLElement, isHeadlessMode: boolean) => void}} start
|
||||
* @property {{() => void}} startHeadless
|
||||
* @property {{() => void}} destroy
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"tiny-emitter": "2.1.0",
|
||||
"typescript": "5.3.3",
|
||||
"uuid": "9.0.1",
|
||||
"vue": "3.3.8",
|
||||
"vue": "3.4.19",
|
||||
"vue-eslint-parser": "9.3.2",
|
||||
"vue-loader": "16.8.3",
|
||||
"webpack": "5.89.0",
|
||||
@@ -107,6 +107,7 @@
|
||||
"test:debug": "KARMA_DEBUG=true karma start karma.conf.cjs",
|
||||
"test:e2e": "npx playwright test",
|
||||
"test:e2e:a11y": "npx playwright test --config=e2e/playwright-visual-a11y.config.js --project=chrome --grep @a11y",
|
||||
"test:e2e:mobile": "npx playwright test --config=e2e/playwright-mobile.config.js",
|
||||
"test:e2e:couchdb": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @couchdb --workers=1",
|
||||
"test:e2e:stable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep-invert \"@unstable|@couchdb|@generatedata\"",
|
||||
"test:e2e:unstable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @unstable",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
import _ from 'lodash';
|
||||
|
||||
import objectUtils from '../objects/object-utils.js';
|
||||
import { makeKeyString, parseKeyString } from '../objects/object-utils.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject
|
||||
@@ -223,18 +223,18 @@ export default class CompositionProvider {
|
||||
* @param {DomainObject} oldDomainObject
|
||||
*/
|
||||
#onMutation(newDomainObject, oldDomainObject) {
|
||||
const id = objectUtils.makeKeyString(oldDomainObject.identifier);
|
||||
const id = makeKeyString(oldDomainObject.identifier);
|
||||
const listeners = this.#listeningTo[id];
|
||||
|
||||
if (!listeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldComposition = oldDomainObject.composition.map(objectUtils.makeKeyString);
|
||||
const newComposition = newDomainObject.composition.map(objectUtils.makeKeyString);
|
||||
const oldComposition = oldDomainObject.composition.map(makeKeyString);
|
||||
const newComposition = newDomainObject.composition.map(makeKeyString);
|
||||
|
||||
const added = _.difference(newComposition, oldComposition).map(objectUtils.parseKeyString);
|
||||
const removed = _.difference(oldComposition, newComposition).map(objectUtils.parseKeyString);
|
||||
const added = _.difference(newComposition, oldComposition).map(parseKeyString);
|
||||
const removed = _.difference(oldComposition, newComposition).map(parseKeyString);
|
||||
|
||||
function notify(value) {
|
||||
return function (listener) {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
import objectUtils from '../objects/object-utils.js';
|
||||
import { makeKeyString } from '../objects/object-utils.js';
|
||||
import CompositionProvider from './CompositionProvider.js';
|
||||
|
||||
/**
|
||||
@@ -91,7 +91,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
|
||||
this.establishTopicListener();
|
||||
|
||||
/** @type {string} */
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
let objectListeners = this.listeningTo[keyString];
|
||||
|
||||
if (!objectListeners) {
|
||||
@@ -120,7 +120,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
|
||||
*/
|
||||
off(domainObject, event, callback, context) {
|
||||
/** @type {string} */
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
const objectListeners = this.listeningTo[keyString];
|
||||
|
||||
const index = objectListeners[event].findIndex((l) => {
|
||||
@@ -228,7 +228,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
|
||||
this.publicAPI.objects.mutate(domainObject, 'composition', newComposition);
|
||||
|
||||
/** @type {string} */
|
||||
let id = objectUtils.makeKeyString(domainObject.identifier);
|
||||
let id = makeKeyString(domainObject.identifier);
|
||||
const listeners = this.listeningTo[id];
|
||||
|
||||
if (!listeners) {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import _ from 'lodash';
|
||||
|
||||
import utils from './object-utils.js';
|
||||
import { makeKeyString, refresh } from './object-utils.js';
|
||||
|
||||
const ANY_OBJECT_EVENT = 'mutation';
|
||||
|
||||
@@ -152,7 +152,7 @@ class MutableDomainObject {
|
||||
|
||||
mutable.$observe('$_synchronize_model', (updatedObject) => {
|
||||
let clone = JSON.parse(JSON.stringify(updatedObject));
|
||||
utils.refresh(mutable, clone);
|
||||
refresh(mutable, clone);
|
||||
});
|
||||
|
||||
return mutable;
|
||||
@@ -168,7 +168,7 @@ class MutableDomainObject {
|
||||
}
|
||||
|
||||
function qualifiedEventName(object, eventName) {
|
||||
let keystring = utils.makeKeyString(object.identifier);
|
||||
let keystring = makeKeyString(object.identifier);
|
||||
|
||||
return [keystring, eventName].join(':');
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import utils from 'objectUtils';
|
||||
import { identifierEquals, makeKeyString, parseKeyString, refresh } from 'objectUtils';
|
||||
|
||||
import ConflictError from './ConflictError.js';
|
||||
import InMemorySearchProvider from './InMemorySearchProvider.js';
|
||||
@@ -82,8 +82,19 @@ import Transaction from './Transaction.js';
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
export default class ObjectAPI {
|
||||
#makeKeyString;
|
||||
#parseKeyString;
|
||||
#identifierEquals;
|
||||
#refresh;
|
||||
#openmct;
|
||||
|
||||
constructor(typeRegistry, openmct) {
|
||||
this.openmct = openmct;
|
||||
this.#makeKeyString = makeKeyString;
|
||||
this.#parseKeyString = parseKeyString;
|
||||
this.#identifierEquals = identifierEquals;
|
||||
this.#refresh = refresh;
|
||||
this.#openmct = openmct;
|
||||
|
||||
this.typeRegistry = typeRegistry;
|
||||
this.SEARCH_TYPES = Object.freeze({
|
||||
OBJECTS: 'OBJECTS',
|
||||
@@ -206,14 +217,14 @@ export default class ObjectAPI {
|
||||
* has been saved, or be rejected if it cannot be saved
|
||||
*/
|
||||
get(identifier, abortSignal, forceRemote = false) {
|
||||
let keystring = this.makeKeyString(identifier);
|
||||
let keystring = this.#makeKeyString(identifier);
|
||||
|
||||
if (!forceRemote) {
|
||||
if (this.cache[keystring] !== undefined) {
|
||||
return this.cache[keystring];
|
||||
}
|
||||
|
||||
identifier = utils.parseKeyString(identifier);
|
||||
identifier = parseKeyString(identifier);
|
||||
|
||||
if (this.isTransactionActive()) {
|
||||
let dirtyObject = this.transaction.getDirtyObject(identifier);
|
||||
@@ -227,7 +238,7 @@ export default class ObjectAPI {
|
||||
const provider = this.getProvider(identifier);
|
||||
|
||||
if (!provider) {
|
||||
throw new Error(`No Provider Matched for keyString "${this.makeKeyString(identifier)}"`);
|
||||
throw new Error(`No Provider Matched for keyString "${this.#makeKeyString(identifier)}"`);
|
||||
}
|
||||
|
||||
if (!provider.get) {
|
||||
@@ -325,7 +336,7 @@ export default class ObjectAPI {
|
||||
*/
|
||||
getMutable(identifier) {
|
||||
if (!this.supportsMutation(identifier)) {
|
||||
throw new Error(`Object "${this.makeKeyString(identifier)}" does not support mutation.`);
|
||||
throw new Error(`Object "${this.#makeKeyString(identifier)}" does not support mutation.`);
|
||||
}
|
||||
|
||||
return this.get(identifier).then((object) => {
|
||||
@@ -352,7 +363,7 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
isPersistable(idOrKeyString) {
|
||||
let identifier = utils.parseKeyString(idOrKeyString);
|
||||
let identifier = parseKeyString(idOrKeyString);
|
||||
let provider = this.getProvider(identifier);
|
||||
if (provider?.isReadOnly) {
|
||||
return !provider.isReadOnly();
|
||||
@@ -362,7 +373,7 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
isMissing(domainObject) {
|
||||
let identifier = utils.makeKeyString(domainObject.identifier);
|
||||
let identifier = makeKeyString(domainObject.identifier);
|
||||
let missingName = 'Missing: ' + identifier;
|
||||
|
||||
return domainObject.name === missingName;
|
||||
@@ -442,21 +453,21 @@ export default class ObjectAPI {
|
||||
if (error instanceof this.errors.Conflict) {
|
||||
// Synchronized objects will resolve their own conflicts
|
||||
if (this.SYNCHRONIZED_OBJECT_TYPES.includes(domainObject.type)) {
|
||||
this.openmct.notifications.info(
|
||||
`Conflict detected while saving "${this.makeKeyString(
|
||||
this.#openmct.notifications.info(
|
||||
`Conflict detected while saving "${this.#makeKeyString(
|
||||
domainObject.name
|
||||
)}", attempting to resolve`
|
||||
);
|
||||
} else {
|
||||
this.openmct.notifications.error(
|
||||
`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`
|
||||
this.#openmct.notifications.error(
|
||||
`Conflict detected while saving ${this.#makeKeyString(domainObject.identifier)}`
|
||||
);
|
||||
|
||||
if (this.isTransactionActive()) {
|
||||
this.endTransaction();
|
||||
}
|
||||
|
||||
await this.refresh(domainObject);
|
||||
await this.#refresh(domainObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,7 +476,7 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
async #getCurrentUsername() {
|
||||
const user = await this.openmct.user.getCurrentUser();
|
||||
const user = await this.#openmct.user.getCurrentUser();
|
||||
let username;
|
||||
|
||||
if (user !== undefined) {
|
||||
@@ -554,7 +565,7 @@ export default class ObjectAPI {
|
||||
*/
|
||||
getRelativePath(objectPath) {
|
||||
return objectPath
|
||||
.map((p) => this.makeKeyString(p.identifier))
|
||||
.map((p) => this.#makeKeyString(p.identifier))
|
||||
.reverse()
|
||||
.join('/');
|
||||
}
|
||||
@@ -574,13 +585,13 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
let sourceTelemetry = null;
|
||||
if (telemetryIdentifier && utils.identifierEquals(identifier, telemetryIdentifier)) {
|
||||
if (telemetryIdentifier && this.#identifierEquals(identifier, telemetryIdentifier)) {
|
||||
sourceTelemetry = identifier;
|
||||
} else if (objectDetails.composition) {
|
||||
sourceTelemetry = objectDetails.composition[0];
|
||||
if (telemetryIdentifier) {
|
||||
sourceTelemetry = objectDetails.composition.find((telemetrySource) =>
|
||||
utils.identifierEquals(telemetrySource, telemetryIdentifier)
|
||||
this.#identifierEquals(telemetrySource, telemetryIdentifier)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -666,7 +677,7 @@ export default class ObjectAPI {
|
||||
mutableObject = MutableDomainObject.createMutable(domainObject, this.eventEmitter);
|
||||
|
||||
// Check if provider supports realtime updates
|
||||
let identifier = utils.parseKeyString(mutableObject.identifier);
|
||||
let identifier = parseKeyString(mutableObject.identifier);
|
||||
let provider = this.getProvider(identifier);
|
||||
|
||||
if (
|
||||
@@ -706,7 +717,7 @@ export default class ObjectAPI {
|
||||
if (domainObject.isMutable) {
|
||||
domainObject.$refresh(refreshedObject);
|
||||
} else {
|
||||
utils.refresh(domainObject, refreshedObject);
|
||||
refresh(domainObject, refreshedObject);
|
||||
}
|
||||
|
||||
return domainObject;
|
||||
@@ -745,7 +756,7 @@ export default class ObjectAPI {
|
||||
* @returns {string} A string representation of the given identifier, including namespace and key
|
||||
*/
|
||||
makeKeyString(identifier) {
|
||||
return utils.makeKeyString(identifier);
|
||||
return makeKeyString(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -753,7 +764,7 @@ export default class ObjectAPI {
|
||||
* @returns {module:openmct.ObjectAPI~Identifier} An identifier object
|
||||
*/
|
||||
parseKeyString(keyString) {
|
||||
return utils.parseKeyString(keyString);
|
||||
return parseKeyString(keyString);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -761,9 +772,9 @@ export default class ObjectAPI {
|
||||
* @param {module:openmct.ObjectAPI~Identifier[]} identifiers
|
||||
*/
|
||||
areIdsEqual(...identifiers) {
|
||||
const firstIdentifier = utils.parseKeyString(identifiers[0]);
|
||||
const firstIdentifier = this.#parseKeyString(identifiers[0]);
|
||||
|
||||
return identifiers.map(utils.parseKeyString).every((identifier) => {
|
||||
return identifiers.map(this.#parseKeyString).every((identifier) => {
|
||||
return (
|
||||
identifier === firstIdentifier ||
|
||||
(identifier.namespace === firstIdentifier.namespace &&
|
||||
@@ -791,7 +802,7 @@ export default class ObjectAPI {
|
||||
}
|
||||
|
||||
return path.some((pathElement) => {
|
||||
const identifierToCheck = utils.parseKeyString(keyStringToCheck);
|
||||
const identifierToCheck = this.#parseKeyString(keyStringToCheck);
|
||||
|
||||
return this.areIdsEqual(identifierToCheck, pathElement.identifier);
|
||||
});
|
||||
@@ -814,7 +825,7 @@ export default class ObjectAPI {
|
||||
if (location && !this.#pathContainsDomainObject(location, path)) {
|
||||
// if we have a location, and we don't already have this in our constructed path,
|
||||
// then keep walking up the path
|
||||
return this.getOriginalPath(utils.parseKeyString(location), path, abortSignal);
|
||||
return this.getOriginalPath(this.#parseKeyString(location), path, abortSignal);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
@@ -853,8 +864,8 @@ export default class ObjectAPI {
|
||||
await Promise.all(
|
||||
keyStrings.map((keyString) =>
|
||||
this.supportsMutation(keyString)
|
||||
? this.getMutable(utils.parseKeyString(keyString))
|
||||
: this.get(utils.parseKeyString(keyString))
|
||||
? this.getMutable(this.#parseKeyString(keyString))
|
||||
: this.get(this.#parseKeyString(keyString))
|
||||
)
|
||||
)
|
||||
).reverse();
|
||||
@@ -866,7 +877,7 @@ export default class ObjectAPI {
|
||||
return (
|
||||
objectPath !== undefined &&
|
||||
objectPath.length > 1 &&
|
||||
domainObject.location !== this.makeKeyString(objectPath[1].identifier)
|
||||
domainObject.location !== this.#makeKeyString(objectPath[1].identifier)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import utils from './object-utils.js';
|
||||
import { isIdentifier } from './object-utils.js';
|
||||
|
||||
export default class RootRegistry {
|
||||
constructor(openmct) {
|
||||
@@ -47,12 +47,12 @@ export default class RootRegistry {
|
||||
}
|
||||
|
||||
_isValid(rootItem) {
|
||||
if (utils.isIdentifier(rootItem) || typeof rootItem === 'function') {
|
||||
if (isIdentifier(rootItem) || typeof rootItem === 'function') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(rootItem)) {
|
||||
return rootItem.every(utils.isIdentifier);
|
||||
return rootItem.every(isIdentifier);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import utils from 'objectUtils';
|
||||
import { makeKeyString, parseKeyString } from 'objectUtils';
|
||||
|
||||
import Transaction from './Transaction.js';
|
||||
|
||||
@@ -9,7 +9,7 @@ let transaction;
|
||||
describe('Transaction Class', () => {
|
||||
beforeEach(() => {
|
||||
objectAPI = {
|
||||
makeKeyString: (identifier) => utils.makeKeyString(identifier),
|
||||
makeKeyString: (identifier) => makeKeyString(identifier),
|
||||
save: () => Promise.resolve(true),
|
||||
mutate: (object, prop, value) => {
|
||||
object[prop] = value;
|
||||
@@ -18,7 +18,7 @@ describe('Transaction Class', () => {
|
||||
},
|
||||
refresh: (object) => Promise.resolve(object),
|
||||
areIdsEqual: (...identifiers) => {
|
||||
return identifiers.map(utils.parseKeyString).every((identifier) => {
|
||||
return identifiers.map(parseKeyString).every((identifier) => {
|
||||
return (
|
||||
identifier === identifiers[0] ||
|
||||
(identifier.namespace === identifiers[0].namespace &&
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
* Utility for checking if a thing is an Open MCT Identifier.
|
||||
* @private
|
||||
*/
|
||||
function isIdentifier(thing) {
|
||||
export function isIdentifier(thing) {
|
||||
return (
|
||||
typeof thing === 'object' &&
|
||||
Object.prototype.hasOwnProperty.call(thing, 'key') &&
|
||||
@@ -36,7 +36,7 @@ function isIdentifier(thing) {
|
||||
* Utility for checking if a thing is a key string. Not perfect.
|
||||
* @private
|
||||
*/
|
||||
function isKeyString(thing) {
|
||||
export function isKeyString(thing) {
|
||||
return typeof thing === 'string';
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ function isKeyString(thing) {
|
||||
* @param keyString
|
||||
* @returns identifier
|
||||
*/
|
||||
function parseKeyString(keyString) {
|
||||
export function parseKeyString(keyString) {
|
||||
if (isIdentifier(keyString)) {
|
||||
return keyString;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ function parseKeyString(keyString) {
|
||||
* @param identifier
|
||||
* @returns keyString
|
||||
*/
|
||||
function makeKeyString(identifier) {
|
||||
export function makeKeyString(identifier) {
|
||||
if (!identifier) {
|
||||
throw new Error('Cannot make key string from null identifier');
|
||||
}
|
||||
@@ -112,7 +112,7 @@ function makeKeyString(identifier) {
|
||||
* @param domainObject
|
||||
* @returns oldFormatModel
|
||||
*/
|
||||
function toOldFormat(model) {
|
||||
export function toOldFormat(model) {
|
||||
model = JSON.parse(JSON.stringify(model));
|
||||
delete model.identifier;
|
||||
if (model.composition) {
|
||||
@@ -131,7 +131,7 @@ function toOldFormat(model) {
|
||||
* @param keyString
|
||||
* @returns domainObject
|
||||
*/
|
||||
function toNewFormat(model, keyString) {
|
||||
export function toNewFormat(model, keyString) {
|
||||
model = JSON.parse(JSON.stringify(model));
|
||||
model.identifier = parseKeyString(keyString);
|
||||
if (model.composition) {
|
||||
@@ -148,7 +148,7 @@ function toNewFormat(model, keyString) {
|
||||
* @param otherIdentifier
|
||||
* @returns Boolean true if identifiers are equal.
|
||||
*/
|
||||
function identifierEquals(a, b) {
|
||||
export function identifierEquals(a, b) {
|
||||
return a.key === b.key && a.namespace === b.namespace;
|
||||
}
|
||||
|
||||
@@ -160,23 +160,12 @@ function identifierEquals(a, b) {
|
||||
* @param otherDomainOBject
|
||||
* @returns Boolean true if objects are equal.
|
||||
*/
|
||||
function objectEquals(a, b) {
|
||||
export function objectEquals(a, b) {
|
||||
return identifierEquals(a.identifier, b.identifier);
|
||||
}
|
||||
|
||||
function refresh(oldObject, newObject) {
|
||||
export function refresh(oldObject, newObject) {
|
||||
let deleted = _.difference(Object.keys(oldObject), Object.keys(newObject));
|
||||
deleted.forEach((propertyName) => delete oldObject[propertyName]);
|
||||
Object.assign(oldObject, newObject);
|
||||
}
|
||||
|
||||
export default {
|
||||
isIdentifier: isIdentifier,
|
||||
toOldFormat: toOldFormat,
|
||||
toNewFormat: toNewFormat,
|
||||
makeKeyString: makeKeyString,
|
||||
parseKeyString: parseKeyString,
|
||||
equals: objectEquals,
|
||||
identifierEquals: identifierEquals,
|
||||
refresh: refresh
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString, parseKeyString, toNewFormat, toOldFormat } from 'objectUtils';
|
||||
|
||||
describe('objectUtils', function () {
|
||||
describe('keyString util', function () {
|
||||
@@ -31,27 +31,27 @@ describe('objectUtils', function () {
|
||||
|
||||
Object.keys(EXPECTATIONS).forEach(function (keyString) {
|
||||
it('parses "' + keyString + '".', function () {
|
||||
expect(objectUtils.parseKeyString(keyString)).toEqual(EXPECTATIONS[keyString]);
|
||||
expect(parseKeyString(keyString)).toEqual(EXPECTATIONS[keyString]);
|
||||
});
|
||||
|
||||
it('parses and re-encodes "' + keyString + '"', function () {
|
||||
const identifier = objectUtils.parseKeyString(keyString);
|
||||
expect(objectUtils.makeKeyString(identifier)).toEqual(keyString);
|
||||
const identifier = parseKeyString(keyString);
|
||||
expect(makeKeyString(identifier)).toEqual(keyString);
|
||||
});
|
||||
|
||||
it('is idempotent for "' + keyString + '".', function () {
|
||||
const identifier = objectUtils.parseKeyString(keyString);
|
||||
let again = objectUtils.parseKeyString(identifier);
|
||||
const identifier = parseKeyString(keyString);
|
||||
let again = parseKeyString(identifier);
|
||||
expect(identifier).toEqual(again);
|
||||
again = objectUtils.parseKeyString(again);
|
||||
again = objectUtils.parseKeyString(again);
|
||||
again = parseKeyString(again);
|
||||
again = parseKeyString(again);
|
||||
expect(identifier).toEqual(again);
|
||||
|
||||
let againKeyString = objectUtils.makeKeyString(again);
|
||||
let againKeyString = makeKeyString(again);
|
||||
expect(againKeyString).toEqual(keyString);
|
||||
againKeyString = objectUtils.makeKeyString(againKeyString);
|
||||
againKeyString = objectUtils.makeKeyString(againKeyString);
|
||||
againKeyString = objectUtils.makeKeyString(againKeyString);
|
||||
againKeyString = makeKeyString(againKeyString);
|
||||
againKeyString = makeKeyString(againKeyString);
|
||||
againKeyString = makeKeyString(againKeyString);
|
||||
expect(againKeyString).toEqual(keyString);
|
||||
});
|
||||
});
|
||||
@@ -60,7 +60,7 @@ describe('objectUtils', function () {
|
||||
describe('old object conversions', function () {
|
||||
it('translate ids', function () {
|
||||
expect(
|
||||
objectUtils.toNewFormat(
|
||||
toNewFormat(
|
||||
{
|
||||
prop: 'someValue'
|
||||
},
|
||||
@@ -77,7 +77,7 @@ describe('objectUtils', function () {
|
||||
|
||||
it('translates composition', function () {
|
||||
expect(
|
||||
objectUtils.toNewFormat(
|
||||
toNewFormat(
|
||||
{
|
||||
prop: 'someValue',
|
||||
composition: ['anotherObjectId', 'scratch:anotherObjectId']
|
||||
@@ -107,7 +107,7 @@ describe('objectUtils', function () {
|
||||
describe('new object conversions', function () {
|
||||
it('removes ids', function () {
|
||||
expect(
|
||||
objectUtils.toOldFormat({
|
||||
toOldFormat({
|
||||
prop: 'someValue',
|
||||
identifier: {
|
||||
namespace: '',
|
||||
@@ -121,7 +121,7 @@ describe('objectUtils', function () {
|
||||
|
||||
it('translates composition', function () {
|
||||
expect(
|
||||
objectUtils.toOldFormat({
|
||||
toOldFormat({
|
||||
prop: 'someValue',
|
||||
composition: [
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import CustomStringFormatter from '../../plugins/displayLayout/CustomStringFormatter.js';
|
||||
import BatchingWebSocket from './BatchingWebSocket.js';
|
||||
@@ -429,7 +429,7 @@ export default class TelemetryAPI {
|
||||
this.#subscribeCache = {};
|
||||
}
|
||||
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
const supportedStrategy = supportsBatching ? requestedStrategy : SUBSCRIBE_STRATEGY.LATEST;
|
||||
// Override the requested strategy with the strategy supported by the provider
|
||||
const optionsWithSupportedStrategy = {
|
||||
@@ -541,7 +541,7 @@ export default class TelemetryAPI {
|
||||
this.stalenessSubscriberCache = {};
|
||||
}
|
||||
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
let stalenessSubscriber = this.stalenessSubscriberCache[keyString];
|
||||
|
||||
if (!stalenessSubscriber) {
|
||||
@@ -600,7 +600,7 @@ export default class TelemetryAPI {
|
||||
this.limitsSubscribeCache = {};
|
||||
}
|
||||
|
||||
const keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const keyString = makeKeyString(domainObject.identifier);
|
||||
let subscriber = this.limitsSubscribeCache[keyString];
|
||||
|
||||
if (!subscriber) {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
import { parseKeyString } from 'objectUtils';
|
||||
import { filter__proto__ } from 'utils/sanitization';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@@ -158,7 +158,7 @@ export default class ImportAsJSONAction {
|
||||
key: uuid()
|
||||
};
|
||||
|
||||
const oldId = objectUtils.parseKeyString(domainObjectId);
|
||||
const oldId = parseKeyString(domainObjectId);
|
||||
|
||||
tree = this._rewriteId(oldId, newId, tree);
|
||||
}, this);
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<template>
|
||||
<li
|
||||
draggable="true"
|
||||
:aria-label="`${elementObject.name} Element Item`"
|
||||
:aria-grabbed="hover"
|
||||
@dragstart="emitDragStartEvent"
|
||||
@dragenter="onDragenter"
|
||||
@dragover.prevent
|
||||
@@ -76,16 +78,19 @@ export default {
|
||||
},
|
||||
emits: ['drop-custom', 'dragstart-custom'],
|
||||
data() {
|
||||
const isAlias =
|
||||
this.elementObject.location !==
|
||||
this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
|
||||
return {
|
||||
contextClickActive: false,
|
||||
hover: false,
|
||||
isAlias
|
||||
hover: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isAlias() {
|
||||
return (
|
||||
this.elementObject.location !==
|
||||
this.openmct.objects.makeKeyString(this.domainObject.identifier)
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emitDropEvent(event) {
|
||||
this.$emit('drop-custom', event);
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
/>
|
||||
<div class="c-elements-pool__elements">
|
||||
<ul
|
||||
v-if="hasElements"
|
||||
v-show="hasElements"
|
||||
id="inspector-elements-tree"
|
||||
class="c-tree c-elements-pool__tree js-elements-pool__tree"
|
||||
>
|
||||
@@ -63,7 +63,7 @@
|
||||
></li>
|
||||
</element-item-group>
|
||||
</ul>
|
||||
<div v-if="!hasElements">No contained elements</div>
|
||||
<div v-show="!hasElements">No contained elements</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -169,7 +169,8 @@ export default {
|
||||
setYAxisIds() {
|
||||
const configId = this.openmct.objects.makeKeyString(this.parentObject.identifier);
|
||||
this.config = configStore.get(configId);
|
||||
this.yAxes = [];
|
||||
// Clear the yAxes array and repopulate it with the current YAxis elements
|
||||
this.yAxes.splice(0);
|
||||
this.yAxes.push({
|
||||
id: this.config.yAxis.id,
|
||||
elements: this.parentObject.configuration.series.filter(
|
||||
@@ -207,7 +208,7 @@ export default {
|
||||
}
|
||||
|
||||
// Store the element in the cache and set its yAxisId
|
||||
this.elementsCache[keyString] = JSON.parse(JSON.stringify(element));
|
||||
this.elementsCache[keyString] = element;
|
||||
if (this.elementsCache[keyString].yAxisId !== yAxisId) {
|
||||
// Mutate the YAxisId on the domainObject itself
|
||||
this.updateCacheAndMutate(element, yAxisId);
|
||||
@@ -276,7 +277,7 @@ export default {
|
||||
yAxisId
|
||||
});
|
||||
this.composition.add(domainObject);
|
||||
this.elementsCache[keyString] = JSON.parse(JSON.stringify(domainObject));
|
||||
this.elementsCache[keyString] = domainObject;
|
||||
}
|
||||
|
||||
this.elementsCache[keyString].yAxisId = yAxisId;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
const NOTEBOOK_LOCAL_STORAGE = 'notebook-storage';
|
||||
let currentNotebookObjectIdentifier = null;
|
||||
@@ -22,8 +22,7 @@ function defaultNotebookObjectChanged(newDomainObject) {
|
||||
function observeDefaultNotebookObject(openmct, notebookStorage, domainObject) {
|
||||
if (
|
||||
currentNotebookObjectIdentifier &&
|
||||
objectUtils.makeKeyString(currentNotebookObjectIdentifier) ===
|
||||
objectUtils.makeKeyString(notebookStorage.identifier)
|
||||
makeKeyString(currentNotebookObjectIdentifier) === makeKeyString(notebookStorage.identifier)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ export default {
|
||||
this.role = activeRole;
|
||||
const status = await this.openmct.user.status.getStatusForRole(activeRole);
|
||||
if (status !== undefined) {
|
||||
this.setStatus({ status });
|
||||
this.setStatus({ role: this.role, status });
|
||||
}
|
||||
},
|
||||
subscribeToMyStatus() {
|
||||
@@ -141,7 +141,11 @@ export default {
|
||||
subscribeToRoleChange() {
|
||||
this.openmct.user.on('roleChanged', this.fetchMyStatus);
|
||||
},
|
||||
setStatus({ status }) {
|
||||
setStatus({ role, status }) {
|
||||
if (role !== this.role) {
|
||||
// not my role
|
||||
return;
|
||||
}
|
||||
status = this.applyStyling(status);
|
||||
this.selectedStatus = status.key;
|
||||
this.indicator.iconClass(status.iconClassPoll);
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
* rootIdentifier, and rewrites all child object identifiers so that they
|
||||
* exist in the same namespace as the rootIdentifier.
|
||||
*/
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString, parseKeyString, toNewFormat } from 'objectUtils';
|
||||
|
||||
class StaticModelProvider {
|
||||
constructor(importData, rootIdentifier) {
|
||||
@@ -38,7 +38,7 @@ class StaticModelProvider {
|
||||
* Standard "Get".
|
||||
*/
|
||||
get(identifier) {
|
||||
const keyString = objectUtils.makeKeyString(identifier);
|
||||
const keyString = makeKeyString(identifier);
|
||||
if (this.objectMap[keyString]) {
|
||||
return this.objectMap[keyString];
|
||||
}
|
||||
@@ -49,7 +49,7 @@ class StaticModelProvider {
|
||||
parseObjectLeaf(objectLeaf, idMap, newRootNamespace, oldRootNamespace) {
|
||||
Object.keys(objectLeaf).forEach((nodeKey) => {
|
||||
if (idMap.get(nodeKey)) {
|
||||
const newIdentifier = objectUtils.makeKeyString({
|
||||
const newIdentifier = makeKeyString({
|
||||
namespace: newRootNamespace,
|
||||
key: idMap.get(nodeKey)
|
||||
});
|
||||
@@ -104,7 +104,7 @@ class StaticModelProvider {
|
||||
let mappedLeafValue;
|
||||
if (oldRootNamespace) {
|
||||
mappedLeafValue = idMap.get(
|
||||
objectUtils.makeKeyString({
|
||||
makeKeyString({
|
||||
namespace: oldRootNamespace,
|
||||
key: leafValue
|
||||
})
|
||||
@@ -125,7 +125,7 @@ class StaticModelProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newLocationIdentifier = objectUtils.makeKeyString({
|
||||
const newLocationIdentifier = makeKeyString({
|
||||
namespace: newRootNamespace,
|
||||
key: mappedLeafValue
|
||||
});
|
||||
@@ -134,7 +134,7 @@ class StaticModelProvider {
|
||||
} else {
|
||||
const mappedLeafValue = idMap.get(leafValue);
|
||||
if (mappedLeafValue) {
|
||||
const newIdentifier = objectUtils.makeKeyString({
|
||||
const newIdentifier = makeKeyString({
|
||||
namespace: newRootNamespace,
|
||||
key: mappedLeafValue
|
||||
});
|
||||
@@ -147,7 +147,7 @@ class StaticModelProvider {
|
||||
}
|
||||
|
||||
rewriteObjectIdentifiers(importData, rootIdentifier) {
|
||||
const { namespace: oldRootNamespace } = objectUtils.parseKeyString(importData.rootId);
|
||||
const { namespace: oldRootNamespace } = parseKeyString(importData.rootId);
|
||||
const { namespace: newRootNamespace } = rootIdentifier;
|
||||
const idMap = new Map();
|
||||
const objectTree = importData.openmct;
|
||||
@@ -172,7 +172,7 @@ class StaticModelProvider {
|
||||
*/
|
||||
convertToNewObjects(oldObjectMap) {
|
||||
return Object.keys(oldObjectMap).reduce(function (newObjectMap, key) {
|
||||
newObjectMap[key] = objectUtils.toNewFormat(oldObjectMap[key], key);
|
||||
newObjectMap[key] = toNewFormat(oldObjectMap[key], key);
|
||||
|
||||
return newObjectMap;
|
||||
}, {});
|
||||
@@ -180,7 +180,7 @@ class StaticModelProvider {
|
||||
|
||||
/* Set the root location correctly for a top-level object */
|
||||
setRootLocation(objectMap, rootIdentifier) {
|
||||
objectMap[objectUtils.makeKeyString(rootIdentifier)].location = 'ROOT';
|
||||
objectMap[makeKeyString(rootIdentifier)].location = 'ROOT';
|
||||
|
||||
return objectMap;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import _ from 'lodash';
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import ConditionEvaluator from './ConditionEvaluator.js';
|
||||
|
||||
@@ -119,7 +119,7 @@ ConditionManager.prototype.addGlobalPropertyType = function (key, type) {
|
||||
* has completed and types have been parsed
|
||||
*/
|
||||
ConditionManager.prototype.parsePropertyTypes = function (object) {
|
||||
const objectId = objectUtils.makeKeyString(object.identifier);
|
||||
const objectId = makeKeyString(object.identifier);
|
||||
|
||||
this.telemetryTypesById[objectId] = {};
|
||||
Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) {
|
||||
@@ -182,7 +182,7 @@ ConditionManager.prototype.createNormalizedDatum = function (objId, telemetryDat
|
||||
ConditionManager.prototype.onCompositionAdd = function (obj) {
|
||||
let compositionKeys;
|
||||
const telemetryAPI = this.openmct.telemetry;
|
||||
const objId = objectUtils.makeKeyString(obj.identifier);
|
||||
const objId = makeKeyString(obj.identifier);
|
||||
let telemetryMetadata;
|
||||
const self = this;
|
||||
|
||||
@@ -191,7 +191,7 @@ ConditionManager.prototype.onCompositionAdd = function (obj) {
|
||||
self.telemetryMetadataById[objId] = {};
|
||||
|
||||
// FIXME: this should just update based on listener.
|
||||
compositionKeys = self.domainObject.composition.map(objectUtils.makeKeyString);
|
||||
compositionKeys = self.domainObject.composition.map(makeKeyString);
|
||||
if (!compositionKeys.includes(objId)) {
|
||||
self.domainObject.composition.push(obj.identifier);
|
||||
}
|
||||
@@ -245,7 +245,7 @@ ConditionManager.prototype.onCompositionAdd = function (obj) {
|
||||
* @private
|
||||
*/
|
||||
ConditionManager.prototype.onCompositionRemove = function (identifier) {
|
||||
const objectId = objectUtils.makeKeyString(identifier);
|
||||
const objectId = makeKeyString(identifier);
|
||||
// FIXME: this should just update by listener.
|
||||
_.remove(this.domainObject.composition, function (id) {
|
||||
return id.key === identifier.key && id.namespace === identifier.namespace;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import Select from './Select.js';
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function ObjectSelect(config, manager, baseOptions) {
|
||||
* @private
|
||||
*/
|
||||
function onCompositionAdd(obj) {
|
||||
self.select.addOption(objectUtils.makeKeyString(obj.identifier), obj.name);
|
||||
self.select.addOption(makeKeyString(obj.identifier), obj.name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +77,7 @@ export default function ObjectSelect(config, manager, baseOptions) {
|
||||
*/
|
||||
ObjectSelect.prototype.generateOptions = function () {
|
||||
const items = Object.values(this.compositionObjs).map(function (obj) {
|
||||
return [objectUtils.makeKeyString(obj.identifier), obj.name];
|
||||
return [makeKeyString(obj.identifier), obj.name];
|
||||
});
|
||||
this.baseOptions.forEach(function (option, index) {
|
||||
items.splice(index, 0, option);
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import SummaryWidgetEvaluator from './SummaryWidgetEvaluator.js';
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function EvaluatorPool(openmct) {
|
||||
}
|
||||
|
||||
EvaluatorPool.prototype.get = function (domainObject) {
|
||||
const objectId = objectUtils.makeKeyString(domainObject.identifier);
|
||||
const objectId = makeKeyString(domainObject.identifier);
|
||||
let poolEntry = this.byObjectId[objectId];
|
||||
if (!poolEntry) {
|
||||
poolEntry = {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import _ from 'lodash';
|
||||
import objectUtils from 'objectUtils';
|
||||
import { makeKeyString } from 'objectUtils';
|
||||
|
||||
import eventHelpers from '../eventHelpers.js';
|
||||
import SummaryWidgetRule from './SummaryWidgetRule.js';
|
||||
@@ -113,7 +113,7 @@ SummaryWidgetEvaluator.prototype.updateRules = function (domainObject) {
|
||||
};
|
||||
|
||||
SummaryWidgetEvaluator.prototype.addChild = function (childObject) {
|
||||
const childId = objectUtils.makeKeyString(childObject.identifier);
|
||||
const childId = makeKeyString(childObject.identifier);
|
||||
const metadata = this.openmct.telemetry.getMetadata(childObject);
|
||||
const formats = this.openmct.telemetry.getFormatMap(metadata);
|
||||
|
||||
@@ -126,7 +126,7 @@ SummaryWidgetEvaluator.prototype.addChild = function (childObject) {
|
||||
};
|
||||
|
||||
SummaryWidgetEvaluator.prototype.removeChild = function (childObject) {
|
||||
const childId = objectUtils.makeKeyString(childObject.identifier);
|
||||
const childId = makeKeyString(childObject.identifier);
|
||||
delete this.baseState[childId];
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import objectUtils from 'objectUtils';
|
||||
import { parseKeyString } from 'objectUtils';
|
||||
|
||||
import TelemetryAverager from './TelemetryAverager.js';
|
||||
|
||||
@@ -43,7 +43,7 @@ MeanTelemetryProvider.prototype.supportsRequest =
|
||||
MeanTelemetryProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
let wrappedUnsubscribe;
|
||||
let unsubscribeCalled = false;
|
||||
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
const objectId = parseKeyString(domainObject.telemetryPoint);
|
||||
const samples = domainObject.samples;
|
||||
|
||||
this.objectAPI
|
||||
@@ -79,7 +79,7 @@ MeanTelemetryProvider.prototype.subscribeToAverage = function (domainObject, sam
|
||||
};
|
||||
|
||||
MeanTelemetryProvider.prototype.request = function (domainObject, request) {
|
||||
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
const objectId = parseKeyString(domainObject.telemetryPoint);
|
||||
const samples = domainObject.samples;
|
||||
|
||||
return this.objectAPI.get(objectId).then(
|
||||
@@ -119,7 +119,7 @@ MeanTelemetryProvider.prototype.requestAverageTelemetry = function (
|
||||
* @private
|
||||
*/
|
||||
MeanTelemetryProvider.prototype.getLinkedObject = function (domainObject) {
|
||||
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
|
||||
const objectId = parseKeyString(domainObject.telemetryPoint);
|
||||
|
||||
return this.objectAPI.get(objectId);
|
||||
};
|
||||
|
||||
@@ -85,8 +85,6 @@ import { getFilteredValues, getValidatedData, getValidatedGroups } from '../plan
|
||||
import { SORT_ORDER_OPTIONS } from './constants.js';
|
||||
import ExpandedViewItem from './ExpandedViewItem.vue';
|
||||
|
||||
const SCROLL_TIMEOUT = 10000;
|
||||
|
||||
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
const SAME_DAY_PRECISION_SECONDS = 'HH:mm:ss';
|
||||
|
||||
@@ -209,7 +207,6 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.updateTimestamp = _.throttle(this.updateTimestamp, 1000);
|
||||
this.deferAutoScroll = _.debounce(this.deferAutoScroll, 500);
|
||||
|
||||
this.setTimeContext();
|
||||
this.timestamp = this.timeContext.now();
|
||||
@@ -238,8 +235,6 @@ export default {
|
||||
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
|
||||
this.$el.parentElement.addEventListener('scroll', this.deferAutoScroll, true);
|
||||
|
||||
if (this.composition) {
|
||||
this.composition.on('add', this.addToComposition);
|
||||
this.composition.on('remove', this.removeItem);
|
||||
@@ -272,11 +267,6 @@ export default {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
this.$el.parentElement?.removeEventListener('scroll', this.deferAutoScroll, true);
|
||||
if (this.clearAutoScrollDisabledTimer) {
|
||||
clearTimeout(this.clearAutoScrollDisabledTimer);
|
||||
}
|
||||
|
||||
if (this.composition) {
|
||||
this.composition.off('add', this.addToComposition);
|
||||
this.composition.off('remove', this.removeItem);
|
||||
@@ -429,8 +419,6 @@ export default {
|
||||
const filteredItems = this.planActivities.filter(this.filterActivities);
|
||||
const sortedItems = this.sortItems(filteredItems);
|
||||
this.sortedItems = this.applyStyles(sortedItems);
|
||||
//We need to wait for the next tick since we need the height of the row from the DOM
|
||||
this.$nextTick(this.setScrollTop);
|
||||
},
|
||||
updateTimeStampAndListActivities(time) {
|
||||
this.timestamp = time;
|
||||
@@ -509,24 +497,13 @@ export default {
|
||||
});
|
||||
},
|
||||
// Add activity classes, increase activity counts by type,
|
||||
// set indices of the first occurrences of current and future activities - used for scrolling
|
||||
styleActivity(activity, index) {
|
||||
if (this.timestamp >= activity.start && this.timestamp <= activity.end) {
|
||||
activity.cssClass = CURRENT_CSS_SUFFIX;
|
||||
if (this.firstCurrentActivityIndex < 0) {
|
||||
this.firstCurrentActivityIndex = index;
|
||||
}
|
||||
this.currentActivitiesCount = this.currentActivitiesCount + 1;
|
||||
} else if (this.timestamp < activity.start) {
|
||||
activity.cssClass = FUTURE_CSS_SUFFIX;
|
||||
//the index of the first activity that's greater than the current timestamp
|
||||
if (this.firstFutureActivityIndex < 0) {
|
||||
this.firstFutureActivityIndex = index;
|
||||
}
|
||||
this.futureActivitiesCount = this.futureActivitiesCount + 1;
|
||||
} else {
|
||||
activity.cssClass = PAST_CSS_SUFFIX;
|
||||
this.pastActivitiesCount = this.pastActivitiesCount + 1;
|
||||
}
|
||||
|
||||
if (!activity.key) {
|
||||
@@ -545,111 +522,7 @@ export default {
|
||||
return activity;
|
||||
},
|
||||
applyStyles(activities) {
|
||||
this.firstCurrentOrFutureActivityIndex = -1;
|
||||
this.firstCurrentActivityIndex = -1;
|
||||
this.firstFutureActivityIndex = -1;
|
||||
this.currentActivitiesCount = 0;
|
||||
this.pastActivitiesCount = 0;
|
||||
this.futureActivitiesCount = 0;
|
||||
|
||||
const styledActivities = activities.map(this.styleActivity);
|
||||
|
||||
if (this.firstCurrentActivityIndex > -1) {
|
||||
this.firstCurrentOrFutureActivityIndex = this.firstCurrentActivityIndex;
|
||||
} else if (this.firstFutureActivityIndex > -1) {
|
||||
this.firstCurrentOrFutureActivityIndex = this.firstFutureActivityIndex;
|
||||
}
|
||||
|
||||
return styledActivities;
|
||||
},
|
||||
canAutoScroll() {
|
||||
//this distinguishes between programmatic vs user-triggered scroll events
|
||||
this.autoScrolled = this.dontAutoScroll !== true;
|
||||
|
||||
return this.autoScrolled;
|
||||
},
|
||||
resetScroll() {
|
||||
if (this.canAutoScroll() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.firstCurrentOrFutureActivityIndex = -1;
|
||||
this.pastActivitiesCount = 0;
|
||||
this.currentActivitiesCount = 0;
|
||||
this.futureActivitiesCount = 0;
|
||||
this.$el.parentElement?.scrollTo({ top: 0 });
|
||||
this.autoScrolled = false;
|
||||
},
|
||||
setScrollTop() {
|
||||
//The view isn't ready yet
|
||||
if (!this.$el.parentElement || this.isExpanded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.canAutoScroll() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See #7167 for scrolling algorithm
|
||||
const scrollTop = this.calculateScrollOffset();
|
||||
|
||||
if (scrollTop === undefined) {
|
||||
this.resetScroll();
|
||||
} else {
|
||||
this.$el.parentElement?.scrollTo({
|
||||
top: scrollTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
this.autoScrolled = false;
|
||||
}
|
||||
},
|
||||
calculateScrollOffset() {
|
||||
let scrollTop;
|
||||
|
||||
//No scrolling necessary if no past events are present
|
||||
if (this.pastActivitiesCount > 0) {
|
||||
const row = this.$el.querySelector('.js-list-item');
|
||||
const ROW_HEIGHT = row.getBoundingClientRect().height;
|
||||
|
||||
const maxViewableActivities =
|
||||
Math.floor(this.$el.parentElement.getBoundingClientRect().height / ROW_HEIGHT) - 1;
|
||||
|
||||
const currentAndFutureActivities = this.currentActivitiesCount + this.futureActivitiesCount;
|
||||
|
||||
//If there is more viewable area than all current and future activities combined, then show some past events
|
||||
const numberOfPastEventsToShow = maxViewableActivities - currentAndFutureActivities;
|
||||
if (numberOfPastEventsToShow > 0) {
|
||||
//some past events can be shown - get that scroll index
|
||||
if (this.pastActivitiesCount > numberOfPastEventsToShow) {
|
||||
scrollTop =
|
||||
ROW_HEIGHT * (this.firstCurrentOrFutureActivityIndex + numberOfPastEventsToShow);
|
||||
}
|
||||
} else {
|
||||
// only show current and future events
|
||||
scrollTop = ROW_HEIGHT * this.firstCurrentOrFutureActivityIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return scrollTop;
|
||||
},
|
||||
deferAutoScroll() {
|
||||
//if this is not a user-triggered event, don't defer auto scrolling
|
||||
if (this.autoScrolled) {
|
||||
this.autoScrolled = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.dontAutoScroll = true;
|
||||
const self = this;
|
||||
if (this.clearAutoScrollDisabledTimer) {
|
||||
clearTimeout(this.clearAutoScrollDisabledTimer);
|
||||
}
|
||||
|
||||
this.clearAutoScrollDisabledTimer = setTimeout(() => {
|
||||
self.dontAutoScroll = false;
|
||||
self.setScrollTop();
|
||||
}, SCROLL_TIMEOUT);
|
||||
return activities.map(this.styleActivity);
|
||||
},
|
||||
setSort() {
|
||||
const sortOrder = SORT_ORDER_OPTIONS[this.domainObject.configuration.sortOrderIndex];
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
class="c-tree__item__label c-object-label"
|
||||
:class="[statusClass]"
|
||||
draggable="true"
|
||||
:aria-label="ariaLabel"
|
||||
@dragstart="dragStart"
|
||||
@click="navigateOrPreview"
|
||||
>
|
||||
@@ -47,7 +48,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import tooltipHelpers from '../../api/tooltips/tooltipMixins.js';
|
||||
import { useIsEditing } from '../../ui/composables/edit.js';
|
||||
import ContextMenuGesture from '../mixins/context-menu-gesture.js';
|
||||
import ObjectLink from '../mixins/object-link.js';
|
||||
import PreviewAction from '../preview/PreviewAction.js';
|
||||
@@ -76,6 +80,13 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const openmct = inject('openmct');
|
||||
const { isEditing } = useIsEditing(openmct);
|
||||
return {
|
||||
isEditing
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
status: ''
|
||||
@@ -92,6 +103,9 @@ export default {
|
||||
},
|
||||
statusClass() {
|
||||
return this.status ? `is-status--${this.status}` : '';
|
||||
},
|
||||
ariaLabel() {
|
||||
return `${this.isEditing ? 'Preview' : 'Navigate to'} ${this.domainObject.name} ${this.domainObject.type} Object`;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -33,7 +33,7 @@ import StyleRuleManager from '@/plugins/condition/StyleRuleManager';
|
||||
import { STYLE_CONSTANTS } from '@/plugins/condition/utils/constants';
|
||||
import stalenessMixin from '@/ui/mixins/staleness-mixin';
|
||||
|
||||
import objectUtils from '../../api/objects/object-utils.js';
|
||||
import { objectEquals } from '../../api/objects/object-utils.js';
|
||||
import VisibilityObserver from '../../utils/visibility/VisibilityObserver.js';
|
||||
|
||||
export default {
|
||||
@@ -224,7 +224,7 @@ export default {
|
||||
this.updateView(true);
|
||||
},
|
||||
reload(domainObjectToReload) {
|
||||
if (objectUtils.equals(domainObjectToReload, this.domainObject)) {
|
||||
if (objectEquals(domainObjectToReload, this.domainObject)) {
|
||||
this.updateView(true);
|
||||
this.initObjectStyles();
|
||||
this.triggerStalenessSubscribe(this.domainObject);
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
&:before {
|
||||
// Mag glass icon
|
||||
content: $glyph-icon-magnify;
|
||||
body.mobile & { // Make search icon stand out in mobile
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__use-regex {
|
||||
@@ -47,15 +50,26 @@
|
||||
display: none;
|
||||
order: 99;
|
||||
padding: 1px 0;
|
||||
body.mobile & {
|
||||
display: block;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
body.mobile & { // In mobile, persist the expanded search bar instead of collapsing upon clicking away
|
||||
background-color: rgba($colorHeadFg, 0.2) !important;
|
||||
width: 50vw !important;
|
||||
}
|
||||
.c-search__use-regex {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
width: 0;
|
||||
body.mobile & {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='text'],
|
||||
|
||||
43
src/ui/composables/edit.js
Normal file
43
src/ui/composables/edit.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useEventEmitter } from './event.js';
|
||||
|
||||
/**
|
||||
* Provides a reactive `isEditing` property that reflects the current editing state of the
|
||||
* application.
|
||||
* @param {OpenMCT} openmct the Open MCT API
|
||||
* @returns {{
|
||||
* isEditing: import('vue').Ref<boolean>
|
||||
* }}
|
||||
*/
|
||||
export function useIsEditing(openmct) {
|
||||
const isEditing = ref(openmct.editor.isEditing());
|
||||
|
||||
useEventEmitter(openmct.editor, 'isEditing', (_isEditing) => {
|
||||
isEditing.value = _isEditing;
|
||||
});
|
||||
|
||||
return { isEditing };
|
||||
}
|
||||
@@ -105,7 +105,13 @@
|
||||
class="l-shell__tree"
|
||||
/>
|
||||
</pane>
|
||||
<pane handle="before" label="Recently Viewed" :persist-position="true">
|
||||
<pane
|
||||
handle="before"
|
||||
label="Recently Viewed"
|
||||
:persist-position="true"
|
||||
collapse-type="horizontal"
|
||||
hide-param="hideRecents"
|
||||
>
|
||||
<RecentObjectsList
|
||||
ref="recentObjectsList"
|
||||
class="l-shell__tree"
|
||||
|
||||
@@ -56,6 +56,10 @@ const LOCAL_STORAGE_KEY__PANE_POSITIONS = 'mct-pane-positions';
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
collapseType: {
|
||||
type: String,
|
||||
default: 'vertical'
|
||||
},
|
||||
handle: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -107,6 +111,7 @@ export default {
|
||||
'l-pane--vertical-handle-before': this.type === 'vertical' && this.handle === 'before',
|
||||
'l-pane--vertical-handle-after': this.type === 'vertical' && this.handle === 'after',
|
||||
'l-pane--collapsed': this.collapsed,
|
||||
'collapse-horizontal': this.collapseType === 'horizontal',
|
||||
'l-pane--reacts': !this.handle,
|
||||
'l-pane--resizing': this.resizing === true
|
||||
};
|
||||
@@ -177,7 +182,9 @@ export default {
|
||||
this.collapsed = true;
|
||||
},
|
||||
handleExpand() {
|
||||
this.$el.style[this.styleProp] = this.currentSize;
|
||||
let size = this.currentSize ? this.currentSize : this.getSavedPosition();
|
||||
this.$el.style[this.styleProp] = size;
|
||||
|
||||
delete this.currentSize;
|
||||
delete this.dragCollapse;
|
||||
this.collapsed = false;
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__head,
|
||||
//&__head,
|
||||
&__pane-inspector {
|
||||
body.mobile & {
|
||||
display: none;
|
||||
@@ -189,6 +189,18 @@
|
||||
margin-bottom: $interiorMargin; // Needs some additional visual separation
|
||||
}
|
||||
|
||||
&__head {
|
||||
body.mobile & {
|
||||
.c-create-button,
|
||||
.c-create-menu,
|
||||
.c-indicator,
|
||||
.c-icon-button {
|
||||
// Making status area visible, but hiding some widgets for mobile.
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.mobile & .l-shell__main-view-browse-bar {
|
||||
margin-left: $mobileMenuIconD; // Make room for the hamburger!
|
||||
.c-button[class*='__actions__edit'] {
|
||||
@@ -241,7 +253,20 @@
|
||||
border-left: $brdr;
|
||||
align-items: start;
|
||||
$p: $interiorMarginSm;
|
||||
padding-left: $p; padding-right: $p;
|
||||
padding-left: $p;
|
||||
padding-right: $p;
|
||||
}
|
||||
|
||||
&__create-button,
|
||||
&__app-logo {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__create-button {
|
||||
body.mobile & {
|
||||
// Fixes weird gap in mobile status area
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&__indicators {
|
||||
@@ -254,7 +279,6 @@
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/******************************* MAIN AREA */
|
||||
@@ -355,14 +379,18 @@
|
||||
.is-editing {
|
||||
.l-shell__main-container {
|
||||
$m: 3px;
|
||||
box-shadow: $colorBodyBg 0 0 0 1px, $editUIAreaShdw;
|
||||
box-shadow:
|
||||
$colorBodyBg 0 0 0 1px,
|
||||
$editUIAreaShdw;
|
||||
margin-left: $m;
|
||||
margin-right: $m;
|
||||
top: $shellToolBarH + $shellMainBrowseBarH + $interiorMarginLg !important;
|
||||
|
||||
&[s-selected] {
|
||||
// Provide a clearer selection context articulation for the main edit area
|
||||
box-shadow: $colorBodyBg 0 0 0 1px, $editUIAreaShdwSelected;
|
||||
box-shadow:
|
||||
$colorBodyBg 0 0 0 1px,
|
||||
$editUIAreaShdwSelected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,9 @@
|
||||
&:before {
|
||||
// '+' icon
|
||||
font-size: 0.8em;
|
||||
margin-bottom: $interiorMarginSm;
|
||||
margin-bottom: $interiorMarginSm; // margin-bottom is needed for Tree and Inspector
|
||||
margin-right: $interiorMarginSm; // margin-right and margin-left are needed for Recent Objects
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -191,6 +193,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--collapsed'] { // For Recent Objects Button
|
||||
&.collapse-horizontal {
|
||||
[class*='expand-button'] {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
border-top-right-radius: $controlCr;
|
||||
border-top-left-radius: $controlCr;
|
||||
}
|
||||
}
|
||||
[class*='expand-button'] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
padding: $interiorMarginSm 1px;
|
||||
font-size: 11px;
|
||||
|
||||
[class*='label'] {
|
||||
text-orientation: mixed;
|
||||
text-transform: uppercase;
|
||||
writing-mode: horizontal-tb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--horizontal'] {
|
||||
> .l-pane__handle {
|
||||
cursor: col-resize;
|
||||
@@ -256,7 +288,7 @@
|
||||
}
|
||||
|
||||
/************************** Horizontal Splitter After */
|
||||
// Example: Tree pane
|
||||
// Example: Tree pane and Recent Objects
|
||||
&[class*='-after'] {
|
||||
margin-right: nth($shellPanePad, 2);
|
||||
padding-right: nth($shellPanePad, 2);
|
||||
@@ -292,7 +324,7 @@
|
||||
}
|
||||
|
||||
/************************** Vertical Splitter Before */
|
||||
// Pane collapses downward. Used by Elements pool in Inspector
|
||||
// Pane collapses downward. Used by Recent Objects in Tree
|
||||
&[class*='-before'] {
|
||||
$m: $interiorMarginLg;
|
||||
margin-top: $m;
|
||||
@@ -303,7 +335,7 @@
|
||||
}
|
||||
|
||||
.l-pane__collapse-button:before {
|
||||
content: $glyph-icon-arrow-down;
|
||||
content: $glyph-icon-line-horz;
|
||||
}
|
||||
|
||||
&.l-pane--collapsed {
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
aria-label="Search Results Dropdown"
|
||||
class="c-gsearch__dropdown"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="c-gsearch__results-close c-click-icon c-overlay__close-button icon-x"
|
||||
@click="selectedResult"
|
||||
></button>
|
||||
<div class="c-gsearch__results" :class="{ 'search-finished': !searchLoading }">
|
||||
<div
|
||||
v-if="objectResults?.length"
|
||||
|
||||
@@ -28,6 +28,19 @@
|
||||
background: rgba($colorHeadFg, 0.2);
|
||||
box-shadow: none;
|
||||
flex: 1 1 auto;
|
||||
body.mobile & {
|
||||
@include phonePortrait() {
|
||||
// This logic of expanding the search input upon click only happens in mobile portrait mode
|
||||
background: $colorHeadBg;
|
||||
width: 15%;
|
||||
&:hover {
|
||||
// When clicked, expand the search bar
|
||||
background-color: rgba($colorHeadFg, 0.2);
|
||||
width: 50vw;
|
||||
transition: width 120ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +53,17 @@
|
||||
max-height: 500px;
|
||||
top: $formInputH;
|
||||
z-index: 60;
|
||||
body.mobile & {
|
||||
// Makes search in mobile look less like an overlay, and more fullscreen.
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: -12px;
|
||||
height: calc(100vh - 22px);
|
||||
min-width: 100vw;
|
||||
max-height: none;
|
||||
border-radius: 0px;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__results,
|
||||
@@ -55,6 +79,10 @@
|
||||
> * + * {
|
||||
margin-top: $interiorMarginLg;
|
||||
}
|
||||
body.mobile & {
|
||||
// Add a margin to results so we have room for the close button
|
||||
width: 93%;
|
||||
}
|
||||
}
|
||||
|
||||
&__results-section {
|
||||
@@ -65,6 +93,7 @@
|
||||
|
||||
&__results-section-title {
|
||||
@include propertiesHeader();
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
&__result-pane-msg {
|
||||
@@ -72,6 +101,22 @@
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
&__results-close {
|
||||
// Close button that appears for mobile only
|
||||
display: none;
|
||||
body.mobile & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
body.mobile & {
|
||||
width: 50vw;
|
||||
@include phonePortrait() {
|
||||
// This logic only appears for a mobile portrait mode
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-gsearch-result {
|
||||
@@ -113,9 +158,9 @@
|
||||
}
|
||||
|
||||
.c-location {
|
||||
//color: $colorBodyFg;
|
||||
//font-size: 0.9em;
|
||||
//opacity: 0.8;
|
||||
color: $colorBodyFg;
|
||||
font-size: 0.9em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import { isProxy, toRaw } from 'vue';
|
||||
|
||||
import { isIdentifier } from '@/api/objects/object-utils';
|
||||
import StalenessUtils from '@/utils/staleness';
|
||||
|
||||
export default {
|
||||
@@ -40,7 +41,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getSubscriptionId(domainObject) {
|
||||
return this.openmct?.objects.makeKeyString(domainObject.identifier);
|
||||
// Only extract the identifier if it is not already an identifier
|
||||
const identifier = isIdentifier(domainObject) ? domainObject : domainObject.identifier;
|
||||
return this.openmct?.objects.makeKeyString(identifier);
|
||||
},
|
||||
setupClockChangedEvent(callback) {
|
||||
this.setupClockChanged = true;
|
||||
@@ -97,6 +100,7 @@ export default {
|
||||
if (isProxy(domainObject)) {
|
||||
domainObject = toRaw(domainObject);
|
||||
}
|
||||
|
||||
const id = this.getSubscriptionId(domainObject);
|
||||
if (!this.stalenessSubscription[id]) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user