Compare commits

..

52 Commits

Author SHA1 Message Date
Shefali
3ccf333fc6 Update event name 2023-03-31 15:21:36 -07:00
Shefali
f3fc2c3471 bubble current sort up to timelist view 2023-03-31 15:01:07 -07:00
Shefali
2f715492e5 Sort by specified sort 2023-03-31 14:36:15 -07:00
Shefali
2f6a1840ac Sort activities before filtering them 2023-03-31 13:47:14 -07:00
Shefali
ef86e023e9 Sort before getting scroll position 2023-03-31 11:23:23 -07:00
dependabot[bot]
f8186e4b4e chore(deps-dev): bump karma-sourcemap-loader from 0.3.8 to 0.4.0 (#6290)
Bumps [karma-sourcemap-loader](https://github.com/demerzel3/karma-sourcemap-loader) from 0.3.8 to 0.4.0.
- [Release notes](https://github.com/demerzel3/karma-sourcemap-loader/releases)
- [Changelog](https://github.com/demerzel3/karma-sourcemap-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/demerzel3/karma-sourcemap-loader/commits/0.4.0)

---
updated-dependencies:
- dependency-name: karma-sourcemap-loader
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-31 02:53:09 -07:00
dependabot[bot]
4e0c364d89 chore(deps-dev): bump sass-loader from 13.2.1 to 13.2.2 (#6513)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 13.2.1 to 13.2.2.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v13.2.1...v13.2.2)

---
updated-dependencies:
- dependency-name: sass-loader
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-31 04:04:35 +00:00
dependabot[bot]
f3bed9c651 chore(deps-dev): bump webpack from 5.76.3 to 5.77.0 (#6520)
Bumps [webpack](https://github.com/webpack/webpack) from 5.76.3 to 5.77.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.76.3...v5.77.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-30 20:54:11 -07:00
dependabot[bot]
4d93907d58 chore(deps-dev): bump eslint-plugin-compat from 4.1.1 to 4.1.2 (#6352)
Bumps [eslint-plugin-compat](https://github.com/amilajack/eslint-plugin-compat) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/amilajack/eslint-plugin-compat/releases)
- [Changelog](https://github.com/amilajack/eslint-plugin-compat/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amilajack/eslint-plugin-compat/compare/v4.1.1...v4.1.2)

---
updated-dependencies:
- dependency-name: eslint-plugin-compat
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-03-30 15:37:59 -07:00
John Hill
6f656a6783 [Build]Remove Node14LTS from supported versions and update our pipelines (#6527)
* bump minimumum and pin drivebys

* move to 16 and remove 14

* remove 14

* update to latest
2023-03-30 20:53:44 +00:00
Jesse Mazzella
767fb6c5fd fix: remove redundant request on FaultManagement mount (#6502)
* fix: remove redundant update request

* fix: handle case where request returns no faults

* test: fix fault management tests

* docs: clean up FaultManagement API types

---------

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
2023-03-30 18:43:55 +00:00
dependabot[bot]
b0a0b4bb58 chore(deps-dev): bump eslint from 8.36.0 to 8.37.0 (#6521)
Bumps [eslint](https://github.com/eslint/eslint) from 8.36.0 to 8.37.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.36.0...v8.37.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-03-30 11:15:36 -07:00
dependabot[bot]
340f4a9e79 chore(deps-dev): bump @percy/cli from 1.17.0 to 1.21.0 (#6439)
Bumps [@percy/cli](https://github.com/percy/cli/tree/HEAD/packages/cli) from 1.17.0 to 1.21.0.
- [Release notes](https://github.com/percy/cli/releases)
- [Commits](https://github.com/percy/cli/commits/v1.21.0/packages/cli)

---
updated-dependencies:
- dependency-name: "@percy/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-03-30 11:05:32 -07:00
Scott Bell
3007b28b0f Simple text export of Notebook (#6510)
* add simple prototype

* tags and metadata now exported

* add form for options

* revert notebook

* add simple e2e test

* add test stubs

* death to debug
2023-03-30 19:44:12 +02:00
David Tsay
20789601b4 Add contextual domain object back for contextual row actions (#6524)
* re-enable historical row action
2023-03-30 10:08:09 -07:00
Shefali Joshi
a56cfed732 Remove ticker and rely solely on the clock ticks to update the timelist durations (#6495)
* Remove ticker for timelist and rename a function for readability

* Use formatting for remote clock if available.

* throttle updates to the timestamp and listing activities

---------

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2023-03-29 15:16:52 -07:00
Charles Hacskaylo
7ec2c4475b LAD Tables now disallow user-select (#6322)
* Closes #6321
- Set `user-select: none` on LAD Table td elements.
- Added hover effect to LAD Table rows as affordance of the Action
menu's availability.

* Add test to ensure rows cannot be selected but do show context menus

---------

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2023-03-28 14:38:52 -07:00
dependabot[bot]
8f59b16465 chore(deps-dev): bump @types/lodash from 4.14.191 to 4.14.192 (#6512)
Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.14.191 to 4.14.192.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

---
updated-dependencies:
- dependency-name: "@types/lodash"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 16:09:39 +00:00
Jamie V
36cfb1d515 [Condition Widgets] Keep styles for widgets with a URL (#6515)
* call update style after view is update with current style rule manager styles for components (namely condition widget) that have DOM changes after the element is grabbed to style

* target blank yo

---------

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2023-03-28 15:58:15 +00:00
David Tsay
2ff7132e90 use relative path (#6518) 2023-03-27 23:52:54 +00:00
Khalid Adil
d0ca398e01 [Plots, Multiple-Y Axis] Fix for rectangle drawn on each axis (#6490)
* Change logic so that the rectangle is only drawn on one axis

* Filter firstDrawableAxis before the rest of the logic

* Update to filter yAxisIds by the canDraw function
2023-03-24 13:46:10 -07:00
Jesse Mazzella
59278e8a06 chore: bump version to 2.2.1-SNAPSHOT (#6501) 2023-03-23 16:27:31 -07:00
dependabot[bot]
c8377f392b chore(deps-dev): bump eslint-plugin-vue from 9.9.0 to 9.10.0 (#6500)
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 9.9.0 to 9.10.0.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v9.9.0...v9.10.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-vue
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-03-23 15:54:10 -07:00
Vitor Henckel
29df748f2b fix(#6457): Tags disappear after resizing then dismissing the Add Tag select (#6499) 2023-03-23 14:48:56 -07:00
Vitor Henckel
665ba6dae1 fix(#6347): Searching a Notebook with whitelisted links for '.' exposes some html (#6396) 2023-03-23 21:23:49 +00:00
David Tsay
f39f8df4e2 Suppress annotations tab if no annotations defined (#6498)
* fix bad copy paste

* suppress annotations inspector tab if no tags
2023-03-23 14:00:46 -07:00
Marcelo Arias
4aa572d489 Button to clear the recent objects list (#6327) 2023-03-23 19:53:01 +00:00
Jesse Mazzella
0b24c4f2c5 fix(#6488): better determination of child tree items when collapsing a parent (#6489)
Splits the parent and child navigationPaths into arrays of keystrings and then checks to ensure that every keystring in the parent path is included in the child path in order
2023-03-23 19:02:44 +00:00
dependabot[bot]
e4657f79cd chore(deps-dev): bump plotly.js-basic-dist from 2.17.0 to 2.20.0 (#6438)
Bumps [plotly.js-basic-dist](https://github.com/plotly/plotly.js) from 2.17.0 to 2.20.0.
- [Release notes](https://github.com/plotly/plotly.js/releases)
- [Changelog](https://github.com/plotly/plotly.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.js/compare/v2.17.0...v2.20.0)

---
updated-dependencies:
- dependency-name: plotly.js-basic-dist
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-03-22 22:09:15 +00:00
dependabot[bot]
f2059406e0 chore(deps-dev): bump webpack from 5.76.2 to 5.76.3 (#6494)
Bumps [webpack](https://github.com/webpack/webpack) from 5.76.2 to 5.76.3.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.76.2...v5.76.3)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-22 13:35:24 -07:00
dependabot[bot]
3e3dc7dd83 chore(deps-dev): bump webpack from 5.74.0 to 5.76.2 (#6440)
Bumps [webpack](https://github.com/webpack/webpack) from 5.74.0 to 5.76.2.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.74.0...v5.76.2)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-22 19:22:53 +00:00
Charles Hacskaylo
50742c4f82 Hide Flexible Layout header buttons when frame is hidden (#6486)
- Hide nested frame header buttons for Flexible Layouts as well as Display Layouts.
- Adjust z-index of frame control header buttons.
2023-03-21 17:24:41 -07:00
Vitor Henckel
2f04add2a3 fix(#6408): Zooming in the corner of an image makes it fly away (#6410)
* fix(#6480): Zooming in the corner of an image makes it fly away

* fix: keeping layers in the right place even when zoomed in
2023-03-22 00:15:02 +00:00
Charles Hacskaylo
0ce5060246 Fix Imagery local controls z-index (#6482)
- Corrected z-index for imagery local controls.
2023-03-22 00:02:34 +00:00
Scott Bell
00353cdccf Cancel annotation selections if you click outside the plot component (#6476)
* resolve conflicts

* resolve conflicts

* more selectively add listeners

* add and fix tests

* address PR review comments

* test(e2e): stabilize flaky overlayPlot test

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2023-03-21 16:53:24 -07:00
dependabot[bot]
a1ac209d74 chore(deps-dev): bump plotly.js-gl2d-dist from 2.17.1 to 2.20.0 (#6441)
Bumps [plotly.js-gl2d-dist](https://github.com/plotly/plotly.js) from 2.17.1 to 2.20.0.
- [Release notes](https://github.com/plotly/plotly.js/releases)
- [Changelog](https://github.com/plotly/plotly.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.js/compare/v2.17.1...v2.20.0)

---
updated-dependencies:
- dependency-name: plotly.js-gl2d-dist
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-21 13:57:59 -07:00
dependabot[bot]
bdd8477b54 chore(deps-dev): bump sass from 1.57.1 to 1.59.3 (#6442)
Bumps [sass](https://github.com/sass/dart-sass) from 1.57.1 to 1.59.3.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.57.1...1.59.3)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-21 19:27:17 +00:00
dependabot[bot]
f690f36bfb chore(deps-dev): bump sass-loader from 13.2.0 to 13.2.1 (#6480)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 13.2.0 to 13.2.1.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v13.2.0...v13.2.1)

---
updated-dependencies:
- dependency-name: sass-loader
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-03-21 12:11:42 -07:00
Shefali Joshi
e174f075df Don't initialize the selected condition style when a view is loaded (#6478)
* Don't initialize the selected condition style when a view is loaded

* Ensure selection of styles in Edit mode works as expected. When out of edit mode, only a computed style should be chosen to display.
2023-03-20 17:06:10 -07:00
Michael Rogers
8cf12db104 Add a view action button to toggle on class to control fixed layout (#6465)
* Add a view action buttons to toggle on class to control fixed layout
* Add configuration watcher and initial view action
* Added next tick in mount and updated action key
* Updated the view action key
2023-03-17 15:30:35 -07:00
David Tsay
453b1f3009 fixes to entries (#6464)
* fixes to entries
fix delete first notebook entry
fix select unfocused on create
* do not blur if nothing is focused
2023-03-17 22:14:15 +00:00
Jesse Mazzella
201c669328 fix(#6455): fix Create modal tree infinite loop (#6462)
* fix(#6455): fix infinite loop

- When the Create modal is opened, it defaults the object selected in the tree to the parent of the currently selected object. However, if this object is static, it can sometimes have a weird navigationPath and an edge case where we try to infinitely walk up the path to find the parent.

- This adds a fail-safe to verify that the navigationPath by this point contains `/browse/mine` (thus is within a creatable path). If not, it sets the default selected tree item to the "My Items" folder

---------

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2023-03-17 22:03:23 +00:00
Shefali Joshi
1b7fb9b952 Ensure that datum that is of type array is formatted as such (#6467)
* Ensure that datum that is of type array is formatted as such
* Ensure the requestEnded event is emitted even when historicalData is empty.
2023-03-17 14:22:51 -07:00
Charles Hacskaylo
a3c5450205 Improvements to reduce repainting (#5876) 2023-03-17 20:07:57 +00:00
Scott Bell
8831b75c5d Clicking a plot annotation should pause the plot (#6446)
* free plot on search selection and remove rectangles when resuming

* add test

* remove rectanges

* update test

* more reliable way to load annotations

* use event to wait for axes and update tests

* restore test

* cancel selection in plots if clicking outside plot

* Revert "cancel selection in plots if clicking outside plot"

This reverts commit 82ea50152b.

* Listen to the navigation triggered selection of the target object before selecting the annotations for the object

* remove commented out code

* check if we've already navigated to the object

---------

Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
Co-authored-by: Shefali <simplyrender@gmail.com>
2023-03-17 20:12:52 +01:00
dependabot[bot]
8fe0472af2 chore(deps-dev): bump mini-css-extract-plugin from 2.7.2 to 2.7.5 (#6463)
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.7.2 to 2.7.5.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.7.2...v2.7.5)

---
updated-dependencies:
- dependency-name: mini-css-extract-plugin
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-03-17 09:44:28 -07:00
Scott Bell
6cb5c47f3a Conditional set output is wrong (#6244)
* Only use default if we've evaluated as default
* Add e2e test for conditional sets
---------

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2023-03-17 15:58:09 +00:00
Khalid Adil
eff0cc96b9 Add check for minimized notification (#6431) 2023-03-17 15:46:09 +00:00
Michael Rogers
6ac7f24c63 Event limit severity css classes (#6445)
* Duplicated event limit css classes

* Closes akhenry/openmct-yamcs#287
- New theme constant values for event styling.
- CSS def for `is-event*` classes moved to correct location.

---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
2023-03-17 15:24:35 +00:00
Jamie V
39463c515f [Greedy LAD] Add new functionality for Latest Available Data views (#6432)
* adding greedyLAD logic to telemetry collections
---------

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2023-03-16 19:12:11 -07:00
Jamie V
25c0dab346 [Remove Action][Move Action] Update logic when working with locked, aliased domain objects (#6384)
* Allow move action for locked shift logs.
* Allow remove action for links to locked shift logs.
---------

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2023-03-16 13:53:25 -07:00
Michael Rogers
3714958627 Set table to fixed layout and ellipted overflowing cells (#6453) 2023-03-16 15:16:46 -05:00
96 changed files with 1220 additions and 462 deletions

View File

@@ -175,11 +175,11 @@ workflows:
overall-circleci-commit-status: #These jobs run on every commit
jobs:
- lint:
name: node14-lint
node-version: lts/fermium
name: node16-lint
node-version: lts/gallium
- unit-test:
name: node18-chrome
node-version: "18"
node-version: lts/hydrogen
- e2e-test:
name: e2e-stable
node-version: lts/gallium
@@ -191,15 +191,12 @@ workflows:
the-nightly: #These jobs do not run on PRs, but against master at night
jobs:
- unit-test:
name: node14-chrome-nightly
node-version: lts/fermium
- unit-test:
name: node16-chrome-nightly
node-version: lts/gallium
- unit-test:
name: node18-chrome
node-version: "18"
node-version: lts/hydrogen
- npm-audit:
node-version: lts/gallium
- e2e-test:

View File

@@ -16,7 +16,6 @@ jobs:
- macos-latest
- windows-latest
node_version:
- 14
- 16
- 18
architecture:

View File

@@ -52,10 +52,9 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
//Set object identifier from url
conditionSetUrl = page.url();
console.log('conditionSetUrl ' + conditionSetUrl);
getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0];
console.debug('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl);
console.debug(`getConditionSetIdentifierFromUrl: ${getConditionSetIdentifierFromUrl}`);
await page.close();
});
@@ -246,4 +245,81 @@ test.describe('Basic Condition Set Use', () => {
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
});
test('ConditionSet should output blank instead of the default value', async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'networkidle' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("Sine Wave Generator")`);
await page.getByRole('spinbutton', { name: 'Loading Delay (ms)' }).fill('8000');
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill("Delayed Sine Wave Generator");
// Click OK button and wait for Navigate event
await Promise.all([
page.waitForLoadState(),
page.click('[aria-label="Save"]'),
// Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
// Create a new condition set
await createDomainObjectWithDefaults(page, {
type: 'Condition Set',
name: "Test Blank Output of Condition Set"
});
// Change the object to edit mode
await page.locator('[title="Edit"]').click();
// Click Add Condition button twice
await page.locator('#addCondition').click();
await page.locator('#addCondition').click();
await page.locator('#conditionCollection').getByRole('textbox').nth(0).fill('First Condition');
await page.locator('#conditionCollection').getByRole('textbox').nth(1).fill('Second Condition');
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator to the Condition Set and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { name: "Delayed Sine Wave Generator"});
const conditionCollection = await page.locator('#conditionCollection');
await sineWaveGeneratorTreeItem.dragTo(conditionCollection);
const firstCriterionTelemetry = await page.locator('[aria-label="Criterion Telemetry Selection"] >> nth=0');
firstCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' });
const secondCriterionTelemetry = await page.locator('[aria-label="Criterion Telemetry Selection"] >> nth=1');
secondCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' });
const firstCriterionMetadata = await page.locator('[aria-label="Criterion Metadata Selection"] >> nth=0');
firstCriterionMetadata.selectOption({ label: 'Sine' });
const secondCriterionMetadata = await page.locator('[aria-label="Criterion Metadata Selection"] >> nth=1');
secondCriterionMetadata.selectOption({ label: 'Sine' });
const firstCriterionComparison = await page.locator('[aria-label="Criterion Comparison Selection"] >> nth=0');
firstCriterionComparison.selectOption({ label: 'is greater than or equal to' });
const secondCriterionComparison = await page.locator('[aria-label="Criterion Comparison Selection"] >> nth=1');
secondCriterionComparison.selectOption({ label: 'is less than' });
const firstCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=0');
await firstCriterionInput.fill("0");
const secondCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=1');
await secondCriterionInput.fill("0");
const saveButtonLocator = page.locator('button[title="Save"]');
await saveButtonLocator.click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
const outputValue = await page.locator('[aria-label="Current Output Value"]');
await expect(outputValue).toHaveText('---');
});
});

View File

@@ -22,6 +22,7 @@
const { test, expect } = require('../../../../pluginFixtures');
const utils = require('../../../../helper/faultUtils');
const { selectInspectorTab } = require('../../../../appActions');
test.describe('The Fault Management Plugin using example faults', () => {
test.beforeEach(async ({ page }) => {
@@ -38,6 +39,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector @unstable', async ({ page }) => {
await utils.selectFaultItem(page, 1);
await selectInspectorTab(page, 'Fault Management Configuration');
const selectedFaultName = await page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname').textContent();
const inspectorFaultNameCount = await page.locator(`.c-inspector__properties >> :text("${selectedFaultName}")`).count();
@@ -52,6 +54,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
const selectedRows = page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname');
expect.soft(await selectedRows.count()).toEqual(2);
await selectInspectorTab(page, 'Fault Management Configuration');
const firstSelectedFaultName = await selectedRows.nth(0).textContent();
const secondSelectedFaultName = await selectedRows.nth(1).textContent();
const firstNameInInspectorCount = await page.locator(`.c-inspector__properties >> :text("${firstSelectedFaultName}")`).count();

View File

@@ -27,26 +27,29 @@ test.describe('Testing LAD table configuration', () => {
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
// Create Sine Wave Generator
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: "Test Sine Wave Generator"
});
// Create LAD table
await createDomainObjectWithDefaults(page, {
const ladTable = await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
name: "Test LAD Table"
});
// Create Sine Wave Generator
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: "Test Sine Wave Generator",
parent: ladTable.uuid
});
await page.goto(ladTable.url);
});
test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => {
// Edit LAD table
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the LAD table and save changes
await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
// // Expand the 'My Items' folder in the left tree
// await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// // Add the Sine Wave Generator to the LAD table and save changes
// await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
// select configuration tab in inspector
await selectInspectorTab(page, 'LAD Table Configuration');
@@ -113,6 +116,24 @@ test.describe('Testing LAD table configuration', () => {
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
});
test('LAD Tables don\'t allow selection of rows but does show context click menus', async ({ page }) => {
const cell = await page.locator('.js-first-data');
const userSelectable = await cell.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue('user-select');
});
expect(userSelectable).toBe('none');
// Right-click on the LAD table row
await cell.click({
button: 'right'
});
const menuOptions = page.locator('.c-menu ul');
await expect.soft(menuOptions).toContainText('View Full Datum');
await expect.soft(menuOptions).toContainText('View Historical Data');
await expect.soft(menuOptions).toContainText('Remove');
// await page.locator('li[title="Remove this object from its containing object."]').click();
});
});
test.describe('Testing LAD table @unstable', () => {

View File

@@ -377,4 +377,31 @@ test.describe('Notebook entry tests', () => {
expect.soft(await sanitizedLink.count()).toBe(1);
expect(await unsanitizedLink.count()).toBe(0);
});
test('can export notebook as text', async ({ page }) => {
await nbUtils.enterTextEntry(page, `Foo bar entry`);
// Click on 3 Dot Menu
await page.locator('button[title="More options"]').click();
const downloadPromise = page.waitForEvent('download');
await page.getByRole('menuitem', { name: /Export Notebook as Text/ }).click();
await page.getByRole('button', { name: 'Save' }).click();
const download = await downloadPromise;
const readStream = await download.createReadStream();
const exportedText = await streamToString(readStream);
expect(exportedText).toContain('Foo bar entry');
});
test.fixme('can export multiple notebook entries as text ', async ({ page }) => {});
test.fixme('can export all notebook entry metdata', async ({ page }) => {});
test.fixme('can export all notebook tags', async ({ page }) => {});
test.fixme('can export all notebook snapshots', async ({ page }) => {});
async function streamToString(readable) {
let result = '';
for await (const chunk of readable) {
result += chunk;
}
return result;
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -268,6 +268,9 @@ async function getCanvasPixelsWithData(page) {
* @param {import('@playwright/test').Page} page
*/
async function assertLimitLinesExistAndAreVisible(page) {
// Wait for plot series data to load
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
// Wait for limit lines to be created
await page.waitForSelector('.js-limit-area', { state: 'attached' });
const limitLineCount = await page.locator('.c-plot-limit-line').count();
// There should be 10 limit lines created by default

View File

@@ -115,6 +115,9 @@ test.describe('Stacked Plot', () => {
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
// collapse inspector
await page.locator('.l-shell__pane-inspector .l-pane__collapse-button').click();
// Save (exit edit mode)
await page.locator('button[title="Save"]').click();
await page.locator('li[title="Save and Finish Editing"]').click();

View File

@@ -28,6 +28,14 @@ const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, setRealTimeMode, setFixedTimeMode } = require('../../../../appActions');
test.describe('Plot Tagging', () => {
/**
* Given a canvas and a set of points, tags the points on the canvas.
* @param {import('@playwright/test').Page} page
* @param {HTMLCanvasElement} canvas a telemetry item with a plot
* @param {Number} xEnd a telemetry item with a plot
* @param {Number} yEnd a telemetry item with a plot
* @returns {Promise}
*/
async function createTags({page, canvas, xEnd, yEnd}) {
await canvas.hover({trial: true});
@@ -64,12 +72,20 @@ test.describe('Plot Tagging', () => {
await page.getByText('Science').click();
}
async function testTelemetryItem(page, canvas, telemetryItem) {
/**
* Given a telemetry item (e.g., a Sine Wave Generator) with a plot, tests that the plot can be tagged.
* @param {import('@playwright/test').Page} page
* @param {import('../../../../appActions').CreatedObjectInfo} telemetryItem a telemetry item with a plot
* @returns {Promise}
*/
async function testTelemetryItem(page, telemetryItem) {
// Check that telemetry item also received the tag
await page.goto(telemetryItem.url);
await expect(page.getByText('No tags to display for this item')).toBeVisible();
const canvas = page.locator('canvas').nth(1);
//Wait for canvas to stablize.
await canvas.hover({trial: true});
@@ -85,19 +101,31 @@ test.describe('Plot Tagging', () => {
await expect(page.getByText('Driving')).toBeHidden();
}
async function basicTagsTests(page, canvas) {
// Search for Science
/**
* Given a page, tests that tags are searchable, deletable, and persist across reloads.
* @param {import('@playwright/test').Page} page
* @returns {Promise}
*/
async function basicTagsTests(page) {
// Search for Driving
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
await expect(page.locator('[aria-label="Search Result"]').nth(0)).toContainText("Science");
await expect(page.locator('[aria-label="Search Result"]').nth(0)).not.toContainText("Drilling");
// Clicking elsewhere should cause annotation selection to be cleared
await expect(page.getByText('No tags to display for this item')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('driv');
// click on the search result
await page.getByRole('searchbox', { name: 'OpenMCT Search' }).getByText(/Sine Wave/).first().click();
// Delete Driving
await page.hover('[aria-label="Tag"]:has-text("Driving")');
await page.locator('[aria-label="Remove tag Driving"]').click();
await expect(page.locator('[aria-label="Tags Inspector"]')).toContainText("Science");
await expect(page.locator('[aria-label="Tags Inspector"]')).not.toContainText("Driving");
// Search for Science
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
await expect(page.locator('[aria-label="Search Result"]').nth(0)).toContainText("Science");
await expect(page.locator('[aria-label="Search Result"]').nth(0)).not.toContainText("Drilling");
// Search for Driving
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
@@ -109,12 +137,13 @@ test.describe('Plot Tagging', () => {
page.reload(),
page.waitForLoadState('networkidle')
]);
// wait for plot progress bar to disappear
await page.locator('.l-view-section.c-progress-bar').waitFor({ state: 'detached' });
// wait for plots to load
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
await page.getByText('Annotations').click();
await expect(page.getByText('No tags to display for this item')).toBeVisible();
const canvas = page.locator('canvas').nth(1);
// click on the tagged plot point
await canvas.click({
position: {
@@ -171,8 +200,23 @@ test.describe('Plot Tagging', () => {
// changing to fixed time mode rebuilds canvas?
canvas = page.locator('canvas').nth(1);
await basicTagsTests(page, canvas);
await testTelemetryItem(page, canvas, alphaSineWave);
await basicTagsTests(page);
await testTelemetryItem(page, alphaSineWave);
// set to real time mode
await setRealTimeMode(page);
// Search for Science
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
// click on the search result
await page.getByRole('searchbox', { name: 'OpenMCT Search' }).getByText('Alpha Sine Wave').first().click();
// wait for plots to load
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
// expect plot to be paused
await expect(page.locator('[title="Resume displaying real-time data"]')).toBeVisible();
await setFixedTimeMode(page);
});
test('Tags work with Plot View of telemetry items', async ({ page }) => {
@@ -187,7 +231,7 @@ test.describe('Plot Tagging', () => {
xEnd: 700,
yEnd: 480
});
await basicTagsTests(page, canvas);
await basicTagsTests(page);
});
test('Tags work with Stacked Plots', async ({ page }) => {
@@ -217,7 +261,7 @@ test.describe('Plot Tagging', () => {
xEnd: 700,
yEnd: 215
});
await basicTagsTests(page, canvas);
await testTelemetryItem(page, canvas, alphaSineWave);
await basicTagsTests(page);
await testTelemetryItem(page, alphaSineWave);
});
});

View File

@@ -191,7 +191,7 @@ test.describe('Recent Objects', () => {
expect(await clockBreadcrumbs.count()).toBe(2);
expect(await clockBreadcrumbs.nth(0).innerText()).not.toEqual(await clockBreadcrumbs.nth(1).innerText());
});
test("Enforces a limit of 20 recent objects", async ({ page }) => {
test("Enforces a limit of 20 recent objects and clears the recent objects", async ({ page }) => {
// Creating 21 objects takes a while, so increase the timeout
test.slow();
@@ -242,6 +242,15 @@ test.describe('Recent Objects', () => {
// Assert that the Clock treeitem is no longer highlighted
await expect(lastClockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
// Click the aria-label="Clear Recently Viewed" button
await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
// Click on the "OK" button in the confirmation dialog
await page.getByRole('button', { name: 'OK' }).click();
// Assert that the list is empty
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
});
function assertInitialRecentObjectsListState() {

View File

@@ -63,7 +63,7 @@ test.describe('Grand Search', () => {
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(`Clock A ${myItemsFolderName} Red Folder Blue Folder`);
// Click [aria-label="OpenMCT Search"] a >> nth=0
await page.locator('[aria-label="OpenMCT Search"] a').first().click();
await page.locator('[aria-label="Search Result"] >> nth=0').click();
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
// Fill [aria-label="OpenMCT Search"] input[type="search"]

View File

@@ -50,7 +50,6 @@ test.describe('Main Tree', () => {
await expandTreePaneItemByName(page, folder.name);
await assertTreeItemIsVisible(page, clock.name);
});
test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @2p', async ({ page, openmctConfig }) => {

View File

@@ -138,6 +138,7 @@ test.describe('Performance tests', () => {
await page.evaluate(() => window.performance.mark("notebook-search-processed"));
//Clear Search
await page.locator('.c-search.c-notebook__search .c-search__input').hover();
await page.locator('.c-search.c-notebook__search .c-search__clear-input').click();
await page.evaluate(() => window.performance.mark("notebook-search-processed"));

View File

@@ -20,11 +20,11 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { test } = require('../../../pluginFixtures');
const { setBoundsToSpanAllActivities } = require('../../../helper/planningUtils');
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions');
const { test } = require('../../pluginFixtures');
const { setBoundsToSpanAllActivities } = require('../../helper/planningUtils');
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../appActions');
const percySnapshot = require('@percy/playwright');
const examplePlanLarge = require('../../../test-data/ExamplePlan_Large.json');
const examplePlanLarge = require('../../test-data/examplePlans/ExamplePlan_Large.json');
test.describe('Visual - Planning', () => {
test.beforeEach(async ({ page }) => {

View File

@@ -33,6 +33,8 @@ export default function (staticFaults = false) {
return Promise.resolve(faultsData);
},
subscribe(domainObject, callback) {
callback({ type: 'global-alarm-status' });
return () => {};
},
supportsRequest(domainObject) {

View File

@@ -1,16 +1,16 @@
{
"name": "openmct",
"version": "2.2.0-SNAPSHOT",
"version": "2.2.1-SNAPSHOT",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.18.9",
"@braintree/sanitize-url": "6.0.2",
"@percy/cli": "1.17.0",
"@percy/cli": "1.21.0",
"@percy/playwright": "1.0.4",
"@playwright/test": "1.29.0",
"@types/eventemitter3": "1.2.0",
"@types/jasmine": "4.3.1",
"@types/lodash": "4.14.191",
"@types/lodash": "4.14.192",
"babel-loader": "9.1.0",
"babel-plugin-istanbul": "6.1.1",
"codecov": "3.8.3",
@@ -20,10 +20,10 @@
"d3-axis": "3.0.0",
"d3-scale": "3.3.0",
"d3-selection": "3.0.0",
"eslint": "8.36.0",
"eslint-plugin-compat": "4.1.1",
"eslint": "8.37.0",
"eslint-plugin-compat": "4.1.2",
"eslint-plugin-playwright": "0.12.0",
"eslint-plugin-vue": "9.9.0",
"eslint-plugin-vue": "9.10.0",
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
"file-saver": "2.0.5",
@@ -38,35 +38,35 @@
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "5.1.0",
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-sourcemap-loader": "0.4.0",
"karma-spec-reporter": "0.0.36",
"karma-webpack": "5.0.0",
"kdbush": "^3.0.0",
"kdbush": "3.0.0",
"location-bar": "3.0.1",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.7.2",
"mini-css-extract-plugin": "2.7.5",
"moment": "2.29.4",
"moment-duration-format": "2.3.2",
"moment-timezone": "0.5.41",
"nyc": "15.1.0",
"painterro": "1.2.78",
"playwright-core": "1.29.0",
"plotly.js-basic-dist": "2.17.0",
"plotly.js-gl2d-dist": "2.17.1",
"plotly.js-basic-dist": "2.20.0",
"plotly.js-gl2d-dist": "2.20.0",
"printj": "1.3.1",
"resolve-url-loader": "5.0.0",
"sanitize-html": "2.10.0",
"sass": "1.57.1",
"sass-loader": "13.2.0",
"sass": "1.59.3",
"sass-loader": "13.2.2",
"sinon": "15.0.1",
"style-loader": "^3.3.1",
"style-loader": "3.3.2",
"typescript": "4.9.5",
"uuid": "9.0.0",
"vue": "2.6.14",
"vue-eslint-parser": "9.1.0",
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.74.0",
"webpack": "5.77.0",
"webpack-cli": "5.0.0",
"webpack-dev-server": "4.11.1",
"webpack-merge": "5.8.0"
@@ -107,7 +107,7 @@
"url": "https://github.com/nasa/openmct.git"
},
"engines": {
"node": ">=14.19.1"
"node": ">=16.20.0"
},
"browserslist": [
"Firefox ESR",

View File

@@ -21,18 +21,31 @@
*****************************************************************************/
export default class FaultManagementAPI {
/**
* @param {import("openmct").OpenMCT} openmct
*/
constructor(openmct) {
this.openmct = openmct;
}
/**
* @param {*} provider
*/
addProvider(provider) {
this.provider = provider;
}
/**
* @returns {boolean}
*/
supportsActions() {
return this.provider?.acknowledgeFault !== undefined && this.provider?.shelveFault !== undefined;
}
/**
* @param {import("../objects/ObjectAPI").DomainObject} domainObject
* @returns {Promise.<FaultAPIResponse[]>}
*/
request(domainObject) {
if (!this.provider?.supportsRequest(domainObject)) {
return Promise.reject();
@@ -41,6 +54,11 @@ export default class FaultManagementAPI {
return this.provider.request(domainObject);
}
/**
* @param {import("../objects/ObjectAPI").DomainObject} domainObject
* @param {Function} callback
* @returns {Function} unsubscribe
*/
subscribe(domainObject, callback) {
if (!this.provider?.supportsSubscribe(domainObject)) {
return Promise.reject();
@@ -49,58 +67,55 @@ export default class FaultManagementAPI {
return this.provider.subscribe(domainObject, callback);
}
/**
* @param {Fault} fault
* @param {*} ackData
*/
acknowledgeFault(fault, ackData) {
return this.provider.acknowledgeFault(fault, ackData);
}
/**
* @param {Fault} fault
* @param {*} shelveData
* @returns {Promise.<T>}
*/
shelveFault(fault, shelveData) {
return this.provider.shelveFault(fault, shelveData);
}
}
/** @typedef {object} Fault
* @property {string} type
* @property {object} fault
* @property {boolean} fault.acknowledged
* @property {object} fault.currentValueInfo
* @property {number} fault.currentValueInfo.value
* @property {string} fault.currentValueInfo.rangeCondition
* @property {string} fault.currentValueInfo.monitoringResult
* @property {string} fault.id
* @property {string} fault.name
* @property {string} fault.namespace
* @property {number} fault.seqNum
* @property {string} fault.severity
* @property {boolean} fault.shelved
* @property {string} fault.shortDescription
* @property {string} fault.triggerTime
* @property {object} fault.triggerValueInfo
* @property {number} fault.triggerValueInfo.value
* @property {string} fault.triggerValueInfo.rangeCondition
* @property {string} fault.triggerValueInfo.monitoringResult
* @example
* {
* "type": "",
* "fault": {
* "acknowledged": true,
* "currentValueInfo": {
* "value": 0,
* "rangeCondition": "",
* "monitoringResult": ""
* },
* "id": "",
* "name": "",
* "namespace": "",
* "seqNum": 0,
* "severity": "",
* "shelved": true,
* "shortDescription": "",
* "triggerTime": "",
* "triggerValueInfo": {
* "value": 0,
* "rangeCondition": "",
* "monitoringResult": ""
* }
* }
* }
/**
* @typedef {object} TriggerValueInfo
* @property {number} value
* @property {string} rangeCondition
* @property {string} monitoringResult
*/
/**
* @typedef {object} CurrentValueInfo
* @property {number} value
* @property {string} rangeCondition
* @property {string} monitoringResult
*/
/**
* @typedef {object} Fault
* @property {boolean} acknowledged
* @property {CurrentValueInfo} currentValueInfo
* @property {string} id
* @property {string} name
* @property {string} namespace
* @property {number} seqNum
* @property {string} severity
* @property {boolean} shelved
* @property {string} shortDescription
* @property {string} triggerTime
* @property {TriggerValueInfo} triggerValueInfo
*/
/**
* @typedef {object} FaultAPIResponse
* @property {string} type
* @property {Fault} fault
*/

View File

@@ -43,7 +43,7 @@
</div>
<div
v-if="!hideOptions && filteredOptions.length > 0"
class="c-menu c-input--autocomplete__options"
class="c-menu c-input--autocomplete__options js-autocomplete-options"
aria-label="Autocomplete Options"
@blur="hideOptions = true"
>

View File

@@ -415,7 +415,10 @@ export default class NotificationAPI extends EventEmitter {
for (; i < this.notifications.length; i++) {
notification = this.notifications[i];
if (!notification.model.minimized
const isNotificationMinimized = notification.model.minimized
|| notification?.model?.options?.minimized;
if (!isNotificationMinimized
&& notification !== this.activeNotification) {
return notification;
}

View File

@@ -81,7 +81,7 @@
}
.c-object-label__name {
filter: $objectLabelNameFilter;
color: $objectLabelNameColorFg;
}
}

View File

@@ -29,6 +29,7 @@ import DefaultMetadataProvider from './DefaultMetadataProvider';
import objectUtils from 'objectUtils';
export default class TelemetryAPI {
#isGreedyLAD;
constructor(openmct) {
this.openmct = openmct;
@@ -44,8 +45,8 @@ export default class TelemetryAPI {
this.requestProviders = [];
this.subscriptionProviders = [];
this.valueFormatterCache = new WeakMap();
this.requestInterceptorRegistry = new TelemetryRequestInterceptorRegistry();
this.#isGreedyLAD = true;
}
abortAllRequests() {
@@ -226,6 +227,31 @@ export default class TelemetryAPI {
return modifiedRequest;
}
/**
* Get or set greedy LAD. For stategy "latest" telemetry in
* realtime mode the start bound will be ignored if true and
* there is no new data to replace the existing data.
* defaults to true
*
* To turn off greedy LAD:
* openmct.telemetry.greedyLAD(false);
*
* @method greedyLAD
* @returns {boolean} if greedyLAD is active or not
* @memberof module:openmct.TelemetryAPI#
*/
greedyLAD(isGreedy) {
if (arguments.length > 0) {
if (isGreedy !== true && isGreedy !== false) {
throw new Error('Error setting greedyLAD. Greedy LAD only accepts true or false values');
}
this.#isGreedyLAD = isGreedy;
}
return this.#isGreedyLAD;
}
/**
* Request telemetry collection for a domain object.
* The `options` argument allows you to specify filters

View File

@@ -30,8 +30,8 @@ export default class TelemetryCollection extends EventEmitter {
/**
* Creates a Telemetry Collection
*
* @param {object} openmct - Openm MCT
* @param {object} domainObject - Domain Object to user for telemetry collection
* @param {OpenMCT} openmct - Open MCT
* @param {module:openmct.DomainObject} domainObject - Domain Object to use for telemetry collection
* @param {object} options - Any options passed in for request/subscribe
*/
constructor(openmct, domainObject, options) {
@@ -50,6 +50,7 @@ export default class TelemetryCollection extends EventEmitter {
this.lastBounds = undefined;
this.requestAbort = undefined;
this.isStrategyLatest = this.options.strategy === 'latest';
this.dataOutsideTimeBounds = false;
}
/**
@@ -129,9 +130,6 @@ export default class TelemetryCollection extends EventEmitter {
this.emit('requestStarted');
const modifiedOptions = await this.openmct.telemetry.applyRequestInterceptors(this.domainObject, options);
historicalData = await historicalProvider.request(this.domainObject, modifiedOptions);
if (!historicalData || !historicalData.length) {
return;
}
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Error requesting telemetry data...');
@@ -142,6 +140,10 @@ export default class TelemetryCollection extends EventEmitter {
this.emit('requestEnded');
this.requestAbort = undefined;
if (!historicalData || !historicalData.length) {
return;
}
this._processNewTelemetry(historicalData);
}
@@ -184,6 +186,7 @@ export default class TelemetryCollection extends EventEmitter {
let afterEndOfBounds;
let added = [];
let addedIndices = [];
let hasDataBeforeStartBound = false;
// loop through, sort and dedupe
for (let datum of data) {
@@ -191,7 +194,7 @@ export default class TelemetryCollection extends EventEmitter {
beforeStartOfBounds = parsedValue < this.lastBounds.start;
afterEndOfBounds = parsedValue > this.lastBounds.end;
if (!afterEndOfBounds && !beforeStartOfBounds) {
if (!afterEndOfBounds && (!beforeStartOfBounds || (this.isStrategyLatest && this.openmct.telemetry.greedyLAD()))) {
let isDuplicate = false;
let startIndex = this._sortedIndex(datum);
let endIndex = undefined;
@@ -218,6 +221,10 @@ export default class TelemetryCollection extends EventEmitter {
this.boundedTelemetry.splice(index, 0, datum);
addedIndices.push(index);
added.push(datum);
if (!hasDataBeforeStartBound && beforeStartOfBounds) {
hasDataBeforeStartBound = true;
}
}
} else if (afterEndOfBounds) {
@@ -226,12 +233,18 @@ export default class TelemetryCollection extends EventEmitter {
}
if (added.length) {
// if latest strategy is requested, we need to check if the value is the latest unmitted value
// if latest strategy is requested, we need to check if the value is the latest unemitted value
if (this.isStrategyLatest) {
this.boundedTelemetry = [this.boundedTelemetry[this.boundedTelemetry.length - 1]];
// if true, then this value has yet to be emitted
if (this.boundedTelemetry[0] !== latestBoundedDatum) {
if (hasDataBeforeStartBound) {
this._handleDataOutsideBounds();
} else {
this._handleDataInsideBounds();
}
this.emit('add', this.boundedTelemetry);
}
} else {
@@ -294,6 +307,17 @@ export default class TelemetryCollection extends EventEmitter {
let added = [];
let testDatum = {};
if (endChanged) {
testDatum[this.timeKey] = bounds.end;
// Calculate the new index of the last item in bounds
endIndex = _.sortedLastIndexBy(
this.futureBuffer,
testDatum,
datum => this.parseTime(datum)
);
added = this.futureBuffer.splice(0, endIndex);
}
if (startChanged) {
testDatum[this.timeKey] = bounds.start;
@@ -307,20 +331,19 @@ export default class TelemetryCollection extends EventEmitter {
);
discarded = this.boundedTelemetry.splice(0, startIndex);
} else if (this.parseTime(testDatum) > this.parseTime(this.boundedTelemetry[0])) {
discarded = this.boundedTelemetry;
this.boundedTelemetry = [];
}
}
// if greedyLAD is active and there is no new data to replace, don't discard
const isGreedyLAD = this.openmct.telemetry.greedyLAD();
const shouldRemove = (!isGreedyLAD || (isGreedyLAD && added.length > 0));
if (endChanged) {
testDatum[this.timeKey] = bounds.end;
// Calculate the new index of the last item in bounds
endIndex = _.sortedLastIndexBy(
this.futureBuffer,
testDatum,
datum => this.parseTime(datum)
);
added = this.futureBuffer.splice(0, endIndex);
if (shouldRemove) {
discarded = this.boundedTelemetry;
this.boundedTelemetry = [];
// since it IS strategy latest, we can assume there will be at least 1 datum
// unless no data was returned in the first request, we need to account for that
} else if (this.boundedTelemetry.length === 1) {
this._handleDataOutsideBounds();
}
}
}
if (discarded.length > 0) {
@@ -331,6 +354,8 @@ export default class TelemetryCollection extends EventEmitter {
if (!this.isStrategyLatest) {
this.boundedTelemetry = [...this.boundedTelemetry, ...added];
} else {
this._handleDataInsideBounds();
added = [added[added.length - 1]];
this.boundedTelemetry = added;
}
@@ -345,6 +370,20 @@ export default class TelemetryCollection extends EventEmitter {
}
_handleDataInsideBounds() {
if (this.dataOutsideTimeBounds) {
this.dataOutsideTimeBounds = false;
this.emit('dataInsideTimeBounds');
}
}
_handleDataOutsideBounds() {
if (!this.dataOutsideTimeBounds) {
this.dataOutsideTimeBounds = true;
this.emit('dataOutsideTimeBounds');
}
}
/**
* whenever the time system is updated need to update related values in
* the Telemetry Collection and reset the telemetry collection

View File

@@ -121,8 +121,9 @@ define([
}
TelemetryValueFormatter.prototype.parse = function (datum) {
const isDatumArray = Array.isArray(datum);
if (_.isObject(datum)) {
const objectDatum = datum[this.valueMetadata.source];
const objectDatum = isDatumArray ? datum : datum[this.valueMetadata.source];
if (Array.isArray(objectDatum)) {
return objectDatum.map((item) => {
return this.formatter.parse(item);
@@ -136,8 +137,9 @@ define([
};
TelemetryValueFormatter.prototype.format = function (datum) {
const isDatumArray = Array.isArray(datum);
if (_.isObject(datum)) {
const objectDatum = datum[this.valueMetadata.source];
const objectDatum = isDatumArray ? datum : datum[this.valueMetadata.source];
if (Array.isArray(objectDatum)) {
return objectDatum.map((item) => {
return this.formatter.format(item);

View File

@@ -22,7 +22,7 @@
const expandColumns = {
name: 'Expand Columns',
key: 'expand-columns',
key: 'lad-expand-columns',
description: "Increase column widths to fit currently available data.",
cssClass: 'icon-arrows-right-left labeled',
invoke: (objectPath, view) => {
@@ -34,7 +34,7 @@ const expandColumns = {
const autosizeColumns = {
name: 'Autosize Columns',
key: 'autosize-columns',
key: 'lad-autosize-columns',
description: "Automatically size columns to fit the table into the available space.",
cssClass: 'icon-expand labeled',
invoke: (objectPath, view) => {

View File

@@ -55,7 +55,7 @@
</template>
<script>
import Vue from 'vue';
import LadRow from './LADRow.vue';
import StalenessUtils from '@/utils/staleness';
@@ -115,7 +115,23 @@ export default {
return '';
}
},
mounted() {
watch: {
configuration: {
handler(newVal) {
if (this.viewActionsCollection) {
if (newVal.isFixedLayout) {
this.viewActionsCollection.show(['lad-expand-columns']);
this.viewActionsCollection.hide(['lad-autosize-columns']);
} else {
this.viewActionsCollection.show(['lad-autosize-columns']);
this.viewActionsCollection.hide(['lad-expand-columns']);
}
}
},
deep: true
}
},
async mounted() {
this.ladTableConfiguration.on('change', this.handleConfigurationChange);
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addItem);
@@ -123,6 +139,9 @@ export default {
this.composition.on('reorder', this.reorder);
this.composition.load();
this.stalenessSubscription = {};
await Vue.nextTick();
this.viewActionsCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
this.initializeViewActions();
},
destroyed() {
this.ladTableConfiguration.off('change', this.handleConfigurationChange);
@@ -208,6 +227,16 @@ export default {
},
toggleFixedLayout() {
this.configuration.isFixedLayout = !this.configuration.isFixedLayout;
},
initializeViewActions() {
if (this.configuration.isFixedLayout) {
this.viewActionsCollection.show(['lad-expand-columns']);
this.viewActionsCollection.hide(['lad-autosize-columns']);
} else {
this.viewActionsCollection.hide(['lad-expand-columns']);
this.viewActionsCollection.show(['lad-autosize-columns']);
}
}
}
};

View File

@@ -93,6 +93,11 @@ export default class ConditionManager extends EventEmitter {
);
this.updateConditionResults({id: id});
this.updateCurrentCondition(latestTimestamp);
if (Object.keys(this.telemetryObjects).length === 0) {
// no telemetry objects
this.emit('noTelemetryObjects');
}
}
initialize() {
@@ -102,6 +107,11 @@ export default class ConditionManager extends EventEmitter {
this.initCondition(conditionConfiguration, index);
});
}
if (Object.keys(this.telemetryObjects).length === 0) {
// no telemetry objects
this.emit('noTelemetryObjects');
}
}
updateConditionTelemetryObjects() {

View File

@@ -35,7 +35,9 @@ export default class StyleRuleManager extends EventEmitter {
}
if (styleConfiguration) {
this.initialize(styleConfiguration);
// We don't set the selectedConditionId here because we want condition set computation to happen before we apply any selected style
const styleConfigurationWithNoSelection = Object.assign(styleConfiguration, {selectedConditionId: ''});
this.initialize(styleConfigurationWithNoSelection);
if (styleConfiguration.conditionSetIdentifier) {
this.openmct.time.on("bounds", this.refreshData);
this.subscribeToConditionSet();
@@ -57,14 +59,18 @@ export default class StyleRuleManager extends EventEmitter {
this.applySelectedConditionStyle();
}
} else if (this.conditionSetIdentifier) {
//reset the selected style and let the condition set output determine what it should be
this.selectedConditionId = undefined;
this.currentStyle = undefined;
this.updateDomainObjectStyle();
this.subscribeToConditionSet();
}
}
initialize(styleConfiguration) {
this.conditionSetIdentifier = styleConfiguration.conditionSetIdentifier;
this.staticStyle = styleConfiguration.staticStyle;
this.selectedConditionId = styleConfiguration.selectedConditionId;
this.staticStyle = styleConfiguration.staticStyle;
this.defaultConditionId = styleConfiguration.defaultConditionId;
this.updateConditionStylesMap(styleConfiguration.styles || []);
}

View File

@@ -132,6 +132,7 @@
<span class="c-cdef__controls">
<select
v-model="condition.configuration.trigger"
aria-label="Condition Trigger"
@change="persist"
>
<option

View File

@@ -114,15 +114,11 @@ export default {
telemetryObjs: [],
moveIndex: undefined,
isDragging: false,
defaultOutput: undefined,
dragCounter: 0,
currentConditionId: ''
};
},
watch: {
defaultOutput(newOutput, oldOutput) {
this.$emit('updateDefaultOutput', newOutput);
},
testData: {
handler() {
this.updateTestData();
@@ -158,7 +154,7 @@ export default {
this.observeForChanges();
this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
this.conditionManager.on('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
this.updateDefaultCondition();
this.conditionManager.on('noTelemetryObjects', this.emitNoTelemetryObjectEvent);
this.stalenessSubscription = {};
},
methods: {
@@ -166,18 +162,16 @@ export default {
this.currentConditionId = data.conditionId;
this.$emit('conditionSetResultUpdated', data);
},
emitNoTelemetryObjectEvent(data) {
this.currentConditionId = '';
this.$emit('noTelemetryObjects');
},
observeForChanges() {
this.stopObservingForChanges = this.openmct.objects.observe(this.domainObject, 'configuration.conditionCollection', (newConditionCollection) => {
//this forces children to re-render
this.conditionCollection = newConditionCollection.map(condition => condition);
this.updateDefaultCondition();
});
},
updateDefaultCondition() {
const defaultCondition = this.domainObject.configuration.conditionCollection
.find(conditionConfiguration => conditionConfiguration.isDefault);
this.defaultOutput = defaultCondition.configuration.output;
},
setMoveIndex(index) {
this.moveIndex = index;
this.isDragging = true;

View File

@@ -28,12 +28,15 @@
<section class="c-cs__current-output c-section">
<div class="c-cs__content c-cs__current-output-value">
<span class="c-cs__current-output-value__label">Current Output</span>
<span class="c-cs__current-output-value__value">
<span
class="c-cs__current-output-value__value"
aria-label="Current Output Value"
>
<template v-if="currentConditionOutput">
{{ currentConditionOutput }}
</template>
<template v-else>
{{ defaultConditionOutput }}
---
</template>
</span>
</div>
@@ -51,7 +54,7 @@
:is-editing="isEditing"
:test-data="testData"
@conditionSetResultUpdated="updateCurrentOutput"
@updateDefaultOutput="updateDefaultOutput"
@noTelemetryObjects="updateCurrentOutput('---')"
@telemetryUpdated="updateTelemetry"
@telemetryStaleness="handleStaleness"
/>
@@ -75,7 +78,6 @@ export default {
data() {
return {
currentConditionOutput: '',
defaultConditionOutput: '',
telemetryObjs: [],
testData: {},
staleObjects: []

View File

@@ -29,6 +29,7 @@
<select
ref="telemetrySelect"
v-model="criterion.telemetry"
aria-label="Criterion Telemetry Selection"
@change="updateMetadataOptions"
>
<option value="">- Select Telemetry -</option>
@@ -50,6 +51,7 @@
<select
ref="metadataSelect"
v-model="criterion.metadata"
aria-label="Criterion Metadata Selection"
@change="updateOperations"
>
<option value="">- Select Field -</option>
@@ -69,6 +71,7 @@
>
<select
v-model="criterion.operation"
aria-label="Criterion Comparison Selection"
@change="updateInputVisibilityAndValues"
>
<option value="">- Select Comparison -</option>
@@ -89,6 +92,7 @@
<input
v-model="criterion.input[inputIndex]"
class="c-cdef__control__input"
aria-label="Criterion Input"
:type="setInputType"
@change="persist"
>
@@ -103,6 +107,7 @@
>
<select
v-model="criterion.input[0]"
aria-label="Criterion Else Selection"
@change="persist"
>
<option

View File

@@ -307,6 +307,8 @@ export default {
delete this.stopProvidingTelemetry;
}
} else {
//reset the selectedConditionID so that the condition set computation can drive it.
this.applySelectedConditionStyle('');
this.subscribeToConditionSet();
}
},

View File

@@ -80,11 +80,9 @@
.is-editing & {
cursor: pointer;
pointer-events: initial;
transition: $transOut;
&:hover {
background: rgba($colorBodyFg, 0.1);
transition: $transIn;
}
&.is-current {

View File

@@ -25,6 +25,7 @@
:is="urlDefined ? 'a' : 'span'"
class="c-condition-widget u-style-receiver js-style-receiver"
:href="url"
:target="url ? '_BLANK' : ''"
>
<div class="c-condition-widget__label">
{{ label }}

View File

@@ -72,7 +72,7 @@ export default {
this.isEditing = isEditing;
},
formatTelemetry(event) {
let newFormat = event.currentTarget.value;
const newFormat = event.currentTarget.value;
this.openmct.selection.get().forEach(selectionPath => {
selectionPath[0].context.updateTelemetryFormat(newFormat);
});

View File

@@ -193,7 +193,7 @@ export default {
},
telemetryValue() {
if (!this.datum) {
return;
return '---';
}
return this.formatter && this.formatter.format(this.datum);

View File

@@ -86,7 +86,7 @@
*[s-selected-parent] {
> .l-layout {
// When main shell layout is the parent
@include displayMarquee(deeppink);
@include displayMarquee(deeppink); // TEMP
}
> * > * > * {
// When a sub-layout is the parent

View File

@@ -45,18 +45,15 @@
// Has-complex-content objects
.c-so-view.has-complex-content {
transition: $transOut;
transition-delay: $moveBarOutDelay;
@include transition($prop: transform, $dur: $transOutTime, $delay: $moveBarOutDelay);
> .c-so-view__local-controls {
transition: transform 250ms ease-in-out;
transition-delay: $moveBarOutDelay;
@include transition($prop: transform, $dur: 250ms, $delay: $moveBarOutDelay);
}
+ .c-frame__move-bar {
display: none;
}
}
.l-layout {
@@ -65,13 +62,11 @@
> .l-layout__frame {
> .c-so-view.has-complex-content {
> .c-so-view__local-controls {
transition: transform $transOutTime ease-in-out;
transition-delay: $moveBarOutDelay;
@include transition($prop: transform, $dur: $transOutTime, $delay: $moveBarOutDelay);
}
+ .c-frame__move-bar {
transition: $transOut;
transition-delay: $moveBarOutDelay;
@include transition($prop: height, $delay: $moveBarOutDelay);
@include userSelectNone();
background: $editFrameMovebarColorBg;
box-shadow: rgba(black, 0.3) 0 2px;
@@ -103,18 +98,17 @@
&:hover {
> .c-so-view.has-complex-content {
transition: $transIn;
transition: $transInTransform;
transition-delay: 0s;
> .c-so-view__local-controls {
transform: translateY($editFrameMovebarH);
transition: transform $transInTime ease-in-out;
@include transition(height, $transOutTime);
transition-delay: 0s;
}
+ .c-frame__move-bar {
transition: $transIn;
transition-delay: 0s;
@include transition(height);
height: $editFrameMovebarH;
}
}

View File

@@ -42,8 +42,6 @@ export default {
};
},
mounted() {
this.updateFaultList();
this.unsubscribe = this.openmct.faults
.subscribe(this.domainObject, this.updateFault);
},
@@ -68,7 +66,11 @@ export default {
this.openmct.faults
.request(this.domainObject)
.then(faultsData => {
this.faultsList = faultsData.map(fd => fd.fault);
if (faultsData?.length > 0) {
this.faultsList = faultsData.map(fd => fd.fault);
} else {
this.faultsList = [];
}
});
}
}

View File

@@ -244,11 +244,10 @@
display: flex;
flex-direction: column;
flex: 0 0 ($margin * 2) + $size;
transition: $transOut;
&:before {
// The visible resize line
background: $editUIColor;
background-color: $editUIColor;
content: '';
display: block;
flex: 1 1 auto;
@@ -270,10 +269,9 @@
}
&:hover {
transition: $transOut;
&:before {
// The visible resize line
background: $editUIColorHov;
background-color: $editUIColorHov;
}
}
}

View File

@@ -37,6 +37,7 @@
.c-grid-item {
// Mobile-first
@include button($bg: $colorItemBg, $fg: $colorItemFg);
@include cControlHov();
cursor: pointer;
display: flex;
padding: $interiorMarginLg;
@@ -142,15 +143,10 @@
body.desktop & {
$transOutMs: 300ms;
flex-flow: column nowrap;
transition: $transOutMs ease-in-out;
&:hover {
filter: $filterItemHoverFg;
transition: $transIn;
.c-grid-item__type-icon {
transform: scale(1.1);
transition: $transInBounce;
}
}
@@ -171,8 +167,6 @@
font-size: floor(math.div($gridItemDesk, 3));
margin: $interiorMargin 22.5% $interiorMargin * 3 22.5%;
order: 2;
transform-origin: center;
transition: all $transOutMs ease-in-out;
}
&__details {

View File

@@ -322,7 +322,7 @@ export default {
rgba(125,125,125,.2) 8px
)`
) : ''}`,
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${this.imageTranslateY / 2}px)`,
transition: `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`,
width: `${this.sizedImageWidth}px`,
height: `${this.sizedImageHeight}px`
@@ -709,7 +709,7 @@ export default {
getVisibleLayerStyles(layer) {
return {
backgroundImage: `url(${layer.source})`,
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX}px, ${this.imageTranslateY}px)`,
transform: `scale(${this.zoomFactor}) translate(${this.imageTranslateX / 2}px, ${this.imageTranslateY / 2}px)`,
transition: `${!this.pan && this.animateZoom ? 'transform 250ms ease-in' : 'initial'}`
};
},

View File

@@ -195,7 +195,7 @@
margin-bottom: 1px;
padding-bottom: $interiorMarginSm;
&.animate-scroll {
scroll-behavior: smooth;
scroll-behavior: smooth;
}
}
@@ -320,7 +320,7 @@
flex-direction: row;
position: absolute;
left: $interiorMargin; top: $interiorMargin;
z-index: 70;
z-index: 10;
background: $colorLocalControlOvrBg;
border-radius: $basicCr;
align-items: center;
@@ -495,7 +495,6 @@
&:hover {
z-index: 2;
filter: brightness(1) contrast(1) !important;
[class*='__image-handle'] {
background-color: $colorBodyFg;
}
@@ -519,9 +518,4 @@
display: block;
align-self: flex-end;
}
&:hover div.c-imagery-tsv__image-wrapper {
// TODO CH: convert to theme constants
filter: brightness(0.5) contrast(0.7);
}
}

View File

@@ -23,11 +23,17 @@
import Annotations from './AnnotationsInspectorView.vue';
import Vue from 'vue';
export default function ElementsViewProvider(openmct) {
export default function AnnotationsViewProvider(openmct) {
return {
key: 'annotationsView',
name: 'Annotations',
canView: function (selection) {
const availableTags = openmct.annotation.getAvailableTags();
if (availableTags.length < 1) {
return false;
}
return selection.length;
},
view: function (selection) {

View File

@@ -177,10 +177,9 @@ export default {
if (this.$refs.TagEditor) {
const clickedInsideTagEditor = this.$refs.TagEditor.contains(event.target);
if (!clickedInsideTagEditor) {
// Remove last tag when user clicks outside of TagSelection
this.addedTags.pop();
// Hide TagSelection and show "Add Tag" button
this.userAddingTag = false;
this.tagsChanged();
}
}
},

View File

@@ -13,6 +13,24 @@
/******************************* TAGS */
.c-tag {
/* merge conflict in 5247
border-radius: 10px; //TODO: convert to theme constant
display: inline-flex;
padding: 1px 10px; //TODO: convert to theme constant
> * + * {
margin-left: $interiorMargin;
}
&__remove-btn {
color: inherit !important;
display: none;
opacity: 0;
overflow: hidden;
padding: 1px !important;
@include transition(opacity);
width: 0;
*/
border-radius: $tagBorderRadius;
display: inline-flex;
overflow: hidden;
@@ -28,15 +46,15 @@
transition: $transIn;
width: 0;
&:hover {
opacity: 1;
&:hover {
opacity: 1;
}
}
}
/* SEARCH RESULTS */
&.--is-not-search-match {
opacity: 0.5;
}
/* SEARCH RESULTS */
&.--is-not-search-match {
opacity: 0.5;
}
}
.c-tag-holder {
@@ -51,6 +69,31 @@
/******************************* TAGS IN INSPECTOR / TAG SELECTION & APPLICATION */
.c-tag-applier {
/* merge conflict in fix-repaint-5247
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
> * + * {
margin-left: $interiorMargin;
}
&__add-btn {
&:before {
font-size: 0.9em;
}
}
.c-tag {
flex-direction: row;
align-items: center;
padding-right: 3px !important;
&__remove-btn {
display: block;
}
*/
$tagApplierPadding: 3px 6px;
@include tagHolder;
grid-column: 1 / 3;
@@ -81,7 +124,6 @@
min-height: auto !important;
padding: $tagApplierPadding;
}
}
}
.c-tag-btn__label {
@@ -90,6 +132,21 @@
/******************************* HOVERS */
.has-tag-applier {
/* merge conflict in fix-repaint-5247
$p: opacity, width;
// Apply this class to all components that should trigger tag removal btn on hover
.c-tag__remove-btn {
@include transition($prop: $p, $dur: $transOutTime);
}
&:hover {
.c-tag__remove-btn {
width: 1.1em;
opacity: 0.7;
}
}
}
*/
// Apply this class to all components that should trigger tag removal btn on hover
&:hover {
.c-tag {
@@ -120,3 +177,4 @@
}
}
}
}

View File

@@ -26,12 +26,13 @@
flex-wrap: wrap;
&__item {
$m: 1px;
cursor: pointer;
margin: 0 $interiorMarginSm $interiorMarginSm 0;
margin: 0 $m $m 0;
.c-object-label {
padding: 0;
transition: $transOut;
border-radius: $smallCr;
padding: 2px 3px;
&__type-icon {
width: auto;
@@ -39,9 +40,8 @@
min-width: auto;
}
&:hover {
transition: $transIn;
filter: $filterHov;
@include hover() {
background: $colorItemTreeHoverBg;
}
}
}

View File

@@ -45,7 +45,7 @@ export default class MoveAction {
}
navigateTo(objectPath) {
let urlPath = objectPath.reverse()
const urlPath = objectPath.reverse()
.map(object => this.openmct.objects.makeKeyString(object.identifier))
.join("/");
@@ -53,8 +53,8 @@ export default class MoveAction {
}
addToNewParent(child, newParent) {
let newParentKeyString = this.openmct.objects.makeKeyString(newParent.identifier);
let compositionCollection = this.openmct.composition.get(newParent);
const newParentKeyString = this.openmct.objects.makeKeyString(newParent.identifier);
const compositionCollection = this.openmct.composition.get(newParent);
this.openmct.objects.mutate(child, 'location', newParentKeyString);
compositionCollection.add(child);
@@ -63,11 +63,7 @@ export default class MoveAction {
async onSave(changes) {
this.startTransaction();
let inNavigationPath = this.inNavigationPath(this.object);
if (inNavigationPath && this.openmct.editor.isEditing()) {
this.openmct.editor.save();
}
const inNavigationPath = this.inNavigationPath(this.object);
const parentDomainObjectpath = changes.location || [this.parent];
const parent = parentDomainObjectpath[0];
@@ -91,12 +87,15 @@ export default class MoveAction {
}
let newObjectPath;
if (parentDomainObjectpath) {
newObjectPath = parentDomainObjectpath && [this.object].concat(parentDomainObjectpath);
} else {
const root = await this.openmct.objects.getRoot();
const rootCompositionCollection = this.openmct.composition.get(root);
const rootComposition = await rootCompositionCollection.load();
const rootChildCount = rootComposition.length;
newObjectPath = await this.openmct.objects.getOriginalPath(this.object.identifier);
let root = await this.openmct.objects.getRoot();
let rootChildCount = root.composition.length;
// if not multiple root children, remove root from path
if (rootChildCount < 2) {
@@ -108,8 +107,7 @@ export default class MoveAction {
}
removeFromOldParent(child) {
let compositionCollection = this.openmct.composition.get(this.oldParent);
const compositionCollection = this.openmct.composition.get(this.oldParent);
compositionCollection.remove(child);
}
@@ -166,9 +164,9 @@ export default class MoveAction {
return false;
}
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
const objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
const parentCandidateComposition = parentCandidate.composition;
if (parentCandidateComposition && parentCandidateComposition.indexOf(objectKeystring) !== -1) {
return false;
}
@@ -178,20 +176,18 @@ export default class MoveAction {
}
appliesTo(objectPath) {
let parent = objectPath[1];
let parentType = parent && this.openmct.types.get(parent.type);
let child = objectPath[0];
let childType = child && this.openmct.types.get(child.type);
let isPersistable = this.openmct.objects.isPersistable(child.identifier);
const parent = objectPath[1];
const parentType = parent && this.openmct.types.get(parent.type);
const child = objectPath[0];
const childType = child && this.openmct.types.get(child.type);
const isPersistable = this.openmct.objects.isPersistable(child.identifier);
if (child.locked || (parent && parent.locked) || !isPersistable) {
if (parent?.locked || !isPersistable) {
return false;
}
return parentType
&& parentType.definition.creatable
&& childType
&& childType.definition.creatable
return parentType?.definition.creatable
&& childType?.definition.creatable
&& Array.isArray(parent.composition);
}

View File

@@ -44,7 +44,8 @@ describe("The Move Action plugin", () => {
identifier: {
namespace: "",
key: "child-folder-object"
}
},
location: "parent-folder-object"
}
}
}).folder;
@@ -90,6 +91,31 @@ describe("The Move Action plugin", () => {
expect(moveAction).toBeDefined();
});
describe("when determining the object is applicable", () => {
beforeEach(() => {
spyOn(moveAction, 'appliesTo').and.callThrough();
});
it("should be true when the parent is creatable and has composition", () => {
let applies = moveAction.appliesTo([childObject, parentObject]);
expect(applies).toBe(true);
});
it("should be true when the child is locked and not an alias", () => {
childObject.locked = true;
let applies = moveAction.appliesTo([childObject, parentObject]);
expect(applies).toBe(true);
});
it("should still be true when the child is locked and is an alias", () => {
childObject.locked = true;
childObject.location = 'another-parent-folder-object';
let applies = moveAction.appliesTo([childObject, parentObject]);
expect(applies).toBe(true);
});
});
describe("when moving an object to a new parent and removing from the old parent", () => {
let unObserve;
beforeEach((done) => {

View File

@@ -0,0 +1,155 @@
import {saveAs} from 'saveAs';
import Moment from 'moment';
const UNKNOWN_USER = 'Unknown';
const UNKNOWN_TIME = 'Unknown';
export default class ExportNotebookAsTextAction {
constructor(openmct) {
this.openmct = openmct;
this.cssClass = 'icon-export';
this.description = 'Exports notebook contents as a text file';
this.group = "action";
this.key = 'exportNotebookAsText';
this.name = 'Export Notebook as Text';
this.priority = 1;
}
invoke(objectPath) {
this.showForm(objectPath);
}
getTagName(tagId, availableTags) {
const foundTag = availableTags.find(tag => tag.id === tagId);
if (foundTag) {
return foundTag.label;
} else {
return tagId;
}
}
getTagsForEntry(entry, domainObjectKeyString, annotations) {
const foundTags = [];
annotations.forEach(annotation => {
const target = annotation.targets?.[domainObjectKeyString];
if (target?.entryId === entry.id) {
annotation.tags.forEach(tag => {
if (!foundTags.includes(tag)) {
foundTags.push(tag);
}
});
}
});
return foundTags;
}
formatTimeStamp(timestamp) {
if (timestamp) {
return `${Moment.utc(timestamp).format('YYYY-MM-DD HH:mm:ss')} UTC`;
} else {
return UNKNOWN_TIME;
}
}
appliesTo(objectPath) {
const domainObject = objectPath[0];
const type = this.openmct.types.get(domainObject.type);
return type?.definition?.name === 'Notebook';
}
async onSave(changes, objectPath) {
const availableTags = this.openmct.annotation.getAvailableTags();
const identifier = objectPath[0].identifier;
const domainObject = await this.openmct.objects.get(identifier);
let foundAnnotations = [];
// only load annotations if there are tags
if (availableTags.length) {
foundAnnotations = await this.openmct.annotation.getAnnotations(domainObject.identifier);
}
let notebookAsText = `# ${domainObject.name}\n\n`;
if (changes.exportMetaData) {
const createdTimestamp = domainObject.created;
const createdBy = domainObject.createdBy ?? UNKNOWN_USER;
const modifiedBy = domainObject.modifiedBy ?? UNKNOWN_USER;
const modifiedTimestamp = domainObject.modified ?? domainObject.created;
notebookAsText += `Created on ${this.formatTimeStamp(createdTimestamp)} by user ${createdBy}\n\n`;
notebookAsText += `Updated on ${this.formatTimeStamp(modifiedTimestamp)} by user ${modifiedBy}\n\n`;
}
const notebookSections = domainObject.configuration.sections;
const notebookEntries = domainObject.configuration.entries;
notebookSections.forEach(section => {
notebookAsText += `## ${section.name}\n\n`;
const notebookPages = section.pages;
notebookPages.forEach(page => {
notebookAsText += `### ${page.name}\n\n`;
const notebookPageEntries = notebookEntries[section.id]?.[page.id];
notebookPageEntries.forEach(entry => {
if (changes.exportMetaData) {
const createdTimestamp = entry.createdOn;
const createdBy = entry.createdBy ?? UNKNOWN_USER;
const modifiedBy = entry.modifiedBy ?? UNKNOWN_USER;
const modifiedTimestamp = entry.modified ?? entry.created;
notebookAsText += `Created on ${this.formatTimeStamp(createdTimestamp)} by user ${createdBy}\n\n`;
notebookAsText += `Updated on ${this.formatTimeStamp(modifiedTimestamp)} by user ${modifiedBy}\n\n`;
}
if (changes.exportTags) {
const domainObjectKeyString = this.openmct.objects.makeKeyString(domainObject.identifier);
const tags = this.getTagsForEntry(entry, domainObjectKeyString, foundAnnotations);
const tagNames = tags.map(tag => this.getTagName(tag, availableTags));
if (tagNames) {
notebookAsText += `Tags: ${tagNames.join(', ')}\n\n`;
}
}
notebookAsText += `${entry.text}\n\n`;
});
});
});
const blob = new Blob([notebookAsText], {type: "text/markdown"});
const fileName = domainObject.name + '.md';
saveAs(blob, fileName);
}
async showForm(objectPath) {
const formStructure = {
title: "Export Notebook Text",
sections: [
{
rows: [
{
key: "exportMetaData",
control: "toggleSwitch",
name: "Include Metadata (created/modified, etc.)",
required: true,
value: false
},
{
name: "Include Tags",
control: "toggleSwitch",
required: true,
key: 'exportTags',
value: false
}
]
}
]
};
const changes = await this.openmct.forms.showForm(formStructure);
return this.onSave(changes, objectPath);
}
}

View File

@@ -236,7 +236,7 @@ export default {
sidebarCoversEntries: false,
filteredAndSortedEntries: [],
notebookAnnotations: {},
selectedEntryId: '',
selectedEntryId: undefined,
activeTransaction: false,
savingTransaction: false
};
@@ -381,8 +381,10 @@ export default {
});
},
updateSelection(selection) {
if (selection?.[0]?.[0]?.context?.targetDetails?.entryId === undefined) {
this.selectedEntryId = '';
const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
if (selection?.[0]?.[0]?.context?.targetDetails?.[keyString]?.entryId === undefined) {
this.selectedEntryId = undefined;
}
},
async loadAnnotations() {
@@ -522,6 +524,8 @@ export default {
this.openmct.notifications.alert('Warning: unable to delete entry');
console.error(`unable to delete entry ${entryId} from section ${this.selectedSection}, page ${this.selectedPage}`);
this.cancelTransaction();
return;
}
@@ -534,10 +538,15 @@ export default {
emphasis: true,
callback: () => {
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
entries.splice(entryPos, 1);
this.updateEntries(entries);
this.filterAndSortEntries();
this.removeAnnotations(entryId);
if (entries) {
entries.splice(entryPos, 1);
this.updateEntries(entries);
this.filterAndSortEntries();
this.removeAnnotations(entryId);
} else {
this.cancelTransaction();
}
dialog.dismiss();
}
},

View File

@@ -54,7 +54,7 @@
class="c-ne__remove c-icon-button c-icon-button--major icon-trash"
title="Delete this entry"
tabindex="-1"
@click="deleteEntry"
@click.stop.prevent="deleteEntry"
>
</button>
</span>
@@ -466,7 +466,10 @@ export default {
if (!this.isSelectedEntry) {
$event.preventDefault();
// blur the previous focused entry if clicking on non selected entry input
document.activeElement.blur();
const focusedElementId = document.activeElement?.id;
if (focusedElementId !== this.entry.id) {
document.activeElement.blur();
}
}
},
editingEntry() {

View File

@@ -76,13 +76,6 @@
}
.c-list__item {
@include hover() {
[class*="__menu-indicator"] {
opacity: 0.7;
transition: $transIn;
}
}
> * + * {
margin-left: $interiorMargin;
}
@@ -92,10 +85,10 @@
}
&__menu-indicator {
// Not sure this is being used
flex: 0 0 auto;
font-size: 0.8em;
opacity: 0;
transition: $transOut;
}
}
}

View File

@@ -21,6 +21,7 @@
*****************************************************************************/
import CopyToNotebookAction from './actions/CopyToNotebookAction';
import ExportNotebookAsTextAction from './actions/ExportNotebookAsTextAction';
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
import NotebookViewProvider from './NotebookViewProvider';
import NotebookType from './NotebookType';
@@ -80,6 +81,7 @@ function installBaseNotebookFunctionality(openmct) {
};
openmct.types.addType('notebookSnapshotImage', notebookSnapshotImageType);
openmct.actions.register(new CopyToNotebookAction(openmct));
openmct.actions.register(new ExportNotebookAsTextAction(openmct));
const notebookSnapshotIndicator = new Vue ({
components: {

View File

@@ -22,7 +22,9 @@
<template>
<div
v-if="loaded"
ref="plot"
class="gl-plot"
:class="{ 'js-series-data-loaded' : seriesDataLoaded }"
>
<slot></slot>
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
@@ -347,6 +349,9 @@ export default {
const parentLeftTickWidth = this.parentYTickWidth.leftTickWidth;
return parentLeftTickWidth || leftTickWidth;
},
seriesDataLoaded() {
return ((this.pending === 0) && this.loaded);
}
},
watch: {
@@ -400,7 +405,7 @@ export default {
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.updateStatus);
this.openmct.objectViews.on('clearData', this.clearData);
this.$on('loadingUpdated', this.loadAnnotations);
this.$on('loadingComplete', this.loadAnnotations);
this.openmct.selection.on('change', this.updateSelection);
this.setTimeContext();
@@ -412,10 +417,11 @@ export default {
this.openmct.selection.off('change', this.updateSelection);
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
document.body.removeEventListener('click', this.cancelSelection);
this.destroy();
},
methods: {
updateSelection(selection) {
async updateSelection(selection) {
const selectionContext = selection?.[0]?.[0]?.context?.item;
// on clicking on a search result we highlight the annotation and zoom - we know it's an annotation result when isAnnotationSearchResult === true
// We shouldn't zoom when we're selecting existing annotations to view them or creating new annotations.
@@ -434,15 +440,7 @@ export default {
return;
}
const currentXaxis = this.config.xAxis.get('displayRange');
const currentYaxis = this.config.yAxis.get('displayRange');
// when there is no plot data, the ranges can be undefined
// in which case we should not perform selection
if (!currentXaxis || !currentYaxis) {
return;
}
await this.waitForAxesToLoad();
const selectedAnnotations = selection?.[0]?.[0]?.context?.annotations;
//This section is only for the annotations search results entry to displaying annotations
if (isAnnotationSearchResult) {
@@ -452,10 +450,39 @@ export default {
//This section is common to all entry points for annotation display
this.prepareExistingAnnotationSelection(selectedAnnotations);
},
cancelSelection(event) {
if (this.$refs?.plot) {
const clickedInsidePlot = this.$refs.plot.contains(event.target);
const clickedInsideInspector = event.target.closest('.js-inspector') !== null;
const clickedOption = event.target.closest('.js-autocomplete-options') !== null;
if (!clickedInsidePlot && !clickedInsideInspector && !clickedOption) {
this.rectangles = [];
this.annotationSelections = [];
this.selectPlot();
document.body.removeEventListener('click', this.cancelSelection);
}
}
},
waitForAxesToLoad() {
return new Promise(resolve => {
// When there is no plot data, the ranges can be undefined
// in which case we should not perform selection.
const currentXaxis = this.config.xAxis.get('displayRange');
const currentYaxis = this.config.yAxis.get('displayRange');
if (!currentXaxis || !currentYaxis) {
this.$once('loadingComplete', () => {
resolve();
});
} else {
resolve();
}
});
},
showAnnotationsFromSearchResults(selectedAnnotations) {
//Start section
if (selectedAnnotations?.length) {
// pause the plot if we haven't already so we can actually display
// the annotations
this.freeze();
// just use first annotation
const boundingBoxes = Object.values(selectedAnnotations[0].targets);
let minX = Number.MAX_SAFE_INTEGER;
@@ -672,6 +699,9 @@ export default {
stopLoading() {
this.pending -= 1;
this.updateLoading();
if (this.pending === 0) {
this.$emit('loadingComplete');
}
},
updateLoading() {
@@ -1265,6 +1295,8 @@ export default {
}
this.openmct.selection.select(selection, true);
document.body.addEventListener('click', this.cancelSelection);
},
selectNewPlotAnnotations(boundingBoxPerYAxis, pointsInBox, event) {
let targetDomainObjects = {};
@@ -1687,6 +1719,9 @@ export default {
},
resumeRealtimeData() {
// remove annotation selections
this.rectangles = [];
this.clearPanZoomHistory();
this.userViewportChangeEnd();
},

View File

@@ -603,19 +603,20 @@ export default {
const mainYAxisId = this.config.yAxis.get('id');
//There has to be at least one yAxis
const yAxisIds = [mainYAxisId].concat(this.config.additionalYAxes.map(yAxis => yAxis.get('id')));
// Repeat drawing for all yAxes
yAxisIds.forEach((id) => {
if (this.canDraw(id)) {
this.updateViewport(id);
this.drawSeries(id);
this.drawRectangles(id);
this.drawHighlights(id);
// only draw these in fixed time mode or plot is paused
if (this.annotationViewingAndEditingAllowed) {
this.drawAnnotatedPoints(id);
this.drawAnnotationSelections(id);
}
// Repeat drawing for all yAxes
yAxisIds.filter(this.canDraw).forEach((id, yAxisIndex) => {
this.updateViewport(id);
this.drawSeries(id);
if (yAxisIndex === 0) {
this.drawRectangles(id);
}
this.drawHighlights(id);
// only draw these in fixed time mode or plot is paused
if (this.annotationViewingAndEditingAllowed) {
this.drawAnnotatedPoints(id);
this.drawAnnotationSelections(id);
}
});
},

View File

@@ -46,6 +46,7 @@ export default class RemoteClock extends DefaultClock {
this.timeTelemetryObject = undefined;
this.parseTime = undefined;
this.formatTime = undefined;
this.metadata = undefined;
this.lastTick = 0;
@@ -137,6 +138,10 @@ export default class RemoteClock extends DefaultClock {
this.parseTime = (datum) => {
return timeFormatter.parse(datum);
};
this.formatTime = (datum) => {
return timeFormatter.format(datum);
};
}
/**

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -20,6 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const SPECIAL_MESSAGE_TYPES = ['layout', 'flexible-layout'];
export default class RemoveAction {
#transaction;
@@ -39,28 +41,37 @@ export default class RemoveAction {
}
async invoke(objectPath) {
let object = objectPath[0];
let parent = objectPath[1];
const child = objectPath[0];
const parent = objectPath[1];
try {
await this.showConfirmDialog(object);
await this.showConfirmDialog(child, parent);
} catch (error) {
return; // form canceled, exit invoke
}
await this.removeFromComposition(parent, object);
await this.removeFromComposition(parent, child, objectPath);
if (this.inNavigationPath(object)) {
if (this.inNavigationPath(child)) {
this.navigateTo(objectPath.slice(1));
}
}
showConfirmDialog(object) {
showConfirmDialog(child, parent) {
let message = 'Warning! This action will remove this object. Are you sure you want to continue?';
if (SPECIAL_MESSAGE_TYPES.includes(parent.type)) {
const type = this.openmct.types.get(parent.type);
const typeName = type.definition.name;
message = `Warning! This action will remove this item from the ${typeName}. Are you sure you want to continue?`;
}
return new Promise((resolve, reject) => {
let dialog = this.openmct.overlays.dialog({
title: `Remove ${object.name}`,
const dialog = this.openmct.overlays.dialog({
title: `Remove ${child.name}`,
iconClass: 'alert',
message: 'Warning! This action will remove this object. Are you sure you want to continue?',
message,
buttons: [
{
label: 'OK',
@@ -94,13 +105,13 @@ export default class RemoveAction {
this.openmct.router.navigate('#/browse/' + urlPath);
}
async removeFromComposition(parent, child) {
async removeFromComposition(parent, child, objectPath) {
this.startTransaction();
const composition = this.openmct.composition.get(parent);
composition.remove(child);
if (!this.isAlias(child, parent)) {
if (!this.openmct.objects.isObjectPathToALink(child, objectPath)) {
this.openmct.objects.mutate(child, 'location', null);
}
@@ -111,18 +122,6 @@ export default class RemoveAction {
await this.saveTransaction();
}
isAlias(child, parent) {
if (parent === undefined) {
// then it's a root item, not an alias
return false;
}
const parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
const childLocation = child.location;
return childLocation !== parentKeyString;
}
appliesTo(objectPath) {
const parent = objectPath[1];
const parentType = parent && this.openmct.types.get(parent.type);
@@ -130,9 +129,9 @@ export default class RemoveAction {
const locked = child.locked ? child.locked : parent && parent.locked;
const isEditing = this.openmct.editor.isEditing();
const isPersistable = this.openmct.objects.isPersistable(child.identifier);
const isAlias = this.isAlias(child, parent);
const isLink = this.openmct.objects.isObjectPathToALink(child, objectPath);
if (locked || (!isPersistable && !isAlias)) {
if (!isLink && (locked || !isPersistable)) {
return false;
}
@@ -142,9 +141,8 @@ export default class RemoveAction {
}
}
return parentType
&& parentType.definition.creatable
&& Array.isArray(parent.composition);
return parentType?.definition.creatable
&& Array.isArray(parent?.composition);
}
startTransaction() {

View File

@@ -52,6 +52,10 @@ describe("The Remove Action plugin", () => {
objectKeyStrings: ['folder'],
overwrite: {
folder: {
identifier: {
namespace: "",
key: "parent-folder-object"
},
name: "Parent Folder",
composition: [childObject.identifier]
}
@@ -116,10 +120,18 @@ describe("The Remove Action plugin", () => {
expect(applies).toBe(true);
});
it("should be false when the child is locked", () => {
it("should be false when the child is locked and not an alias", () => {
childObject.locked = true;
childObject.location = 'parent-folder-object';
let applies = removeAction.appliesTo([childObject, parentObject]);
expect(applies).toBe(false);
});
it("should be true when the child is locked and IS an alias", () => {
childObject.locked = true;
childObject.location = 'other-folder-object';
let applies = removeAction.appliesTo([childObject, parentObject]);
expect(applies).toBe(true);
});
});
});

View File

@@ -29,7 +29,7 @@ define([
'./TelemetryTableColumn',
'./TelemetryTableUnitColumn',
'./TelemetryTableConfiguration',
'@/utils/staleness'
'../../utils/staleness'
], function (
EventEmitter,
_,

View File

@@ -88,7 +88,7 @@ define([], function () {
}
getContextMenuActions() {
return ['viewDatumAction'];
return ['viewDatumAction', 'viewHistoricalData'];
}
}

View File

@@ -175,14 +175,22 @@ export default {
getDatum() {
return this.row.fullDatum;
},
showContextMenu: function (event) {
showContextMenu: async function (event) {
event.preventDefault();
this.updateViewContext();
this.markRow(event);
const contextualDomainObject = await this.row.getContextualDomainObject?.(this.openmct, this.row.objectKeyString);
let objectPath = this.objectPath;
if (contextualDomainObject) {
objectPath = objectPath.slice();
objectPath.unshift(contextualDomainObject);
}
const actions = this.row.getContextMenuActions().map(key => this.openmct.actions.getAction(key));
const menuItems = this.openmct.menus.actionsToMenuItems(actions, this.objectPath, this.currentView);
const menuItems = this.openmct.menus.actionsToMenuItems(actions, objectPath, this.currentView);
if (menuItems.length) {
this.openmct.menus.showMenu(event.x, event.y, menuItems);
}

View File

@@ -49,12 +49,10 @@
background-size: 3px 30%;
background-color: $colorBodyBgSubtle;
box-shadow: inset rgba(black, 0.4) 0 1px 1px;
transition: $transOut;
svg text {
fill: $colorBodyFg;
stroke: $colorBodyBgSubtle;
transition: $transOut;
}
}

View File

@@ -68,7 +68,6 @@
&:hover,
&:active {
cursor: col-resize;
filter: $timeConductorAxisHoverFilter;
}
}
}
@@ -269,7 +268,6 @@
grid-column-gap: 3px;
grid-row-gap: 4px;
align-items: start;
filter: $filterMenu;
box-shadow: $shdwMenu;
padding: $interiorMargin;
position: absolute;

View File

@@ -30,6 +30,7 @@
:header-items="headerItems"
:default-sort="defaultSort"
class="sticky"
@sortChanged="updateDefaultSort"
/>
</div>
</template>
@@ -38,9 +39,8 @@
import {getValidatedData} from "../plan/util";
import ListView from '../../ui/components/List/ListView.vue';
import {getPreciseDuration} from "../../utils/duration";
import ticker from 'utils/clock/Ticker';
import {SORT_ORDER_OPTIONS} from "./constants";
import _ from 'lodash';
import moment from "moment";
import { v4 as uuid } from 'uuid';
@@ -53,16 +53,26 @@ const headerItems = [
isSortable: true,
property: 'start',
name: 'Start Time',
format: function (value, object) {
return `${moment(value).format(TIME_FORMAT)}Z`;
format: function (value, object, key, openmct) {
const clock = openmct.time.clock();
if (clock && clock.formatTime) {
return clock.formatTime(value);
} else {
return `${moment(value).format(TIME_FORMAT)}Z`;
}
}
}, {
defaultDirection: true,
isSortable: true,
property: 'end',
name: 'End Time',
format: function (value, object) {
return `${moment(value).format(TIME_FORMAT)}Z`;
format: function (value, object, key, openmct) {
const clock = openmct.time.clock();
if (clock && clock.formatTime) {
return clock.formatTime(value);
} else {
return `${moment(value).format(TIME_FORMAT)}Z`;
}
}
}, {
defaultDirection: false,
@@ -119,7 +129,8 @@ export default {
this.unlistenConfig = this.openmct.objects.observe(this.domainObject, 'configuration', this.setViewFromConfig);
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
this.status = this.openmct.status.get(this.domainObject.identifier);
this.unlistenTicker = ticker.listen(this.clearPreviousActivities);
this.updateTimestamp = _.throttle(this.updateTimestamp, 1000);
this.openmct.time.on('bounds', this.updateTimestamp);
this.openmct.editor.on('isEditing', this.setEditState);
@@ -144,10 +155,6 @@ export default {
this.unlistenConfig();
}
if (this.unlistenTicker) {
this.unlistenTicker();
}
if (this.removeStatusListener) {
this.removeStatusListener();
}
@@ -192,8 +199,8 @@ export default {
}
},
updateTimestamp(_bounds, isTick) {
if (isTick === true) {
this.timestamp = this.openmct.time.clock().currentValue();
if (isTick === true && this.openmct.time.clock() !== undefined) {
this.updateTimeStampAndListActivities(this.openmct.time.clock().currentValue());
}
},
setViewFromClock(newClock) {
@@ -202,12 +209,11 @@ export default {
if (isFixedTime) {
this.hideAll = false;
this.showAll = true;
// clear invokes listActivities
this.clearPreviousActivities(this.openmct.time.bounds()?.start);
this.updateTimeStampAndListActivities(this.openmct.time.bounds()?.start);
} else {
this.setSort();
this.setViewBounds();
this.listActivities();
this.updateTimeStampAndListActivities(this.openmct.time.clock().currentValue());
}
},
addItem(domainObject) {
@@ -340,18 +346,15 @@ export default {
groups.forEach((key) => {
activities = activities.concat(this.planData[key]);
});
activities = activities.sort(this.sortByProperty);
activities = activities.filter(this.filterActivities);
activities = this.applyStyles(activities);
this.setScrollTop();
// sort by start time
this.planActivities = activities.sort(this.sortByStartTime);
this.planActivities = activities;
},
clearPreviousActivities(time) {
if (time instanceof Date) {
this.timestamp = time.getTime();
} else {
this.timestamp = time;
}
updateTimeStampAndListActivities(time) {
this.timestamp = time;
this.listActivities();
},
@@ -471,16 +474,29 @@ export default {
const sortOrder = SORT_ORDER_OPTIONS[this.domainObject.configuration.sortOrderIndex];
const property = sortOrder.property;
const direction = sortOrder.direction.toLowerCase() === 'asc';
this.defaultSort = {
this.updateDefaultSort({
property,
defaultDirection: direction
direction
});
},
updateDefaultSort(sortOption) {
this.defaultSort = {
property: sortOption.property,
defaultDirection: sortOption.direction
};
},
sortByStartTime(a, b) {
const numA = parseInt(a.start, 10);
const numB = parseInt(b.start, 10);
sortByProperty(a, b) {
const property = this.defaultSort.property;
const defaultDirection = this.defaultSort.defaultDirection;
return numA - numB;
const numA = parseInt(a[property], 10);
const numB = parseInt(b[property], 10);
if (defaultDirection) {
return numA - numB;
} else {
return numB - numA;
}
},
setStatus(status) {
this.status = status;

View File

@@ -72,7 +72,7 @@ $colorHeadBg: #262626;
$colorHeadFg: $colorBodyFg;
$colorKey: #0099cc;
$colorKeyFg: #fff;
$colorKeyHov: #26d8ff;
$colorKeyHov: lighten($colorKey, 10%);
$colorKeyFilter: invert(36%) sepia(76%) saturate(2514%) hue-rotate(170deg) brightness(99%) contrast(101%);
$colorKeyFilterHov: invert(63%) sepia(88%) saturate(3029%) hue-rotate(154deg) brightness(101%) contrast(100%);
$colorKeySelectedBg: $colorKey;
@@ -86,7 +86,7 @@ $colorSelectedFg: pullForward($colorBodyFg, 20%);
// Object labels
$objectLabelTypeIconOpacity: 0.7;
$objectLabelNameFilter: brightness(1.3);
$objectLabelNameColorFg: lighten($colorBodyFg, 10%);
// Layout
$shellMainPad: 4px 0;
@@ -135,7 +135,7 @@ $colorPausedFg: #333;
// Base variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
$colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%);
$colorKeySubtle: pushBack($colorKey, 10%);
// Time Colors
@@ -202,6 +202,7 @@ $colorTabsHolderBg: rgba(black, 0.2);
// Buttons and Controls
$colorBtnBg: pullForward($colorBodyBg, 10%);
$colorBtnBgHov: pullForward($colorBtnBg, 10%);
$shdwBtnHov: inset rgba(white, 10%) 0 0 0 100px;
$colorBtnFg: pullForward($colorBodyFg, 10%);
$colorBtnReverseFg: pullForward($colorBtnFg, 10%);
$colorBtnReverseBg: pullForward($colorBtnBg, 10%);
@@ -333,6 +334,12 @@ $colorLimitCyanBg: #4BA6B3;
$colorLimitCyanFg: #D3FAFF;
$colorLimitCyanIc: #6BEDFF;
// Events
$colorEventPurpleFg: #6433ff;
$colorEventRedFg: #cc0000;
$colorEventOrangeFg: orange;
$colorEventYellowFg: #ffcc00;
// Bubble colors
$colorInfoBubbleBg: #dddddd;
$colorInfoBubbleFg: #666;
@@ -464,6 +471,8 @@ $transInTime: 50ms;
$transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInTransform: transform $transInTime ease-in-out;
$transOutTransform: transform $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);

View File

@@ -337,6 +337,12 @@ $colorLimitCyanBg: #4BA6B3;
$colorLimitCyanFg: #D3FAFF;
$colorLimitCyanIc: #6BEDFF;
// Events
$colorEventPurpleFg: #6433ff;
$colorEventRedFg: #cc0000;
$colorEventOrangeFg: orange;
$colorEventYellowFg: #ffcc00;
// Bubble colors
$colorInfoBubbleBg: #dddddd;
$colorInfoBubbleFg: #666;
@@ -468,6 +474,8 @@ $transInTime: 50ms;
$transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInTransform: transform $transInTime ease-in-out;
$transOutTransform: transform $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);

View File

@@ -30,6 +30,7 @@ $mobileOverlayMargin: 20px;
$mobileMenuIconD: 25px;
$phoneItemH: floor(math.div($gridItemMobile, 4));
$tabletItemH: floor(math.div($gridItemMobile, 3));
$shellTimeConductorMobileH: 90px;
/************************** MOBILE TREE MENU DIMENSIONS */
$mobileTreeItemH: 35px;

View File

@@ -86,7 +86,7 @@ $colorSelectedFg: pullForward($colorBodyFg, 10%);
// Object labels
$objectLabelTypeIconOpacity: 0.5;
$objectLabelNameFilter: brightness(0.9);
$objectLabelNameColorFg: darken($colorBodyFg, 10%);
// Layout
$shellMainPad: 4px 0;
@@ -135,7 +135,7 @@ $colorPausedFg: #fff;
// Base variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
$colorBodyBgSubtleHov: pullForward($colorBodyBg, 10%);
$colorKeySubtle: pushBack($colorKey, 20%);
// Time Colors
@@ -202,6 +202,7 @@ $colorTabsHolderBg: rgba($colorBodyFg, 0.2);
// Buttons and Controls
$colorBtnBg: #aaa;
$colorBtnBgHov: pullForward($colorBtnBg, 10%);
$shdwBtnHov: inset rgba(white, 10%) 0 0 0 20px;
$colorBtnFg: #fff;
$colorBtnReverseFg: $colorBodyBg;
$colorBtnReverseBg: $colorBodyFg;
@@ -333,6 +334,12 @@ $colorLimitCyanBg: #4BA6B3;
$colorLimitCyanFg: #D3FAFF;
$colorLimitCyanIc: #1795c0;
// Events
$colorEventPurpleFg: #6433ff;
$colorEventRedFg: #cc0000;
$colorEventOrangeFg: orange;
$colorEventYellowFg: #ffcc00;
// Bubble colors
$colorInfoBubbleBg: $colorMenuBg;
$colorInfoBubbleFg: #666;
@@ -464,6 +471,8 @@ $transInTime: 50ms;
$transOutTime: 250ms;
$transIn: all $transInTime ease-in-out;
$transOut: all $transOutTime ease-in-out;
$transInTransform: transform $transInTime ease-in-out;
$transOutTransform: transform $transOutTime ease-in-out;
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);

View File

@@ -47,6 +47,9 @@ $overlayOuterMarginDialog: (5%, 20%);
$overlayInnerMargin: 25px;
$mainViewPad: 0px;
$treeNavArrowD: 20px;
$shellMainBrowseBarH: 22px;
$shellTimeConductorH: 55px;
$shellToolBarH: 29px;
/*************** Items */
$itemPadLR: 5px;
$gridItemDesk: 175px;

View File

@@ -565,7 +565,7 @@ select {
}
@include hover() {
filter: $filterHov;
box-shadow: $shdwBtnHov;
}
&.is-current {
@@ -725,11 +725,6 @@ select {
width: $d;
height: $d;
text-align: center;
transition: $transOut;
&:hover {
transition: $transIn;
}
&.is-selected {
border-width: 1px;
@@ -1084,7 +1079,7 @@ input[type="range"] {
// Controls that are in close proximity to an element they effect
&--show-on-hover {
// Hidden by default; requires a hover 1 - 3 levels above to display
transition: $transOut;
@include transition(opacity, $transOutTime);
opacity: 0;
pointer-events: none;
}
@@ -1096,7 +1091,7 @@ input[type="range"] {
> * > * > .c-local-controls--show-on-hover,
> * > * > * > .c-local-controls--show-on-hover
{
transition: $transIn;
@include transition(opacity);
opacity: 1;
pointer-events: inherit;
@@ -1109,6 +1104,7 @@ input[type="range"] {
.c-drop-hint {
// Used in Tabs View, Flexible Grid Layouts
@include abs();
@include transition($prop: background-color, $dur: $transOutTime);
background-color: $colorDropHintBg;
color: $colorDropHintFg;
border-radius: $basicCr;
@@ -1117,7 +1113,6 @@ input[type="range"] {
flex-direction: column;
justify-content: center;
align-items: center;
transition: $transOut;
z-index: 50;
&:not(.c-drop-hint--always-show) {
@@ -1142,13 +1137,11 @@ input[type="range"] {
.is-dragging &,
&.is-dragging {
pointer-events: inherit;
transition: $transIn;
opacity: 0.8;
}
.is-mouse-over &,
&.is-mouse-over {
transition: $transIn;
background-color: $colorDropHintBgHov;
opacity: 0.9;
}

View File

@@ -60,7 +60,7 @@
margin-right: 0;
min-width: 0;
overflow: hidden;
transition: $transOut;
@include transition($prop: width, $dur: $transOutTime);
width: 0;
.c-icon-button:before { font-size: 1em; }
@@ -78,7 +78,7 @@
&:hover {
.c-timer__controls {
transition: $transOut; // On purpose: want this to take a bit longer
@include transition($prop: width, $dur: $transOutTime); // On purpose: want this to take a bit longer
margin-right: $interiorMargin;
width: $ctrlW * 2;
}
@@ -168,7 +168,6 @@
.widget-rules-wrapper,
.widget-rule-content,
.w-widget-test-data-content {
transition: $transIn;
min-height: 0;
height: 0;
opacity: 0;
@@ -430,7 +429,6 @@
&:hover {
.l-autoflow-header .s-button.change-column-width {
transition: $transIn;
opacity: 1;
}
}
@@ -444,7 +442,7 @@
overflow: hidden;
}
.s-button.change-column-width {
transition: $transOut;
@include transition($prop: opacity, $dur: $transOutTime);
opacity: 0;
}
.l-filter {
@@ -737,7 +735,7 @@ body.desktop {
&:hover {
&:after {
background-color: $colorSplitterHover !important;
transiiton: background-color, 150ms;
@include transition($prop: background-color, $dur: 150ms);
}
}
}

View File

@@ -96,6 +96,13 @@
animation-timing-function: ease-in-out;
}
@mixin transition($prop: all, $dur: $transInTime, $timing: ease-in-out, $delay: 0ms) {
transition-property: $prop;
transition-duration: $dur;
transition-timing-function: $timing;
transition-delay: $delay;
}
/************************** VISUALS */
@mixin ancillaryIcon($d, $c) {
// Used for small icons used in combination with larger icons,
@@ -463,22 +470,12 @@
}
@mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) {
// Is this being used? Remove if not.
background: $bg;
color: $fg;
border-radius: $radius;
box-shadow: $shdw;
}
@mixin buttonBehavior() {
// Assign transition timings
transition: $transOut;
@include hover() {
transition: $transIn;
}
}
@mixin cControl() {
$fs: 1em;
@include userSelectNone();
@@ -515,8 +512,18 @@
}
}
@mixin cControlHov($styleConst: $shdwBtnHov) {
transition: box-shadow $transOutTime;
@include hover() {
transition: box-shadow $transInTime;
box-shadow: $styleConst !important;
}
}
@mixin cButton() {
@include cControl();
@include cControlHov();
@include themedButton();
border-radius: $controlCr;
color: $colorBtnFg;
@@ -528,10 +535,6 @@
margin-left: $interiorMarginSm;
}
@include hover() {
filter: $filterHov;
}
&[class*="--major"],
&[class*='is-active']{
background: $colorBtnMajorBg;
@@ -546,11 +549,11 @@
@mixin cClickIcon() {
@include cControl();
@include cControlHov();
color: $colorBodyFg;
cursor: pointer;
padding: 4px; // Bigger hit area
opacity: 0.7;
transition: $transOut;
transform-origin: center;
&[class*="--major"] {
@@ -560,7 +563,6 @@
@include hover() {
transform: scale(1.1);
transition: $transIn;
opacity: 1;
}
}
@@ -584,21 +586,14 @@
// Padding is included to facilitate a bigger hit area
// Make the icon bigger relative to its container
@include cControl();
@include cControlHov();
@include cClickIconButtonLayout();
background: none;
color: $colorClickIconButton;
box-shadow: none;
cursor: pointer;
transition: $transOut;
border-radius: $controlCr;
@include hover() {
transition: $transIn;
background: $colorClickIconButtonBgHov;
//color: $colorClickIconButtonFgHov;
filter: $filterHov;
}
&[class*="--major"] {
color: $colorBtnMajorBg !important;
}
@@ -654,10 +649,6 @@
border-color: $colorFg;
}
@include hover {
filter: $filterHov;
}
&--up, &--prev {
&:before {
transform: translate(-30%, -50%) rotate(135deg);

View File

@@ -192,3 +192,12 @@ tr {
@include isStatus($glyph: $glyph-icon-alert-rect, $color: $colorWarningLo);
}
}
.is-event {
&--purple { color: $colorEventPurpleFg !important; }
&--red { color: $colorEventRedFg !important; }
&--orange { color: $colorEventOrangeFg !important; }
&--yellow { color: $colorEventYellowFg !important; }
&--no-style { color: inherit; }
}

View File

@@ -208,9 +208,19 @@ div.c-table {
}
th, td {
width: 33%; // Needed to prevent size jumping as values dynamically update
overflow: hidden;
text-overflow: ellipsis;
}
tbody tr {
&:hover {
background: $colorItemTreeHoverBg;
}
}
td {
user-select: none; // Table supports context-click to display Actions menu, don't allow text selection.
&.is-stale {
@include isStaleElement();
}

View File

@@ -37,7 +37,7 @@ export default {
// eslint-disable-next-line you-dont-need-lodash-underscore/get
let value = _.get(this.item, property.key);
if (property.format) {
value = property.format(value, this.item, property.key);
value = property.format(value, this.item, property.key, this.openmct);
}
values.push({

View File

@@ -136,6 +136,11 @@ export default {
)
);
}
this.$emit('sortChanged', {
property: this.sortBy,
defaultDirection: this.ascending
});
}
}
};

View File

@@ -2,7 +2,6 @@
.c-list-view {
tbody tr {
background: $colorListItemBg;
transition: $transOut;
}
td {
@@ -22,8 +21,6 @@
&:hover {
background: $colorListItemBgHov;
filter: $filterHov;
transition: $transIn;
}
}
}

View File

@@ -286,6 +286,7 @@ export default {
this.openmct.objectViews.on('clearData', this.clearData);
this.$nextTick(() => {
this.updateStyle(this.styleRuleManager?.currentStyle);
this.getActionCollection();
});
},

View File

@@ -20,7 +20,7 @@
}
&__name {
filter: $objectLabelNameFilter;
color: $objectLabelNameColorFg;
}
}
}
@@ -61,7 +61,7 @@
pointer-events: none;
position: absolute;
top: 0; right: 0; bottom: auto; left: 0;
z-index: 2;
z-index: 10;
.c-object-label {
visibility: hidden;
@@ -99,6 +99,8 @@
}
}
}
&.c-so-view--flexible-layout,
&.c-so-view--layout {
// For sub-layouts with hidden frames, completely hide the header to avoid overlapping buttons
> .c-so-view__header {

View File

@@ -22,6 +22,7 @@
&__name {
@include ellipsize();
color: $objectLabelNameColorFg;
display: inline;
padding: 1px 0;
}

View File

@@ -1,7 +1,7 @@
@mixin visibleRegexButton {
opacity: 1;
padding: 1px 3px;
width: 24px;
min-width: 24px;
}
.c-search {
@@ -31,7 +31,7 @@
overflow: hidden;
padding: 1px 0;
transform-origin: left;
transition: $transOut;
@include transition($prop: min-width, $dur: $transOutTime);
width: 0;
&.is-active {
@@ -54,8 +54,19 @@
margin-left: 0;
}
.c-search__clear-input {
display: block;
&:before {
width: 0;
}
input[type='text'],
input[type='search'] {
margin-left: 0;
}
@include hover {
.c-search__clear-input {
display: block;
}
}
}
@@ -66,10 +77,9 @@
text-align: left;
}
&:hover {
@include hover {
.c-search__use-regex {
@include visibleRegexButton();
transition: $transIn;
}
}
}

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
<template>
<div class="c-inspector">
<div class="c-inspector js-inspector">
<object-name />
<InspectorTabs
:selection="selection"

View File

@@ -25,10 +25,6 @@
}
&__selected {
.c-object-label__name {
filter: $objectLabelNameFilter;
}
.c-object-label__type-icon {
opacity: $objectLabelTypeIconOpacity;
}

View File

@@ -93,9 +93,18 @@
:persist-position="true"
>
<RecentObjectsList
ref="recentObjectsList"
class="l-shell__tree"
@openAndScrollTo="openAndScrollTo($event)"
/>
<button
slot="controls"
class="c-icon-button icon-clear-data"
aria-label="Clear Recently Viewed"
title="Clear Recently Viewed"
@click="handleClearRecentObjects"
>
</button>
</pane>
</multipane>
</pane>
@@ -279,6 +288,9 @@ export default {
handleTreeReset() {
this.triggerReset = !this.triggerReset;
},
handleClearRecentObjects() {
this.$refs.recentObjectsList.clearRecentObjects();
},
onStartResizing() {
this.isResizing = true;
},

View File

@@ -191,6 +191,33 @@ export default {
shouldTrackCompositionFor(domainObject, navigationPath) {
return this.compositionCollections[navigationPath] === undefined
&& this.openmct.composition.supportsComposition(domainObject);
},
/**
* Clears the Recent Objects list in localStorage and in the component.
* Before clearing, prompts the user to confirm the action with a dialog.
*/
clearRecentObjects() {
const dialog = this.openmct.overlays.dialog({
title: 'Clear Recently Viewed Objects',
iconClass: 'alert',
message: 'This action will clear the Recently Viewed Objects list. Are you sure you want to continue?',
buttons: [
{
label: 'OK',
callback: () => {
localStorage.removeItem(LOCAL_STORAGE_KEY__RECENT_OBJECTS);
this.recents = [];
dialog.dismiss();
}
},
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
}
}
]
});
}
}
};

View File

@@ -20,7 +20,7 @@
z-index: 70;
[class*="__icon"] {
filter: $colorKeyFilter;
filter: $colorKeyFilter;
}
[class*="__item-description"] {

View File

@@ -35,7 +35,7 @@
min-height: 0;
max-height: 15%;
overflow: hidden;
transition: $transIn;
@include transition(min-height);
&.is-expanded {
min-height: 100px;
@@ -65,8 +65,7 @@
}
&__pane-tree,
&__pane-inspector,
&__pane-main {
&__pane-inspector {
.l-pane__contents {
display: flex;
flex-flow: column nowrap;
@@ -74,8 +73,7 @@
}
}
&__pane-tree,
&__pane-main {
&__pane-tree {
.l-pane__contents {
> * {
flex: 0 0 auto;
@@ -89,6 +87,37 @@
&__pane-main {
.l-pane__header { display: none; }
.l-pane__contents {
.l-shell__main-.l-shell__main-view-browse-bar {
position: relative;
}
// Using `position: absolute` due to flex having repaint issues with the Time Conductor, #5247
.l-shell__time-conductor,
.l-shell__main-container {
position: absolute;
left: 0;
right: 0;
height: auto;
width: auto;
}
.l-shell__main-container {
top: $shellMainBrowseBarH + $interiorMarginLg;
bottom: $shellTimeConductorH + $interiorMargin;
}
@include phonePortrait() {
.l-shell__main-container {
bottom: $shellTimeConductorMobileH + $interiorMargin;
}
}
.l-shell__time-conductor {
bottom: 0;
}
}
}
body.mobile & {
@@ -307,6 +336,7 @@
height: $p + 24px; // Need to standardize the height
justify-content: space-between;
padding: $p;
z-index: 2;
}
&__resizing {
@@ -332,6 +362,7 @@
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
@@ -408,10 +439,6 @@
flex: 0 1 auto;
}
.c-object-label__name {
filter: $objectLabelNameFilter;
}
.c-object-label__type-icon {
opacity: $objectLabelTypeIconOpacity;
}
@@ -433,7 +460,7 @@
/************************** DRAWER */
.c-drawer {
/* New sliding overlay or push element to contain things
/* Sliding overlay or push element to contain things
* Designed for mobile and compact desktop scenarios
* Variations:
* --overlays: position absolute, overlays neighboring elements
@@ -442,7 +469,8 @@
* &.is-expanded: applied when expanded.
*/
transition: $transOut;
$transProps: width, min-width, height, min-height;
min-height: 0;
min-width: 0;
overflow: hidden;
@@ -455,11 +483,12 @@
}
&.c-drawer--align-left {
@include transition($prop: $transProps, $dur: $transOutTime);
height: 100%;
}
&.c-drawer--align-top {
// Need anything here?
@include transition($prop: $transProps, $dur: $transOutTime);
}
&.c-drawer--overlays {

View File

@@ -97,7 +97,7 @@
@include hover {
background: $colorItemTreeHoverBg;
filter: $filterHov;
//filter: $filterHov; // FILTER REMOVAL, CONVERT TO THEME CONSTANT
}
&.is-navigated-object,
@@ -192,14 +192,6 @@
}
}
}
&__item__label {
@include desktop {
&:hover {
filter: $filterHov;
}
}
}
}
.is-editing .is-navigated-object {

View File

@@ -355,17 +355,18 @@ export default {
this.abortItemLoad(path);
}
let pathIndex = this.openTreeItems.indexOf(path);
const pathIndex = this.openTreeItems.indexOf(path);
if (pathIndex === -1) {
return;
}
this.treeItems = this.treeItems.filter((checkItem) => {
if (checkItem.navigationPath !== path
&& checkItem.navigationPath.includes(path)) {
this.destroyObserverByPath(checkItem.navigationPath);
this.destroyMutableByPath(checkItem.navigationPath);
this.treeItems = this.treeItems.filter((item) => {
const otherPath = item.navigationPath;
if (otherPath !== path
&& this.isTreeItemAChildOf(otherPath, path)) {
this.destroyObserverByPath(otherPath);
this.destroyMutableByPath(otherPath);
return false;
}
@@ -450,13 +451,14 @@ export default {
}, Promise.resolve()).then(() => {
if (this.isSelectorTree) {
let item = this.getTreeItemByPath(navigationPath);
// If item is missing due to error in object creation,
// walk up the navigationPath until we find an item
let item = this.getTreeItemByPath(navigationPath);
while (!item) {
while (!item && navigationPath !== '') {
const startIndex = 0;
const endIndex = navigationPath.lastIndexOf('/');
navigationPath = navigationPath.substring(startIndex, endIndex);
item = this.getTreeItemByPath(navigationPath);
}
@@ -959,6 +961,24 @@ export default {
isTreeItemPathOpen(path) {
return this.openTreeItems.includes(path);
},
isTreeItemAChildOf(childNavigationPath, parentNavigationPath) {
const childPathKeys = childNavigationPath.split('/');
const parentPathKeys = parentNavigationPath.split('/');
// If child path is shorter than or same length as
// the parent path, then it's not a child.
if (childPathKeys.length <= parentPathKeys.length) {
return false;
}
for (let i = 0; i < parentPathKeys.length; i++) {
if (childPathKeys[i] !== parentPathKeys[i]) {
return false;
}
}
return true;
},
getElementStyleValue(el, style) {
if (!el) {
return;

View File

@@ -107,10 +107,10 @@
/************************************************ DESKTOP STYLES */
body.desktop & {
&__handle {
background: $colorSplitterBg;
background-color: $colorSplitterBg;
display: block;
position: absolute;
transition: $transOut;
@include transition(background-color, $transOutTime);
&:before {
// Extended hit area
@@ -121,8 +121,8 @@
}
&:hover {
background: $colorSplitterHover;
transition: $transIn;
background-color: $colorSplitterHover;
@include transition(background-color);
}
}
@@ -142,14 +142,14 @@
[class*="expand-button"] {
display: none; // Hidden by default
background: $splitterCollapsedBtnColorBg;
background-color: $splitterCollapsedBtnColorBg;
color: $splitterCollapsedBtnColorFg;
font-size: 0.9em;
&:hover {
background: $splitterCollapsedBtnColorBgHov;
background-color: $splitterCollapsedBtnColorBgHov;
color: inherit;
transition: $transIn;
@include transition(background-color);
}
}
@@ -163,7 +163,7 @@
.l-pane {
&__handle {
background: $colorSplitterHover;
background-color: $colorSplitterHover;
}
}
}

View File

@@ -129,9 +129,11 @@ export default {
mounted() {
this.previewAction = new PreviewAction(this.openmct);
this.previewAction.on('isVisible', this.togglePreviewState);
this.clickedPlotAnnotation = this.clickedPlotAnnotation.bind(this);
},
destroyed() {
this.previewAction.off('isVisible', this.togglePreviewState);
this.openmct.selection.off('change', this.clickedPlotAnnotation);
},
methods: {
clickedResult(event) {
@@ -141,15 +143,19 @@ export default {
this.preview(objectPath);
} else {
const resultUrl = identifierToString(this.openmct, objectPath);
if (!this.openmct.router.isNavigatedObject(objectPath)) {
// if we're not on the correct page, navigate to the object,
// then wait for the selection event to fire before issuing a new selection
if (this.result.annotationType === this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL) {
this.openmct.selection.on('change', this.clickedPlotAnnotation);
}
this.openmct.router.navigate(resultUrl);
}
if (this.result.annotationType === this.openmct.annotation.ANNOTATION_TYPES.PLOT_SPATIAL) {
//wait a beat for the navigation
setTimeout(() => {
this.openmct.router.navigate(resultUrl);
} else {
// if this is the navigated object, then we are already on the correct page
// and just need to issue the selection event
this.clickedPlotAnnotation();
}, 100);
}
}
},
preview(objectPath) {
@@ -158,6 +164,8 @@ export default {
}
},
clickedPlotAnnotation() {
this.openmct.selection.off('change', this.clickedPlotAnnotation);
const targetDetails = {};
const targetDomainObjects = {};
Object.entries(this.result.targets).forEach(([key, value]) => {

View File

@@ -43,8 +43,8 @@
.s-button,
.c-button {
// Make <a> in label look like buttons
transition: $transIn;
background: transparent;
@include transition(background-color);
background-color: transparent;
border: 1px solid rgba($colorIndicatorMenuFg, 0.5);
border-radius: $controlCr;
box-sizing: border-box;
@@ -53,8 +53,8 @@
height: auto;
line-height: normal;
padding: 0 2px;
&:hover {
background: rgba($colorIndicatorMenuFg, 0.1);
@include hover {
background-color: rgba($colorIndicatorMenuFg, 0.1);
border-color: rgba($colorIndicatorMenuFg, 0.75);
color: $colorIndicatorMenuFgHov;
}

View File

@@ -49,9 +49,19 @@ export default {
},
computed: {
highlightedText() {
let regex = new RegExp(`(?<!<[^>]*)(${this.highlight})`, 'gi');
const highlight = this.highlight;
return this.text.replace(regex, `<span class="${this.highlightClass}">${this.highlight}</span>`);
const normalCharsRegex = /^[^A-Za-z0-9]+$/g;
const newHighLight = normalCharsRegex.test(highlight)
? `\\${highlight}`
: highlight;
const highlightRegex = new RegExp(`(?<!<[^>]*)(${newHighLight})`, 'gi');
const replacement = `<span class="${this.highlightClass}">${highlight}</span>`;
return this.text.replace(highlightRegex, replacement);
}
}
};