Compare commits

..

76 Commits

Author SHA1 Message Date
fd4dcc8513 Add check for destroy method on store.
Change failing test to use new data getter.
2021-08-10 09:55:22 -07:00
9ebd18318b Remve console log 2021-08-10 09:55:22 -07:00
4a89b81f4f Logging for beforeunload events 2021-08-10 09:55:22 -07:00
98e1abd7b1 Don't draw points if the count is 0 2021-08-10 09:55:22 -07:00
56c25762ac Fix resize handler for plots 2021-08-10 09:55:22 -07:00
5c8e726b87 Fix data store id 2021-08-10 09:55:22 -07:00
d80f4a1f7d Revert commented out code 2021-08-10 09:55:22 -07:00
3fe4c7a954 Revert eslint changes 2021-08-10 09:55:22 -07:00
676ef60128 Revert eslint changes 2021-08-10 09:55:22 -07:00
5a90d28450 Separate plot series data from the configuration (like it should be!) 2021-08-10 09:55:22 -07:00
2bb6822e6b Draft 2021-08-10 09:55:22 -07:00
383b4c0d8d Fix no mutating props violation for Browsebar and StyleEditor 2021-08-10 09:55:22 -07:00
404ab720ad Enable no mutating props vue lint configuration. Fix error for plots 2021-08-10 09:55:22 -07:00
259ab53060 Refactor clock object and clock indicator to remove AngularJS dependency (#4094)
* To enable clock indicator, pass in the following configuration { enableClockIndicator: true }.
2021-08-09 14:29:45 -07:00
1db7ac55b4 [Imagery] Click on image to get a large view #3582 (#4085)
fixed issue where large imagery view opens only once.
2021-08-04 15:44:50 -07:00
82b3383834 Set the yKey value on the series when it's changed (#4083) 2021-08-04 13:56:37 -07:00
ac240d524c Add check for stop observing before calling it (#4080) 2021-08-04 10:40:16 -07:00
1b034f6125 remove can edit from hyperlink (#4076) 2021-08-03 16:01:29 -07:00
b329ed6ed5 Couch object provider performance improvement using SharedWorker (#3993)
* Use the window SharedWorker instead of the WorkerService
* Use relative asset path for Shared Workers
* Remove beforeunload listener on destroy
2021-07-30 15:23:02 -07:00
9b7a0d7e4c Reimplement hyperlink vue (#4062)
* added vue hyperlink plugin
* remove angular code. update target attribute
* Polishing on form styles
- Remove `display: flex` from `.l-shell__main-container` and
`.c-so-view__object-view` CSS - IMPORTANT: NEEDS REGRESSION TESTING!
- Improvements to `.c-hyperlink` CSS;
- Markup cleanups and simplification;
- Remove duped CSS in object-frame.scss, probably result of prior bad
past merge;
* Fixes for object-frame and preview.scss
* Refinement to make hyperlink button have same display behavior as
Condition Widget;
* refactor layout template. update tests
* remove legacy hyperlink
* Updating firefox launcher

Co-authored-by: Henry Hsu <henry.hsu@nasa.gov>
Co-authored-by: charlesh88 <charles.f.hacskaylo@nasa.gov>
2021-07-30 13:49:31 -07:00
5c15e53abb Mct4039 (#4057)
Re-implements ImageExportService as ES6 class instead of Angular managed service.

Co-authored-by: John Hill <jchill2.spam@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-07-29 22:05:18 -07:00
f58b3881f2 Not everything fits into the two types of issue (#4064)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-07-29 17:17:51 -07:00
071a13b219 [Imagery] Click on image to get a large view (#3770)
* [Imagery] Click on image to get a large view #3582
* Created new viewLargeAction.
* Changes in view registry to add parent element property inside view object.
* Separate class for views and added missing changes for LadTableSet.
* Renamed callBack to onItemClicked.

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-07-29 09:19:07 -07:00
ca66898e51 [Plugin] Remote Clock based off Telemetry (#3998)
* Remote-clock plugin
* Added a default clock class that can be extended by other classes
* Updated local clock to use the parent default clock class
* added a period check to make sure its been a certain amount of time before emitting
Co-authored-by: John Hill <jchill2.spam@gmail.com>
2021-07-28 16:46:08 -07:00
94c7b2343a Add Enhancement request template (#4063)
* Add Enhancement request template

* Link to github discussions

* Update config.yml
2021-07-27 15:02:30 -07:00
c397c336ab [Telemetry Tables] Observe for changes in table configuration (#4053) 2021-07-27 14:08:44 -07:00
eea23f2caf Fix typo in template (#4059) 2021-07-27 11:05:02 -07:00
6665641c02 Update issue templates (#4058)
Add a bug report directly to the project
2021-07-27 10:38:24 -07:00
c3ebf52dd2 Prepare for sprint 1.7.6 (#4052) 2021-07-26 14:04:26 -07:00
f8f2e7da9b Remove deprecated timeline bundle (#4048) 2021-07-23 13:51:32 -07:00
240f58b2d0 [Telemetry API] wrap limits function return in promise if needed (#4044) 2021-07-20 07:25:43 -07:00
7d3baee7b5 URL Params to hide tree and inspector (#3951)
* Add checks and hide panes accordingly, toggle hide params when toggling panes, add params on change event
* add tests

Co-authored-by: Henry Hsu <henry.hsu@nasa.gov>
Co-authored-by: John Hill <jchill2.spam@gmail.com>
2021-07-19 10:01:05 -07:00
1f5cb7ca42 Prevent default on click events for conductor delta buttons (#4022)
* Prevent default on click events for conductor delta buttons
2021-07-15 14:46:53 -07:00
4a7ebe326c Plot view policy fix (#3995)
* deny plot view for non-numeric telemetry

* revert plot type for backwards compatibility
2021-07-14 18:12:26 -07:00
10da314a4a Add resize event and disable pointer events on iframes on trigger of resize (#4016) 2021-07-14 17:32:45 -07:00
b3ceccd7fb temporarily skip test on chrome (#4021) 2021-07-14 15:58:44 -07:00
1bde4c9a0c Update all unit test packages, set node engine rules, new circleci workflow, pin to stable (#3957)
* Update all unit test packages, set node engine rules, new circleci workflow

Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-07-14 09:26:38 -07:00
4b85360446 1.7.4 stable master (#4015) 2021-07-13 13:59:37 -07:00
41b860a547 Fixes for Imagery Snapshotting #3963 (#4001)
* refactored compass structure and code.
* resize image wrapper
* center image properly
* Refactor imagery compass rose as SVG
* Suppress prev/next image arrows from Snapshot

Co-authored-by: Nikhil Mandlik <nikhil.k.mandlik@nasa.gov>
2021-07-09 18:03:11 -07:00
254b3db966 added mock values to compass for example imagery. (#3999) 2021-07-09 15:13:54 -07:00
cbb3f32d1e 1.7.4 into master (#3985)
catching any errors from a user canceling from dialog (#3968)
Fix navigation errors (#3970)
Only add listeners to observables on creation
Do not double destroy mutable objects
Disallow pause and play in time strip view for plots. (#3972)
Update version

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-06-29 09:40:30 -07:00
e3bf72e77f New time conductor popups should be closed by hitting ESC key (#3978) 2021-06-29 09:03:58 -07:00
0b63b782cf Image thumbs autoscroll resumption functionality (#3892)
* add logs for testing

* check autoScroll value

* Add auto scroll button at thumbnails

* add auto scroll button functionality

* turn on auto scroll whenever scroll bar is not at the right end

* check if scroll right when the page load

* update scroll to right condition

* remove resetScroll method, refactor scrollToRight and some cleaning

* check initial status

* remove logs

* Styling for imagery thumbs 'autoscroll resume' button

- Refined look and feel of the approach;
- CSS classes now assigned at the wrapper level;
- Markup changed to allow wrapping of scroll area and button;
- TODO: prevent a drag resize of the main view area from forcing
autoscroll to pause;

* Add tests for auto scroll

* add resize observer for thumb wrapper

* reset scroll bar position when window is resized

* Revert "upgrade to webpack5 (#3871)" (#3907) (#3908)

This reverts commit e1e0eeac56.

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>

* Check for getlimits api (#3910)

* add debounce and resizing observer to thumbwrapper

* handling resizing window and auto scroll

* 1.clean up comments and logs 2.make variable names more descriptive 3.add scroll to right for play button

* fix eslint formate issue

* update class name in test

* fix eslint format error

* remove a couple variables that were created but not used.

Co-authored-by: Henry Hsu <henry.hsu@nasa.gov>
Co-authored-by: Henry Hsu <hhsu0219@gmail.com>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
Co-authored-by: Jamie Vigliotta <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-06-29 07:52:27 -07:00
da39fd0c70 Overlay fixes and improvements (#3901)
* Overlay-related fixes
- Prevent navigation when a folder's grid or list view is displayed in
an overlay;

* Overlay-related fixes
- Get rid of theme-based special overlay coloring that was making the
overlay hard to use;
- Add margin to `l-overlay-large` to make it clearer that user is in an
overlay when interacting with that view;
- Refinements to colors and layout in About screens;
2021-06-28 14:35:35 -07:00
96dd581a67 Timestrip plan Inspection (#3863)
* Stub in static HTML for Timestrip Activity Inspection
- Added static markup with placeholder values and display logic;
- Refined approach for Links;
* Refactor duration formatting
* Display activity name when it's available
* Don't use indices for keys
* Don't show properties with no labels
2021-06-28 10:04:54 -07:00
2a1e322230 adding link to discussions in readme to encourage users to showcase their use of Open MCT (#3933)
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2021-06-22 09:34:36 -07:00
300b98bd54 Disallow pan, zoom and pause/play controls in time strip view (#3936)
* Disallow pan and zoom when in time strip view

* Disable plot controls in time strip view
2021-06-22 09:25:12 -07:00
c946609d13 Added LGTM code quality badge (#3960)
We score an A, we should flaunt it! (We should also aim for A+).
2021-06-22 09:10:08 -07:00
7ca559fbe4 [Styles] add unit tests (#3557)
* unit tests for inspector styles feature
* add mock capability for local storage

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-06-22 06:50:49 -07:00
71392915c1 Mct3834 (#3962)
* remove redundant code

Co-authored-by: Henry Hsu <henry.hsu@nasa.gov>
2021-06-21 17:23:42 -07:00
2889e88a97 Styling for plot limits (#3917)
* Styling for plot limits and colors
* Updates to limit provider and css
* Change limits related CSS "*--upper" and "*--lower" to "*--upr" and
"*--lwr" for better parity with legacy naming;
* Refactor limit class to util
* Use new classes for sine wave generator for red-low and yellow-high
* Added modifier classes for right and below-aligned labels;
* Prevent label overlap of limits as much as possible
* Add border colors to limit labels for better visual ties to their lines
* Add documentation for limit level specification API change

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-06-21 16:22:28 -07:00
d56d176aac Issue #3834 Reimplement New Tab action in vanilla JS (#3876)
* [Reimplement] create new action plugin for issue #3834

Co-authored-by mariuszr mariusz.rosinski@gmail.com
Co-authored-by: Henry Hsu <henryhsu@henrys-air.lan>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-06-21 16:11:31 -07:00
925518c83f [Notebooks] Don't save images on the object (#3792)
* Create and store image data into new domain object of type 'notebookSnapshotImage'
* Reduced thumbnail size to 30px
* Image migration script for old notebooks.
* Saves thumbnail image on notebook instead of new object.
2021-06-21 15:42:33 -07:00
fa5aceb7b3 Bind method to 'this' so that its listeners are correctly unbound on destroy (#3948)
* Bind method to 'this' so that its listener can work correctly
* Bind this for toggling subscriptions as well
2021-06-21 10:44:59 -07:00
6755ef4641 Support for remote mutation of Notebooks with Couch DB (#3887)
* Update notebook automatically when modified by another user
* Don't persist selected and default page and section IDs on notebook object
* Fixing object synchronization bugs
* Adding unit tests
* Synchronize notebooks AND plans
* Removed observeEnabled flag

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2021-06-21 10:25:17 -07:00
333e8b5583 [Testing] Resolve all promises (#3829)
* all promises in test specs should be returned

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-06-18 16:06:15 -07:00
9d8a8b36d2 Move duplicate fixes (#3947)
* Changed text of form labels;
* Corrected case of "Location" in Duplicate action;
* changed from objects.mutate to objects.save for duplicate action name change
* handling cancel of move

Co-authored-by: charlesh88 <charles.f.hacskaylo@nasa.gov>
2021-06-17 14:04:47 -07:00
b484a4a959 Initial LighthouseCI Commit (#3906)
* Initial LighthouseCI Commit

Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2021-06-16 11:37:37 -07:00
64e7c62d98 [Style] check if there is an element to style before applying style (#3950)
* check if there is an element to style

* add mmgis (external plugin) type to style exclusion list

* revert b919cf9 to be fixed properly

* reduce code
2021-06-15 16:03:23 -07:00
6483fe2402 Prepare master for Sprint 1.7.4 (#3925) 2021-06-07 11:37:49 -07:00
a123889d6a Pre release for Sprint 1.7.3 (#3924)
* Revert "upgrade to webpack5 (#3871)" (#3907) (#3908)
* [Navigation Tree] Fix composition on closed folders and scrolling for items NOT in tree (#3920)
* Update package.json version and version documentation to include tags for npmjs

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
2021-06-07 11:25:58 -07:00
a40867d544 Revert "upgrade to webpack5 (#3871)" (#3907)
This reverts commit e1e0eeac56.
2021-06-02 07:04:40 -07:00
dbed9262c0 Tree revert refactor (#3886)
* Reverts object tree to expand/collapse tree model
* Retains expanded/collapsed state 
* Adds collapse all feature
2021-06-01 15:47:21 -07:00
43ac66233e [Issue] remove FollowTimerAction, followIndicator and tests (#3855)
Co-authored-by: Henry Hsu <henryhsu@Henrys-MacBook-Air.local>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-05-28 16:06:09 -07:00
04e85c176a Update templates (#3904) 2021-05-28 14:55:36 -07:00
8274c23129 Plots limit lines (#3882) 2021-05-28 14:51:29 -07:00
5fafde5f23 Updates to recognize issue template config (#3902)
* issue template config

* Update PR Template

Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-05-28 14:16:56 -07:00
80a6e7f719 Fix erroneously checked in dev code (#3897)
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2021-05-28 13:22:11 -07:00
2c13aeecce Make legend configuration properties reactive (#3889)
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2021-05-28 13:13:57 -07:00
ac015c3e45 Updates to the sprint cycle and end of sprint release process (#3878)
* Updates to the sprint cycle and end of sprint release process

* Adds reviewer checklist item for labelling an issue as a bug

* Update Cycle doc with test process changes.

* Update static.md references, add Issue templates

* Updated criticality definition

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: John Hill <jchill2.spam@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-05-28 12:24:14 -07:00
ae1a4bcc6a A change in the parent container's size for plots re-requests telemetry (#3890)
* Use resize obeserver to detect a change in the parent container's size for plots and re-request telemetry
2021-05-28 11:44:56 -07:00
e1e0eeac56 upgrade to webpack5 (#3871)
Upgrade to webpack 5
Changes dependencies to work with webpack 5 as well.
2021-05-27 15:16:03 -07:00
c90dfb2a1f Fix the browser back button in Open MCT (#3526)
Fixes Open MCT back button.

Co-authored-by: Joshi <simplyrender@gmail.com>
2021-05-26 17:00:36 -07:00
1dfa5e5b8c Prepare snapshot for sprint 1.7.3 (#3877)
Co-authored-by: John Hill <jchill2.spam@gmail.com>
2021-05-24 10:54:51 -07:00
99896b72ea Small typo (#3838)
Looks like there is a small typo: `this this object`.

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
2021-05-21 15:57:53 -07:00
979ba77c8e Normalize "OK" to uppercase in all dialogs; (#3850)
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
2021-05-21 15:38:29 -07:00
264 changed files with 8650 additions and 5029 deletions

View File

@ -1,36 +1,69 @@
version: 2
jobs:
build:
version: 2.1
executors:
linux:
docker:
- image: circleci/node:13-browsers
environment:
CHROME_BIN: "/usr/bin/google-chrome"
steps:
- checkout
- run:
name: Update npm
command: 'sudo npm install -g npm@latest'
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
- run:
name: Installing dependencies (npm install)
command: npm install
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
- node_modules
- run:
name: npm run test:coverage
command: npm run test:coverage
- run:
name: npm run lint
command: npm run lint
- store_artifacts:
path: dist
prefix: dist
workflows:
version: 2
- image: cimg/base:stable
orbs:
node: circleci/node@4.5.1
browser-tools: circleci/browser-tools@1.1.3
jobs:
test:
parameters:
node-version:
type: string
browser:
type: string
always-pass:
type: boolean
executor: linux
steps:
- checkout
- restore_cache:
key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}
- node/install:
node-version: << parameters.node-version >>
- node/install-packages:
override-ci-command: npm install
- when: # Just to save time until caching saves the browser bin
condition:
equal: [ "FirefoxESR", <<parameters.browser>> ]
steps:
- browser-tools/install-firefox:
version: "78.11.0esr" #https://archive.mozilla.org/pub/firefox/releases/
- when: # Just to save time until caching saves the browser bin
condition:
equal: [ "ChromeHeadless", <<parameters.browser>> ]
steps:
- browser-tools/install-chrome:
replace-existing: false
- save_cache:
key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}
paths:
- ~/.npm
- ~/.cache
- node_modules
- run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
- store_test_results:
path: dist/reports/tests/
- store_artifacts:
path: dist/reports/
workflows:
matrix-tests:
jobs:
- build
- test:
name: node10-chrome
node-version: lts/dubnium
browser: ChromeHeadless
always-pass: false
- test:
name: node12-firefoxESR
node-version: lts/erbium
browser: FirefoxESR
always-pass: true
- test:
name: node14-chrome
node-version: lts/fermium
browser: ChromeHeadless
always-pass: true

44
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,44 @@
---
name: Bug report
about: File a Bug !
title: ''
labels: type:bug
assignees: ''
---
<!--- Focus on user impact in the title. Use the Summary Field to -->
<!--- describe the problem technically. -->
#### Summary
<!--- A description of the issue encountered. When possible, a description -->
<!--- of the impact of the issue. What use case does it impede?-->
#### Expected vs Current Behavior
<!--- Tell us what should have happened -->
#### Impact Check List
<!--- Please select from the following options -->
- [ ] Data loss or misrepresented data?
- [ ] Regression? Did this used to work or has it always been broken?
- [ ] Is there a workaround available?
- [ ] Does this impact a critical component?
- [ ] Is this just a visual bug?
#### Steps to Reproduce
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
#### Environment
* Open MCT Version: <!--- date of build, version, or SHA -->
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yamcs? -->
* OS:
* Browser:
#### Additional Information
<!--- Include any screenshots, gifs, or logs which will expedite triage -->

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Discussions
url: https://github.com/nasa/openmct/discussions
about: Got a question?

View File

@ -0,0 +1,20 @@
---
name: Enhancement request
about: Suggest an enhancement or new improvement for this project
title: ''
labels: type:enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,23 @@
<!--- This is for filing enhancements or features. If you have a general -->
<!--- question, please visit https://github.com/nasa/openmct/discussions -->
---
name: Feature Request
about: Suggest an idea for this project
---
<!--
Thank you for suggesting an idea to make Open MCT better.
Please fill in as much of the template below as you're able.
-->
**Is your feature request related to a problem? Please describe.**
<!-- Please describe the problem you are trying to solve. -->
**Describe the solution you'd like**
<!--- Please describe the desired behavior. -->
**Describe alternatives you've considered**
<!--- Please describe alternative solutions or features you have considered. -->

12
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,12 @@
### All Submissions:
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
### Author Checklist
* [ ] Changes address original issue?
* [ ] Unit tests included and/or updated with changes?
* [ ] Command line build passes?
* [ ] Has this been smoke tested?
* [ ] Testing instructions included in associated issue?

20
.github/workflows/lighthouse.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: lighthouse
on:
workflow_dispatch:
inputs:
version:
description: 'Which branch do you want to test?' # Limited to branch for now
required: false
default: 'master'
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.version }}
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install && npm install -g @lhci/cli #Don't want to include this in our deps
- run: lhci autorun

3
.gitignore vendored
View File

@ -40,4 +40,7 @@ npm-debug.log
# karma reports
report.*.json
# Lighthouse reports
.lighthouseci
package-lock.json

16
API.md
View File

@ -423,7 +423,7 @@ attribute | type | flags | notes
###### Value Hints
Each telemetry value description has an object defining hints. Keys in this this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns.
Each telemetry value description has an object defining hints. Keys in this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns.
Known hints:
@ -595,9 +595,17 @@ section.
#### Limit Evaluators **draft**
Limit evaluators allow a telemetry integrator to define how limits should be
applied to telemetry from a given domain object. For an example of a limit
evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`.
Limit evaluators allow a telemetry integrator to define which limits exist for a
telemetry endpoint and how limits should be applied to telemetry from a given domain object.
A limit evaluator can implement the `evalute` method which is used to define how limits
should be applied to telemetry and the `getLimits` method which is used to specify
what the limit values are for different limit levels.
Limit levels can be mapped to one of 5 colors for visualization:
`purple`, `red`, `orange`, `yellow` and `cyan`.
For an example of a limit evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`.
### Telemetry Consumer APIs **draft**

View File

@ -10,7 +10,7 @@ accept changes from external contributors.
The short version:
1. Write your contribution.
1. Write your contribution or describe your idea in the form of an [GitHub issue](https://github.com/nasa/openmct/issues/new/choose) or [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions)
2. Make sure your contribution meets code, test, and commit message
standards as described below.
3. Submit a pull request from a topic branch back to `master`. Include a check
@ -18,6 +18,7 @@ The short version:
for review.)
4. Respond to any discussion. When the reviewer decides it's ready, they
will merge back `master` and fill out their own check list.
5. If you are a first-time contributor, please see [this discussion](https://github.com/nasa/openmct/discussions/3821) for further information.
## Contribution Process
@ -115,7 +116,7 @@ the pull request containing the reviewer checklist (from below) and complete
the merge back to the master branch.
Additionally:
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull requests __author__. If no issue exists, create one.
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull requests __author__. If no issue exists, [create one](https://github.com/nasa/openmct/issues/new/choose).
* Every __author__ must include testing instructions. These instructions should identify the areas of code affected, and some minimal test steps. If addressing a bug, reproduction steps should be included, if they were not included in the original issue. If reproduction steps were included on the original issue, and are sufficient, refer to them.
* A pull request that closes an issue should say so in the description. Including the text “Closes #1234” will cause the linked issue to be automatically closed when the pull request is merged. This is the responsibility of the pull requests __author__.
* When a pull request is merged, and the corresponding issue closed, the __reviewer__ must add the tag “unverified” to the original issue. This will indicate that although the issue is closed, it has not been tested yet.
@ -296,23 +297,12 @@ these standards.
Issues are tracked at https://github.com/nasa/openmct/issues.
Issues should include:
* A short description of the issue encountered.
* A longer-form description of the issue encountered. When possible, steps to
reproduce the issue.
* When possible, a description of the impact of the issue. What use case does
it impede?
* An assessment of the severity of the issue.
Issue severity is categorized as follows (in ascending order):
* _Trivial_: Minimal impact on the usefulness and functionality of the
software; a "nice-to-have."
* _(Unspecified)_: Major loss of functionality or impairment of use.
* _Critical_: Large-scale loss of functionality or impairment of use,
such that remaining utility becomes marginal.
* _Blocker_: Harmful or otherwise unacceptable behavior. Must fix.
* _Trivial_: Minimal impact on the usefulness and functionality of the software; a "nice-to-have." Visual impact without functional impact,
* _Medium_: Some impairment of use, but simple workarounds exist
* _Critical_: Significant loss of functionality or impairment of use. Display of telemetry data is not affected though.
* _Blocker_: Major functionality is impaired or lost, threatening mission success. Display of telemetry data is impaired or blocked by the bug, which could lead to loss of situational awareness.
## Check Lists
@ -322,16 +312,19 @@ checklist).
### Author Checklist
1. Changes address original issue?
2. Unit tests included and/or updated with changes?
3. Command line build passes?
4. Changes have been smoke-tested?
5. Testing instructions included?
[Within PR Template](.github/PULL_REQUEST_TEMPLATE.md)
### Reviewer Checklist
1. Changes appear to address issue?
2. Appropriate unit tests included?
3. Code style and in-line documentation are appropriate?
4. Commit messages meet standards?
5. Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)
* [ ] Changes appear to address issue?
* [ ] Appropriate unit tests included?
* [ ] Code style and in-line documentation are appropriate?
* [ ] Commit messages meet standards?
* [ ] Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)
* [ ] Has associated issue been labelled `bug`? (only applicable if this PR is for a bug fix)
* [ ] List of Acceptance Tests Performed.
Write out a small list of tests performed with just enough detail for another developer on the team
to execute.
i.e. ```When Clicking on Add button, new `object` appears in dropdown.```

View File

@ -1,9 +1,11 @@
# Open MCT [![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
# Open MCT [![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/nasa/openmct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/nasa/openmct/context:javascript)
Open MCT (Open Mission Control Technologies) is a next-generation mission control framework for visualization of data on desktop and mobile devices. It is developed at NASA's Ames Research Center, and is being used by NASA for data analysis of spacecraft missions, as well as planning and operation of experimental rover systems. As a generalizable and open source framework, Open MCT could be used as the basis for building applications for planning, operation, and analysis of any systems producing telemetry data.
Please visit our [Official Site](https://nasa.github.io/openmct/) and [Getting Started Guide](https://nasa.github.io/openmct/getting-started/)
Once you've created something amazing with Open MCT, showcase your work in our GitHub Discussions [Show and Tell](https://github.com/nasa/openmct/discussions/categories/show-and-tell) section. We love seeing unique and wonderful implementations of Open MCT!
## See Open MCT in Action
Try Open MCT now with our [live demo](https://openmct-demo.herokuapp.com/).
@ -44,7 +46,7 @@ The clearest examples for developing Open MCT plugins are in the
our documentation.
We want Open MCT to be as easy to use, install, run, and develop for as
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues/new/choose), [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
## Building Applications With Open MCT

View File

@ -73,11 +73,11 @@ acceptance testing (e.g. by resolving any blockers found); any
resources not needed for this effort should be used to begin work
for the subsequent sprint.
| Week | Mon | Tue | Wed | Thu | Fri |
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-----------:|
| __1__ | Sprint plan | Tag-up | | | |
| __2__ | | Tag-up | | | Code freeze |
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship |
| Week | Mon | Tue | Wed | Thu | Fri |
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-------------------------------------:|
| __1__ | Sprint plan | Tag-up | | | |
| __2__ | | Tag-up | | | Code freeze and sprint branch |
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship and merge sprint branch to master|
&ast; If necessary.
@ -105,14 +105,20 @@ emphasis on testing.
that team may begin work for that sprint during the
third week, since testing and blocker resolution is unlikely
to require all available resources.
* Testing success criteria identified per issue (where necessary). This could be in the form of acceptance tests on the issue or detailing performance tests, for example.
* __Tag-up.__ Check in and status update among development team.
May amend plan for sprint as-needed.
* __Code freeze.__ Any new work from this sprint
(features, bug fixes, enhancements) must be integrated by the
end of the second week of the sprint. After code freeze
(and until the end of the sprint) the only changes that should be
merged into the master branch should directly address issues
needed to pass acceptance testing.
end of the second week of the sprint. After code freeze, a sprint
branch will be created (and until the end of the sprint) the only
changes that should be merged into the sprint branch should
directly address issues needed to pass acceptance testing.
During this time, any other feature development will continue to
be merged into the master branch for the next sprint.
* __Sprint branch merge to master.__ After acceptance testing, the sprint branch
will be merged back to the master branch. Any code conflicts that
arise will be resolved by the team.
* [__Per-release Testing.__](testing/plan.md#per-release-testing)
Structured testing with predefined
success criteria. No release should ship without passing
@ -126,8 +132,8 @@ emphasis on testing.
* [__Testathon.__](testing/plan.md#user-testing)
Multi-user testing, involving as many users as
is feasible, plus development team. Open-ended; should verify
completed work from this sprint, test exploratorily for
regressions, et cetera.
completed work from this sprint using the sprint branch, test
exploratorily for regressions, et cetera.
* [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A
test to verify that the software remains
stable after running for longer durations. May include some
@ -143,7 +149,7 @@ emphasis on testing.
Subset of Pre-release Testing
which should be performed before shipping at the end of any
sprint. Time is allocated for a second round of
Pre-release Testing if the first round is not passed.
Pre-release Testing if the first round is not passed. Smoke tests collected from issues/PRs
* __Triage.__ Team reviews issues from acceptance testing and uses
success criteria to determine whether or not they should block
release, then formulates a plan to address these issues before

View File

@ -19,7 +19,7 @@ Testing for Open MCT includes:
Manual, non-rigorous testing of the software and/or specific features
of interest. Verifies that the software runs and that basic functionality
is present.
is present. The outcome of Smoke Testing should be a simplified list of Acceptance Tests which could be executed by another team member with sufficient context.
### Unit Testing
@ -49,7 +49,7 @@ User testing will focus on the following activities:
* General "trying to break things."
During user testing, users will
[report issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
[report issues](https://github.com/nasa/openmct/issues/new/choose)
as they are encountered.
Desired outcomes of user testing are:
@ -71,7 +71,7 @@ usage. After twenty-four hours, the software is evaluated for:
at the start of the test? Is it as responsive?
Any defects or unexpected behavior identified during testing should be
[reported as issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
[reported as issues](https://github.com/nasa/openmct/issues/new/choose)
and reviewed for severity.
## Test Performance

View File

@ -92,8 +92,8 @@ should update (or delegate the task of updating) Open MCT version
numbers by the following process:
1. Update version number in `package.json`
1. Create a new branch off the `master` branch.
2. Remove `-SNAPSHOT` suffix from the version in `package.json`.
1. Checkout branch created for the last sprint that has been successfully tested.
2. Remove a `-SNAPSHOT` suffix from the version in `package.json`.
3. Verify that resulting version number meets semantic versioning
requirements relative to previous stable version. Increment the
version number if necessary.
@ -131,7 +131,8 @@ numbers by the following process:
3. In `package.json` change package to be public (private: false)
4. Test the package before publishing by doing `npm publish --dry-run`
if necessary.
5. Publish the package to the npmjs registry (e.g. `npm publish --access public`)
5. Publish the package to the npmjs registry (e.g. `npm publish --access public`)
NOTE: Use the `--tag unstable` flag to the npm publishj if this is a prerelease.
6. Confirm the package has been published (e.g. `https://www.npmjs.com/package/openmct`)
5. Update snapshot status in `package.json`
1. Create a new branch off the `master` branch.

View File

@ -26,14 +26,26 @@ define([
) {
var RED = {
var PURPLE = {
sin: 2.2,
cos: 2.2
},
RED = {
sin: 0.9,
cos: 0.9
},
ORANGE = {
sin: 0.7,
cos: 0.7
},
YELLOW = {
sin: 0.5,
cos: 0.5
},
CYAN = {
sin: 0.45,
cos: 0.45
},
LIMITS = {
rh: {
cssClass: "is-limit--upr is-limit--red",
@ -93,5 +105,70 @@ define([
};
};
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
return {
limits: function () {
return Promise.resolve({
WATCH: {
low: {
color: "cyan",
sin: -CYAN.sin,
cos: -CYAN.cos
},
high: {
color: "cyan",
...CYAN
}
},
WARNING: {
low: {
color: "yellow",
sin: -YELLOW.sin,
cos: -YELLOW.cos
},
high: {
color: "yellow",
...YELLOW
}
},
DISTRESS: {
low: {
color: "orange",
sin: -ORANGE.sin,
cos: -ORANGE.cos
},
high: {
color: "orange",
...ORANGE
}
},
CRITICAL: {
low: {
color: "red",
sin: -RED.sin,
cos: -RED.cos
},
high: {
color: "red",
...RED
}
},
SEVERE: {
low: {
color: "purple",
sin: -PURPLE.sin,
cos: -PURPLE.cos
},
high: {
color: "purple",
...PURPLE
}
}
});
}
};
};
return SinewaveLimitProvider;
});

View File

@ -49,6 +49,10 @@ define([
];
const IMAGE_DELAY = 20000;
function getCompassValues(min, max) {
return min + Math.random() * (max - min);
}
function pointForTimestamp(timestamp, name) {
const url = IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length];
const urlItems = url.split('/');
@ -59,6 +63,9 @@ define([
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
url,
sunOrientation: getCompassValues(0, 360),
cameraPan: getCompassValues(0, 360),
heading: getCompassValues(0, 360),
imageDownloadName
};
}

View File

@ -1,4 +1,4 @@
import Vue from 'Vue';
import Vue from 'vue';
import HelloWorld from './HelloWorld.vue';
function SimpleVuePlugin() {

View File

@ -88,6 +88,7 @@
openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.PlanLayout());
openmct.install(openmct.plugins.Timeline());
openmct.install(openmct.plugins.Hyperlink());
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel"
@ -194,6 +195,7 @@
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
{indicator: true}
));
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
openmct.start();
</script>
</html>

View File

@ -23,9 +23,9 @@
/*global module,process*/
const devMode = process.env.NODE_ENV !== 'production';
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'FirefoxHeadless'];
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
const coverageEnabled = process.env.COVERAGE === 'true';
const reporters = ['progress', 'html'];
const reporters = ['progress', 'html', 'junit'];
if (coverageEnabled) {
reporters.push('coverage-istanbul');
@ -59,7 +59,8 @@ module.exports = (config) => {
browsers: browsers,
client: {
jasmine: {
random: false
random: false,
timeoutInterval: 30000
}
},
customLaunchers: {
@ -67,6 +68,10 @@ module.exports = (config) => {
base: 'Chrome',
flags: ['--remote-debugging-port=9222'],
debug: true
},
FirefoxESR: {
base: 'FirefoxHeadless',
name: 'FirefoxESR'
}
},
colors: true,
@ -78,11 +83,21 @@ module.exports = (config) => {
preserveDescribeNesting: true,
foldAll: false
},
junitReporter: {
outputDir: "dist/reports/tests",
outputFile: "test-results.xml",
useBrowserName: false
},
browserConsoleLogOptions: {
level: "error",
format: "%b %T: %m",
terminal: true
},
coverageIstanbulReporter: {
fixWebpackSourcePaths: true,
dir: process.env.CIRCLE_ARTIFACTS ?
process.env.CIRCLE_ARTIFACTS + '/coverage' :
"dist/reports/coverage",
dir: process.env.CIRCLE_ARTIFACTS
? process.env.CIRCLE_ARTIFACTS + '/coverage'
: "dist/reports/coverage",
reports: ['html', 'lcovonly', 'text-summary'],
thresholds: {
global: {

96
lighthouserc.yml Normal file
View File

@ -0,0 +1,96 @@
---
ci:
collect:
urls:
- http://localhost/
numberOfRuns: 5
settings:
onlyCategories:
- performance
- best-practices
upload:
target: temporary-public-storage
assert:
preset: lighthouse:recommended
assertions:
### Applicable assertions
bootup-time:
- warn
- minScore: 0.88 #Original value was calculated at 0.88
dom-size:
- error
- maxNumericValue: 200 #Original value was calculated at 188
first-contentful-paint:
- error
- minScore: 0.07 #Original value was calculated at 0.08
mainthread-work-breakdown:
- warn
- minScore: 0.8 #Original value was calculated at 0.8
unused-javascript:
- warn
- maxLength: 1
- error
- maxNumericValue: 2000 #Original value was calculated at 1855
unused-css-rules: warn
installable-manifest: warn
service-worker: warn
### Disabled seo, accessibility, and pwa assertions, below
categories:seo: 'off'
categories:accessibility: 'off'
categories:pwa: 'off'
accesskeys: 'off'
apple-touch-icon: 'off'
aria-allowed-attr: 'off'
aria-command-name: 'off'
aria-hidden-body: 'off'
aria-hidden-focus: 'off'
aria-input-field-name: 'off'
aria-meter-name: 'off'
aria-progressbar-name: 'off'
aria-required-attr: 'off'
aria-required-children: 'off'
aria-required-parent: 'off'
aria-roles: 'off'
aria-toggle-field-name: 'off'
aria-tooltip-name: 'off'
aria-treeitem-name: 'off'
aria-valid-attr: 'off'
aria-valid-attr-value: 'off'
button-name: 'off'
bypass: 'off'
canonical: 'off'
color-contrast: 'off'
content-width: 'off'
crawlable-anchors: 'off'
csp-xss: 'off'
font-display: 'off'
font-size: 'off'
maskable-icon: 'off'
heading-order: 'off'
hreflang: 'off'
html-has-lang: 'off'
html-lang-valid: 'off'
http-status-code: 'off'
image-alt: 'off'
input-image-alt: 'off'
is-crawlable: 'off'
label: 'off'
link-name: 'off'
link-text: 'off'
list: 'off'
listitem: 'off'
meta-description: 'off'
meta-refresh: 'off'
meta-viewport: 'off'
object-alt: 'off'
plugins: 'off'
robots-txt: 'off'
splash-screen: 'off'
tabindex: 'off'
tap-targets: 'off'
td-headers-attr: 'off'
th-has-data-cells: 'off'
themed-omnibox: 'off'
valid-lang: 'off'
video-caption: 'off'
viewport: 'off'

View File

@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "1.7.1-SNAPSHOT",
"version": "1.7.6-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {},
"devDependencies": {
@ -34,20 +34,21 @@
"git-rev-sync": "^1.4.0",
"glob": ">= 3.0.0",
"html-loader": "^0.5.5",
"html2canvas": "^1.0.0-alpha.12",
"html2canvas": "^1.0.0-rc.7",
"imports-loader": "^0.8.0",
"istanbul-instrumenter-loader": "^3.0.1",
"jasmine-core": "^3.1.0",
"jasmine-core": "^3.7.1",
"jsdoc": "^3.3.2",
"karma": "5.1.1",
"karma": "6.3.4",
"karma-chrome-launcher": "3.1.0",
"karma-firefox-launcher": "1.3.0",
"karma-firefox-launcher": "2.1.1",
"karma-cli": "2.0.0",
"karma-coverage": "2.0.3",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-junit-reporter": "2.0.1",
"karma-html-reporter": "0.2.7",
"karma-jasmine": "3.3.1",
"karma-sourcemap-loader": "0.3.7",
"karma-jasmine": "4.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-webpack": "4.0.2",
"location-bar": "^3.0.1",
"lodash": "^4.17.12",
@ -60,7 +61,7 @@
"moment-timezone": "0.5.28",
"node-bourbon": "^4.2.3",
"node-sass": "^4.14.1",
"painterro": "^1.0.35",
"painterro": "^1.2.56",
"printj": "^1.2.1",
"raw-loader": "^0.5.1",
"request": "^2.69.0",
@ -78,7 +79,8 @@
"zepto": "^1.2.0"
},
"scripts": {
"clean": "rm -rf ./dist",
"clean": "rm -rf ./dist /node_modules; rm package-lock.json",
"clean-test-lint": "npm run clean; npm install ; npm run test; npm run lint",
"start": "node app.js",
"lint": "eslint platform example src --ext .js,.vue openmct.js",
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
@ -88,6 +90,7 @@
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run",
"test:coverage:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
"verify": "concurrently 'npm:test' 'npm:lint'",
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
@ -99,6 +102,9 @@
"type": "git",
"url": "https://github.com/nasa/openmct.git"
},
"engines": {
"node": ">=10.10.2 <16.0.0"
},
"author": "",
"license": "Apache-2.0",
"private": true

View File

@ -24,7 +24,6 @@ define([
"./src/navigation/NavigationService",
"./src/navigation/NavigateAction",
"./src/navigation/OrphanNavigationHandler",
"./src/windowing/NewTabAction",
"./res/templates/browse.html",
"./res/templates/browse-object.html",
"./res/templates/browse/object-header.html",
@ -37,7 +36,6 @@ define([
NavigationService,
NavigateAction,
OrphanNavigationHandler,
NewTabAction,
browseTemplate,
browseObjectTemplate,
objectHeaderTemplate,
@ -128,23 +126,6 @@ define([
"depends": [
"navigationService"
]
},
{
"key": "window",
"name": "Open In New Tab",
"implementation": NewTabAction,
"description": "Open in a new browser tab",
"category": [
"view-control",
"contextual"
],
"depends": [
"urlService",
"$window"
],
"group": "windowing",
"priority": 10,
"cssClass": "icon-new-window"
}
],
"runs": [

View File

@ -1,75 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/windowing/NewTabAction"],
function (NewTabAction) {
describe("The new tab action", function () {
var actionSelected,
actionCurrent,
mockWindow,
mockContextCurrent,
mockContextSelected,
mockUrlService;
beforeEach(function () {
mockWindow = jasmine.createSpyObj("$window", ["open", "location"]);
// Context if the current object is selected
// For example, when the top right new tab
// button is clicked, the user is using the
// current domainObject
mockContextCurrent = jasmine.createSpyObj("context", ["domainObject"]);
// Context if the selected object is selected
// For example, when an object in the left
// tree is opened in a new tab using the
// context menu
mockContextSelected = jasmine.createSpyObj("context", ["selectedObject",
"domainObject"]);
// Mocks the urlService used to make the new tab's url from a
// domainObject and mode
mockUrlService = jasmine.createSpyObj("urlService", ["urlForNewTab"]);
// Action done using the current context or mockContextCurrent
actionCurrent = new NewTabAction(mockUrlService, mockWindow,
mockContextCurrent);
// Action done using the selected context or mockContextSelected
actionSelected = new NewTabAction(mockUrlService, mockWindow,
mockContextSelected);
});
it("new tab with current url is opened", function () {
actionCurrent.perform();
});
it("new tab with a selected url is opened", function () {
actionSelected.perform();
});
});
}
);

View File

@ -86,7 +86,7 @@ define(
})
.join('/');
window.location.href = url;
openmct.router.navigate(url);
if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
openmct.editor.edit();

View File

@ -21,36 +21,24 @@
*****************************************************************************/
define([
"moment-timezone",
"./src/indicators/ClockIndicator",
"./src/indicators/FollowIndicator",
"./src/services/TickerService",
"./src/services/TimerService",
"./src/controllers/ClockController",
"./src/controllers/TimerController",
"./src/controllers/RefreshingController",
"./src/actions/FollowTimerAction",
"./src/actions/StartTimerAction",
"./src/actions/RestartTimerAction",
"./src/actions/StopTimerAction",
"./src/actions/PauseTimerAction",
"./res/templates/clock.html",
"./res/templates/timer.html"
], function (
MomentTimezone,
ClockIndicator,
FollowIndicator,
TickerService,
TimerService,
ClockController,
TimerController,
RefreshingController,
FollowTimerAction,
StartTimerAction,
RestartTimerAction,
StopTimerAction,
PauseTimerAction,
clockTemplate,
timerTemplate
) {
return {
@ -77,16 +65,6 @@ define([
"value": "YYYY/MM/DD HH:mm:ss"
}
],
"indicators": [
{
"implementation": ClockIndicator,
"depends": [
"tickerService",
"CLOCK_INDICATOR_FORMAT"
],
"priority": "preferred"
}
],
"services": [
{
"key": "tickerService",
@ -103,14 +81,6 @@ define([
}
],
"controllers": [
{
"key": "ClockController",
"implementation": ClockController,
"depends": [
"$scope",
"tickerService"
]
},
{
"key": "TimerController",
"implementation": TimerController,
@ -130,12 +100,6 @@ define([
}
],
"views": [
{
"key": "clock",
"type": "clock",
"editable": false,
"template": clockTemplate
},
{
"key": "timer",
"type": "timer",
@ -144,15 +108,6 @@ define([
}
],
"actions": [
{
"key": "timer.follow",
"implementation": FollowTimerAction,
"depends": ["timerService"],
"category": "contextual",
"name": "Follow Timer",
"cssClass": "icon-clock",
"priority": "optional"
},
{
"key": "timer.start",
"implementation": StartTimerAction,
@ -194,75 +149,11 @@ define([
],
"category": "contextual",
"name": "Stop",
"cssClass": "icon-box",
"cssClass": "icon-box-round-corners",
"priority": "preferred"
}
],
"types": [
{
"key": "clock",
"name": "Clock",
"cssClass": "icon-clock",
"description": "A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.",
"priority": 101,
"features": [
"creation"
],
"properties": [
{
"key": "clockFormat",
"name": "Display Format",
"control": "composite",
"items": [
{
"control": "select",
"options": [
{
"value": "YYYY/MM/DD hh:mm:ss",
"name": "YYYY/MM/DD hh:mm:ss"
},
{
"value": "YYYY/DDD hh:mm:ss",
"name": "YYYY/DDD hh:mm:ss"
},
{
"value": "hh:mm:ss",
"name": "hh:mm:ss"
}
],
"cssClass": "l-inline"
},
{
"control": "select",
"options": [
{
"value": "clock12",
"name": "12hr"
},
{
"value": "clock24",
"name": "24hr"
}
],
"cssClass": "l-inline"
}
]
},
{
"key": "timezone",
"name": "Timezone",
"control": "autocomplete",
"options": MomentTimezone.tz.names()
}
],
"model": {
"clockFormat": [
"YYYY/MM/DD hh:mm:ss",
"clock12"
],
"timezone": "UTC"
}
},
{
"key": "timer",
"name": "Timer",
@ -299,10 +190,7 @@ define([
}
}
],
"runs": [{
"implementation": FollowIndicator,
"depends": ["openmct", "timerService"]
}],
"runs": [],
"licenses": [
{
"name": "moment-duration-format",

View File

@ -1,32 +0,0 @@
<!--
Open MCT, Copyright (c) 2009-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="c-clock l-time-display u-style-receiver js-style-receiver" ng-controller="ClockController as clock">
<div class="c-clock__timezone">
{{clock.zone()}}
</div>
<div class="c-clock__value">
{{clock.text()}}
</div>
<div class="c-clock__ampm">
{{clock.ampm()}}
</div>
</div>

View File

@ -1,110 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'moment',
'moment-timezone'
],
function (
moment,
momentTimezone
) {
/**
* Controller for views of a Clock domain object.
*
* @constructor
* @memberof platform/features/clock
* @param {angular.Scope} $scope the Angular scope
* @param {platform/features/clock.TickerService} tickerService
* a service used to align behavior with clock ticks
*/
function ClockController($scope, tickerService) {
var lastTimestamp,
unlisten,
timeFormat,
zoneName,
self = this;
function update() {
var m = zoneName
? moment.utc(lastTimestamp).tz(zoneName) : moment.utc(lastTimestamp);
self.zoneAbbr = m.zoneAbbr();
self.textValue = timeFormat && m.format(timeFormat);
self.ampmValue = m.format("A"); // Just the AM or PM part
}
function tick(timestamp) {
lastTimestamp = timestamp;
update();
}
function updateModel(model) {
var baseFormat;
if (model !== undefined) {
baseFormat = model.clockFormat[0];
self.use24 = model.clockFormat[1] === 'clock24';
timeFormat = self.use24
? baseFormat.replace('hh', "HH") : baseFormat;
// If wrong timezone is provided, the UTC will be used
zoneName = momentTimezone.tz.names().includes(model.timezone)
? model.timezone : "UTC";
update();
}
}
// Pull in the model (clockFormat and timezone) from the domain object model
$scope.$watch('model', updateModel);
// Listen for clock ticks ... and stop listening on destroy
unlisten = tickerService.listen(tick);
$scope.$on('$destroy', unlisten);
}
/**
* Get the clock's time zone, as displayable text.
* @returns {string}
*/
ClockController.prototype.zone = function () {
return this.zoneAbbr;
};
/**
* Get the current time, as displayable text.
* @returns {string}
*/
ClockController.prototype.text = function () {
return this.textValue;
};
/**
* Get the text to display to qualify a time as AM or PM.
* @returns {string}
*/
ClockController.prototype.ampm = function () {
return this.use24 ? '' : this.ampmValue;
};
return ClockController;
}
);

View File

@ -1,65 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['moment'],
function (moment) {
/**
* Indicator that displays the current UTC time in the status area.
* @implements {Indicator}
* @memberof platform/features/clock
* @param {platform/features/clock.TickerService} tickerService
* a service used to align behavior with clock ticks
* @param {string} indicatorFormat format string for timestamps
* shown in this indicator
*/
function ClockIndicator(tickerService, indicatorFormat) {
var self = this;
this.text = "";
tickerService.listen(function (timestamp) {
self.text = moment.utc(timestamp)
.format(indicatorFormat) + " UTC";
});
}
ClockIndicator.prototype.getGlyphClass = function () {
return "";
};
ClockIndicator.prototype.getCssClass = function () {
return "t-indicator-clock icon-clock no-minify c-indicator--not-clickable";
};
ClockIndicator.prototype.getText = function () {
return this.text;
};
ClockIndicator.prototype.getDescription = function () {
return "";
};
return ClockIndicator;
}
);

View File

@ -1,89 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"../../src/actions/FollowTimerAction"
], function (FollowTimerAction) {
var TIMER_SERVICE_METHODS =
['setTimer', 'getTimer', 'clearTimer', 'on', 'off'];
describe("The Follow Timer action", function () {
var testContext;
var testModel;
var testAdaptedObject;
beforeEach(function () {
testModel = {};
testContext = {
domainObject: jasmine.createSpyObj('domainObject', [
'getModel',
'useCapability'
])
};
testAdaptedObject = { foo: 'bar' };
testContext.domainObject.getModel.and.returnValue(testModel);
testContext.domainObject.useCapability.and.callFake(function (c) {
return c === 'adapter' && testAdaptedObject;
});
});
it("is applicable to timers", function () {
testModel.type = "timer";
expect(FollowTimerAction.appliesTo(testContext)).toBe(true);
});
it("is inapplicable to non-timers", function () {
testModel.type = "folder";
expect(FollowTimerAction.appliesTo(testContext)).toBe(false);
});
describe("when instantiated", function () {
var mockTimerService;
var action;
beforeEach(function () {
mockTimerService = jasmine.createSpyObj(
'timerService',
TIMER_SERVICE_METHODS
);
action = new FollowTimerAction(mockTimerService, testContext);
});
it("does not interact with the timer service", function () {
TIMER_SERVICE_METHODS.forEach(function (method) {
expect(mockTimerService[method]).not.toHaveBeenCalled();
});
});
describe("and performed", function () {
beforeEach(function () {
action.perform();
});
it("sets the active timer", function () {
expect(mockTimerService.setTimer)
.toHaveBeenCalledWith(testAdaptedObject);
});
});
});
});
});

View File

@ -1,107 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/controllers/ClockController"],
function (ClockController) {
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000;
describe("A clock view's controller", function () {
var mockScope,
mockTicker,
mockUnticker,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.and.returnValue(mockUnticker);
controller = new ClockController(mockScope, mockTicker);
});
it("watches for model (clockFormat and timezone) from the domain object model", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"model",
jasmine.any(Function)
);
});
it("subscribes to clock ticks", function () {
expect(mockTicker.listen)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("unsubscribes to ticks when destroyed", function () {
// Make sure $destroy is being listened for...
expect(mockScope.$on.calls.mostRecent().args[0]).toEqual('$destroy');
expect(mockUnticker).not.toHaveBeenCalled();
// ...and makes sure that its listener unsubscribes from ticker
mockScope.$on.calls.mostRecent().args[1]();
expect(mockUnticker).toHaveBeenCalled();
});
it("formats using the format string from the model", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
mockScope.$watch.calls.mostRecent().args[1]({
"clockFormat": [
"YYYY-DDD hh:mm:ss",
"clock24"
],
"timezone": "Canada/Eastern"
});
expect(controller.zone()).toEqual("EDT");
expect(controller.text()).toEqual("2015-154 13:56:14");
expect(controller.ampm()).toEqual("");
});
it("formats 12-hour time", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
mockScope.$watch.calls.mostRecent().args[1]({
"clockFormat": [
"YYYY-DDD hh:mm:ss",
"clock12"
],
"timezone": ""
});
expect(controller.zone()).toEqual("UTC");
expect(controller.text()).toEqual("2015-154 05:56:14");
expect(controller.ampm()).toEqual("PM");
});
it("does not throw exceptions when model is undefined", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
expect(function () {
mockScope.$watch.calls.mostRecent().args[1](undefined);
}).not.toThrow();
});
});
}
);

View File

@ -101,7 +101,7 @@ define(
name: "Pause"
});
mockStop.getMetadata.and.returnValue({
cssClass: "icon-box",
cssClass: "icon-box-round-corners",
name: "Stop"
});
mockScope.domainObject = mockDomainObject;

View File

@ -1,58 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/indicators/ClockIndicator"],
function (ClockIndicator) {
// Wed, 03 Jun 2015 17:56:14 GMT
var TEST_TIMESTAMP = 1433354174000,
TEST_FORMAT = "YYYY-DDD HH:mm:ss";
describe("The clock indicator", function () {
var mockTicker,
mockUnticker,
indicator;
beforeEach(function () {
mockTicker = jasmine.createSpyObj('ticker', ['listen']);
mockUnticker = jasmine.createSpy('unticker');
mockTicker.listen.and.returnValue(mockUnticker);
indicator = new ClockIndicator(mockTicker, TEST_FORMAT);
});
it("displays the current time", function () {
mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
expect(indicator.getText()).toEqual("2015-154 17:56:14 UTC");
});
it("implements the Indicator interface", function () {
expect(indicator.getCssClass()).toEqual(jasmine.any(String));
expect(indicator.getText()).toEqual(jasmine.any(String));
expect(indicator.getDescription()).toEqual(jasmine.any(String));
});
});
}
);

View File

@ -1,96 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"../../src/indicators/FollowIndicator",
"../../src/services/TimerService",
"../../../../../src/MCT",
'zepto'
], function (
FollowIndicator,
TimerService,
MCT,
$
) {
xdescribe("The timer-following indicator", function () {
var timerService;
var openmct;
beforeEach(function () {
openmct = new MCT();
timerService = new TimerService(openmct);
spyOn(openmct.indicators, "add");
});
it("adds an indicator when installed", function () {
FollowIndicator(openmct, timerService);
expect(openmct.indicators.add).toHaveBeenCalled();
});
it("indicates that no timer is being followed", function () {
FollowIndicator(openmct, timerService);
var simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
var element = simpleIndicator.element;
var text = $('.indicator-text', element).text().trim();
expect(text).toEqual('No timer being followed');
});
describe("when a timer is set", function () {
var testObject;
var simpleIndicator;
beforeEach(function () {
testObject = {
identifier: {
namespace: 'namespace',
key: 'key'
},
name: "some timer!"
};
timerService.setTimer(testObject);
FollowIndicator(openmct, timerService);
simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
});
it("displays the timer's name", function () {
var element = simpleIndicator.element;
var text = $('.indicator-text', element).text().trim();
expect(text).toEqual('Following timer ' + testObject.name);
});
it("displays the timer's name when it changes", function () {
var secondTimer = {
identifier: {
namespace: 'namespace',
key: 'key2'
},
name: "Some other timer"
};
var element = simpleIndicator.element;
timerService.setTimer(secondTimer);
var text = $('.indicator-text', element).text().trim();
expect(text).toEqual('Following timer ' + secondTimer.name);
});
});
});
});

View File

@ -1,120 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./src/HyperlinkController',
'./res/templates/hyperlink.html'
], function (
HyperlinkController,
hyperlinkTemplate
) {
return {
name: "platform/features/hyperlink",
definition: {
"name": "Hyperlink",
"description": "Insert a hyperlink to reference a link",
"extensions": {
"types": [
{
"key": "hyperlink",
"name": "Hyperlink",
"cssClass": "icon-chain-links",
"description": "A hyperlink to redirect to a different link",
"features": ["creation"],
"properties": [
{
"key": "url",
"name": "URL",
"control": "textfield",
"required": true,
"cssClass": "l-input-lg"
},
{
"key": "displayText",
"name": "Text to Display",
"control": "textfield",
"required": true,
"cssClass": "l-input-lg"
},
{
"key": "displayFormat",
"name": "Display Format",
"control": "select",
"options": [
{
"name": "Link",
"value": "link"
},
{
"value": "button",
"name": "Button"
}
],
"cssClass": "l-inline"
},
{
"key": "openNewTab",
"name": "Tab to Open Hyperlink",
"control": "select",
"options": [
{
"name": "Open in this tab",
"value": "thisTab"
},
{
"value": "newTab",
"name": "Open in a new tab"
}
],
"cssClass": "l-inline"
}
],
"model": {
"displayFormat": "link",
"openNewTab": "thisTab",
"removeTitle": true
}
}
],
"views": [
{
"key": "hyperlink",
"type": "hyperlink",
"name": "Hyperlink Display",
"template": hyperlinkTemplate,
"editable": false
}
],
"controllers": [
{
"key": "HyperlinkController",
"implementation": HyperlinkController,
"depends": ["$scope"]
}
]
}
}
};
});

View File

@ -1,61 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* This bundle adds the Hyperlink object type, which can be used to add hyperlinks as a domain Object type
and into display Layouts as either a button or link that can be chosen to open in either the same tab or
create a new tab to open the link in
* @namespace platform/features/hyperlink
*/
define(
[],
function () {
function HyperlinkController($scope) {
this.$scope = $scope;
}
/**Function to analyze the location in which to open the hyperlink
@returns true if the hyperlink is chosen to open in a different tab, false if the same tab
**/
HyperlinkController.prototype.openNewTab = function () {
if (this.$scope.domainObject.getModel().openNewTab === "thisTab") {
return false;
} else {
return true;
}
};
/**Function to specify the format in which the hyperlink should be created
@returns true if the hyperlink is chosen to be created as a button, false if a link
**/
HyperlinkController.prototype.isButton = function () {
if (this.$scope.domainObject.getModel().displayFormat === "link") {
return false;
}
return true;
};
return HyperlinkController;
}
);

View File

@ -1,89 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../src/HyperlinkController"],
function (HyperlinkController) {
describe("The controller for hyperlinks", function () {
var domainObject,
controller,
scope;
beforeEach(function () {
scope = jasmine.createSpyObj(
"$scope",
["domainObject"]
);
domainObject = jasmine.createSpyObj(
"domainObject",
["getModel"]
);
scope.domainObject = domainObject;
controller = new HyperlinkController(scope);
});
it("knows when it should open a new tab", function () {
scope.domainObject.getModel.and.returnValue({
"displayFormat": "link",
"openNewTab": "newTab",
"showTitle": false
}
);
controller = new HyperlinkController(scope);
expect(controller.openNewTab())
.toBe(true);
});
it("knows when it is a button", function () {
scope.domainObject.getModel.and.returnValue({
"displayFormat": "button",
"openNewTab": "thisTab",
"showTitle": false
}
);
controller = new HyperlinkController(scope);
expect(controller.isButton())
.toEqual(true);
});
it("knows when it should open in the same tab", function () {
scope.domainObject.getModel.and.returnValue({
"displayFormat": "link",
"openNewTab": "thisTab",
"showTitle": false
}
);
controller = new HyperlinkController(scope);
expect(controller.openNewTab())
.toBe(false);
});
it("knows when it is a link", function () {
scope.domainObject.getModel.and.returnValue({
"displayFormat": "link",
"openNewTab": "thisTab",
"showTitle": false
}
);
controller = new HyperlinkController(scope);
expect(controller.openNewTab())
.toBe(false);
});
});
}
);

View File

@ -1,70 +0,0 @@
This bundle provides the Timeline domain object type, as well
as other associated domain object types and relevant views.
# Implementation notes
## Model Properties
The properties below record properties relevant to using and
understanding timelines based on their JSON representation.
Additional common properties, such as `modified`
or `persisted` timestamps, may also be present.
### Timeline Model
A timeline's model looks like:
```
{
"type": "timeline",
"start": {
"timestamp": <number> (milliseconds since epoch),
"epoch": <string> (currently, always "SET")
},
"capacity": <number> (optional; battery capacity in watt-hours)
"composition": <string[]> (array of identifiers for contained objects)
}
```
The identifiers in a timeline's `composition` field should refer to
other Timeline objects, or to Activity objects.
### Activity Model
An activity's model looks like:
```
{
"type": "activity",
"start": {
"timestamp": <number> (milliseconds since epoch),
"epoch": <string> (currently, always "SET")
},
"duration": {
"timestamp": <number> (duration of this activity, in milliseconds)
"epoch": "SET" (this is ignored)
},
"relationships": {
"modes": <string[]> (array of applicable Activity Mode ids)
},
"link": <string> (optional; URL linking to associated external resource)
"composition": <string[]> (array of identifiers for contained objects)
}
```
The identifiers in a timeline's `composition` field should only refer to
other Activity objects.
### Activity Mode Model
An activity mode's model looks like:
```
{
"type": "mode",
"resources": {
"comms": <number> (communications utilization, in Kbps)
"power": <number> (power utilization, in watts)
}
}
```

View File

@ -1,10 +0,0 @@
<div>
Timeline, Activity and Activity Mode objects have been deprecated and will no longer be supported.
</div>
<div>
Please open an issue in the
<a href="https://github.com/nasa/openmct/issues" target="_blank">
Open MCT Issue tracker
</a>
if you have any questions about the timeline plugin.
</div>

View File

@ -122,6 +122,7 @@ define([
}
};
this.destroy = this.destroy.bind(this);
/**
* Tracks current selection state of the application.
* @private
@ -252,7 +253,7 @@ define([
this.status = new api.StatusAPI(this);
this.router = new ApplicationRouter();
this.router = new ApplicationRouter(this);
this.branding = BrandingAPI.default;
@ -262,7 +263,7 @@ define([
// Plugins that are installed by default
this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable());
this.install(this.plugins.TelemetryTable.default());
this.install(PreviewPlugin.default());
this.install(LegacyIndicatorsPlugin());
this.install(LicensesPlugin.default());
@ -274,6 +275,7 @@ define([
this.install(ImageryPlugin.default());
this.install(this.plugins.FlexibleLayout());
this.install(this.plugins.GoToOriginalAction());
this.install(this.plugins.OpenInNewTabAction());
this.install(this.plugins.ImportExport());
this.install(this.plugins.WebPage());
this.install(this.plugins.Condition());
@ -282,6 +284,7 @@ define([
this.install(this.plugins.NotificationIndicator());
this.install(this.plugins.NewFolderAction());
this.install(this.plugins.ViewDatumAction());
this.install(this.plugins.ViewLargeAction());
this.install(this.plugins.ObjectInterceptors());
this.install(this.plugins.NonEditableFolder());
}
@ -433,6 +436,8 @@ define([
Browse(this);
}
window.addEventListener('beforeunload', this.destroy);
this.router.start();
this.emit('start');
}.bind(this));
@ -456,17 +461,11 @@ define([
};
MCT.prototype.destroy = function () {
window.removeEventListener('beforeunload', this.destroy);
this.emit('destroy');
this.router.destroy();
};
/**
* Expose this project's Vue so plugins can make use of it.
*
* @memberof module:openmct.MCT#
*/
MCT.prototype.Vue = Vue;
MCT.prototype.plugins = plugins;
return MCT;

View File

@ -36,8 +36,7 @@ define([
'./views/installLegacyViews',
'./policies/LegacyCompositionPolicyAdapter',
'./actions/LegacyActionAdapter',
'./services/LegacyPersistenceAdapter',
'./services/ExportImageService'
'./services/LegacyPersistenceAdapter'
], function (
ActionDialogDecorator,
AdapterCapability,
@ -54,8 +53,7 @@ define([
installLegacyViews,
legacyCompositionPolicyAdapter,
LegacyActionAdapter,
LegacyPersistenceAdapter,
ExportImageService
LegacyPersistenceAdapter
) {
return {
name: 'src/adapter',
@ -84,13 +82,6 @@ define([
"identifierService",
"cacheService"
]
},
{
"key": "exportImageService",
"implementation": ExportImageService,
"depends": [
"dialogService"
]
}
],
components: [

View File

@ -161,6 +161,23 @@ define([
evaluate: function (datum, property) {
return limitEvaluator.evaluate(datum, property && property.key);
}
};
};
LegacyTelemetryProvider.prototype.getLimits = function (domainObject) {
const oldObject = this.instantiate(
utils.toOldFormat(domainObject),
utils.makeKeyString(domainObject.identifier)
);
const limitEvaluator = oldObject.getCapability("limit");
return {
limits: () => {
return limitEvaluator.limits.then !== undefined
? limitEvaluator.limits()
: Promise.resolve(limitEvaluator.limits());
}
};
};

View File

@ -1,186 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* Module defining ExportImageService. Created by hudsonfoo on 09/02/16
*/
define(
[
"html2canvas",
"saveAs"
],
function (
html2canvas,
{ saveAs }
) {
/**
* The export image service will export any HTML node to
* JPG, or PNG.
* @param {object} dialogService
* @constructor
*/
function ExportImageService(dialogService) {
this.dialogService = dialogService;
this.exportCount = 0;
}
/**
* Converts an HTML element into a PNG or JPG Blob.
* @private
* @param {node} element that will be converted to an image
* @param {string} type of image to convert the element to.
* @returns {promise}
*/
ExportImageService.prototype.renderElement = function (element, imageType, className) {
const dialogService = this.dialogService;
const dialog = dialogService.showBlockingMessage({
title: "Capturing...",
hint: "Capturing an image",
unknownProgress: true,
severity: "info",
delay: true
});
let mimeType = "image/png";
if (imageType === "jpg") {
mimeType = "image/jpeg";
}
let exportId = undefined;
let oldId = undefined;
if (className) {
exportId = 'export-element-' + this.exportCount;
this.exportCount++;
oldId = element.id;
element.id = exportId;
}
return html2canvas(element, {
onclone: function (document) {
if (className) {
const clonedElement = document.getElementById(exportId);
clonedElement.classList.add(className);
}
element.id = oldId;
},
removeContainer: true // Set to false to debug what html2canvas renders
}).then(function (canvas) {
dialog.dismiss();
return new Promise(function (resolve, reject) {
return canvas.toBlob(resolve, mimeType);
});
}, function (error) {
console.log('error capturing image', error);
dialog.dismiss();
const errorDialog = dialogService.showBlockingMessage({
title: "Error capturing image",
severity: "error",
hint: "Image was not captured successfully!",
options: [{
label: "OK",
callback: function () {
errorDialog.dismiss();
}
}]
});
});
};
/**
* Takes a screenshot of a DOM node and exports to JPG.
* @param {node} element to be exported
* @param {string} filename the exported image
* @param {string} className to be added to element before capturing (optional)
* @returns {promise}
*/
ExportImageService.prototype.exportJPG = function (element, filename, className) {
const processedFilename = replaceDotsWithUnderscores(filename);
return this.renderElement(element, "jpg", className).then(function (img) {
saveAs(img, processedFilename);
});
};
/**
* Takes a screenshot of a DOM node and exports to PNG.
* @param {node} element to be exported
* @param {string} filename the exported image
* @param {string} className to be added to element before capturing (optional)
* @returns {promise}
*/
ExportImageService.prototype.exportPNG = function (element, filename, className) {
const processedFilename = replaceDotsWithUnderscores(filename);
return this.renderElement(element, "png", className).then(function (img) {
saveAs(img, processedFilename);
});
};
/**
* Takes a screenshot of a DOM node in PNG format.
* @param {node} element to be exported
* @param {string} filename the exported image
* @returns {promise}
*/
ExportImageService.prototype.exportPNGtoSRC = function (element, className) {
return this.renderElement(element, "png", className);
};
function replaceDotsWithUnderscores(filename) {
const regex = /\./gi;
return filename.replace(regex, '_');
}
/**
* canvas.toBlob() not supported in IE < 10, Opera, and Safari. This polyfill
* implements the method in browsers that would not otherwise support it.
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
*/
function polyfillToBlob() {
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", {
value: function (callback, mimeType, quality) {
const binStr = atob(this.toDataURL(mimeType, quality).split(',')[1]);
const len = binStr.length;
const arr = new Uint8Array(len);
for (let i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
callback(new Blob([arr], {type: mimeType || "image/png"}));
}
});
}
}
polyfillToBlob();
return ExportImageService;
}
);

View File

@ -46,8 +46,6 @@ class ActionCollection extends EventEmitter {
this._observeObjectPath();
this.openmct.editor.on('isEditing', this._updateActions);
}
this._initializeActions();
}
disable(actionKeys) {
@ -156,19 +154,10 @@ class ActionCollection extends EventEmitter {
});
}
_initializeActions() {
Object.keys(this.applicableActions).forEach(key => {
this.applicableActions[key].callBack = () => {
return this.applicableActions[key].invoke(this.objectPath, this.view);
};
});
}
_updateActions() {
let newApplicableActions = this.openmct.actions._applicableActions(this.objectPath, this.view);
this.applicableActions = this._mergeOldAndNewActions(this.applicableActions, newApplicableActions);
this._initializeActions();
this._update();
}

View File

@ -119,7 +119,8 @@ describe('The ActionCollection', () => {
afterEach(() => {
actionCollection.destroy();
resetApplicationState(openmct);
return resetApplicationState(openmct);
});
describe("disable method invoked with action keys", () => {

View File

@ -34,7 +34,7 @@ class ActionsAPI extends EventEmitter {
this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json'];
this.register = this.register.bind(this);
this.get = this.get.bind(this);
this.getActionsCollection = this.getActionsCollection.bind(this);
this._applicableActions = this._applicableActions.bind(this);
this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this);
}
@ -43,12 +43,14 @@ class ActionsAPI extends EventEmitter {
this._allActions[actionDefinition.key] = actionDefinition;
}
get(objectPath, view) {
if (view) {
getAction(key) {
return this._allActions[key];
}
getActionsCollection(objectPath, view) {
if (view) {
return this._getCachedActionCollection(objectPath, view) || this._newActionCollection(objectPath, view, true);
} else {
return this._newActionCollection(objectPath, view, true);
}
}
@ -57,15 +59,6 @@ class ActionsAPI extends EventEmitter {
this._groupOrder = groupArray;
}
_get(objectPath, view) {
let actionCollection = this._newActionCollection(objectPath, view);
this._actionCollections.set(view, actionCollection);
actionCollection.on('destroy', this._updateCachedActionCollections);
return actionCollection;
}
_getCachedActionCollection(objectPath, view) {
let cachedActionCollection = this._actionCollections.get(view);
@ -75,7 +68,17 @@ class ActionsAPI extends EventEmitter {
_newActionCollection(objectPath, view, skipEnvironmentObservers) {
let applicableActions = this._applicableActions(objectPath, view);
return new ActionCollection(applicableActions, objectPath, view, this._openmct, skipEnvironmentObservers);
const actionCollection = new ActionCollection(applicableActions, objectPath, view, this._openmct, skipEnvironmentObservers);
if (view) {
this._cacheActionCollection(view, actionCollection);
}
return actionCollection;
}
_cacheActionCollection(view, actionCollection) {
this._actionCollections.set(view, actionCollection);
actionCollection.on('destroy', this._updateCachedActionCollections);
}
_updateCachedActionCollections(key) {

View File

@ -99,14 +99,14 @@ describe('The Actions API', () => {
});
afterEach(() => {
resetApplicationState(openmct);
return resetApplicationState(openmct);
});
describe("register method", () => {
it("adds action to ActionsAPI", () => {
actionsAPI.register(mockAction);
let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
let action = actionCollection.getActionsObject()[mockAction.key];
expect(action.key).toEqual(mockAction.key);
@ -121,21 +121,21 @@ describe('The Actions API', () => {
});
it("returns an ActionCollection when invoked with an objectPath only", () => {
let actionCollection = actionsAPI.get(mockObjectPath);
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath);
let instanceOfActionCollection = actionCollection instanceof ActionCollection;
expect(instanceOfActionCollection).toBeTrue();
});
it("returns an ActionCollection when invoked with an objectPath and view", () => {
let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
let instanceOfActionCollection = actionCollection instanceof ActionCollection;
expect(instanceOfActionCollection).toBeTrue();
});
it("returns relevant actions when invoked with objectPath only", () => {
let actionCollection = actionsAPI.get(mockObjectPath);
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath);
let action = actionCollection.getActionsObject()[mockObjectPathAction.key];
expect(action.key).toEqual(mockObjectPathAction.key);
@ -143,7 +143,7 @@ describe('The Actions API', () => {
});
it("returns relevant actions when invoked with objectPath and view", () => {
let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
let action = actionCollection.getActionsObject()[mockAction.key];
expect(action.key).toEqual(mockAction.key);

View File

@ -215,12 +215,12 @@ define([
* @memberof {module:openmct.CompositionCollection#}
* @name load
*/
CompositionCollection.prototype.load = function () {
CompositionCollection.prototype.load = function (abortSignal) {
this.cleanUpMutables();
return this.provider.load(this.domainObject)
.then(function (children) {
return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
return Promise.all(children.map((c) => this.publicAPI.objects.get(c, abortSignal)));
}.bind(this))
.then(function (childObjects) {
childObjects.forEach(c => this.add(c, true));

View File

@ -37,7 +37,7 @@ import Menu, { MENU_PLACEMENT } from './menu.js';
* @property {Boolean} isDisabled adds disable class if true
* @property {String} name Menu item text
* @property {String} description Menu item description
* @property {Function} callBack callback function: invoked when item is clicked
* @property {Function} onItemClicked callback function: invoked when item is clicked
*/
/**
@ -66,12 +66,27 @@ class MenuAPI {
* @param {Array.<Action>|Array.<Array.<Action>>} actions collection of actions{@link Action} or collection of groups of actions {@link Action}
* @param {MenuOptions} [menuOptions] [Optional] The {@link MenuOptions} options for Menu
*/
showMenu(x, y, actions, menuOptions) {
this._createMenuComponent(x, y, actions, menuOptions);
showMenu(x, y, items, menuOptions) {
this._createMenuComponent(x, y, items, menuOptions);
this.menuComponent.showMenu();
}
actionsToMenuItems(actions, objectPath, view) {
return actions.map(action => {
const isActionGroup = Array.isArray(action);
if (isActionGroup) {
action = this.actionsToMenuItems(action, objectPath, view);
} else {
action.onItemClicked = () => {
action.invoke(objectPath, view);
};
}
return action;
});
}
/**
* Show popup menu with description of item on hover
* @param {number} x x-coordinates for popup

View File

@ -57,7 +57,7 @@ describe ('The Menu API', () => {
name: 'Test Action 1',
cssClass: 'icon-clock',
description: 'This is a test action',
callBack: () => {
onItemClicked: () => {
result = 'Test Action 1 Invoked';
}
},
@ -66,7 +66,7 @@ describe ('The Menu API', () => {
name: 'Test Action 2',
cssClass: 'icon-clock',
description: 'This is a test action',
callBack: () => {
onItemClicked: () => {
result = 'Test Action 2 Invoked';
}
}
@ -76,7 +76,7 @@ describe ('The Menu API', () => {
});
afterEach(() => {
resetApplicationState(openmct);
return resetApplicationState(openmct);
});
describe("showMenu method", () => {

View File

@ -11,7 +11,7 @@
:key="action.name"
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
:title="action.description"
@click="action.callBack"
@click="action.onItemClicked"
>
{{ action.name }}
</li>
@ -36,7 +36,7 @@
:key="action.name"
:class="action.cssClass"
:title="action.description"
@click="action.callBack"
@click="action.onItemClicked"
>
{{ action.name }}
</li>

View File

@ -13,7 +13,7 @@
:key="action.name"
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
:title="action.description"
@click="action.callBack"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
>
@ -42,7 +42,7 @@
:key="action.name"
:class="action.cssClass"
:title="action.description"
@click="action.callBack"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
>

View File

@ -71,12 +71,12 @@ class Menu extends EventEmitter {
showMenu() {
this.component = new Vue({
provide: {
options: this.options
},
components: {
MenuComponent
},
provide: {
options: this.options
},
template: '<menu-component />'
});
@ -85,12 +85,12 @@ class Menu extends EventEmitter {
showSuperMenu() {
this.component = new Vue({
provide: {
options: this.options
},
components: {
SuperMenuComponent
},
provide: {
options: this.options
},
template: '<super-menu-component />'
});

View File

@ -45,6 +45,8 @@ function ObjectAPI(typeRegistry, openmct) {
this.rootProvider = new RootObjectProvider(this.rootRegistry);
this.cache = {};
this.interceptorRegistry = new InterceptorRegistry();
this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan'];
}
/**
@ -397,20 +399,25 @@ ObjectAPI.prototype._toMutable = function (object) {
mutableObject = object;
} else {
mutableObject = MutableDomainObject.createMutable(object, this.eventEmitter);
}
// Check if provider supports realtime updates
let identifier = utils.parseKeyString(mutableObject.identifier);
let provider = this.getProvider(identifier);
// Check if provider supports realtime updates
let identifier = utils.parseKeyString(mutableObject.identifier);
let provider = this.getProvider(identifier);
if (provider !== undefined
&& provider.observe !== undefined) {
let unobserve = provider.observe(identifier, (updatedModel) => {
mutableObject.$refresh(updatedModel);
});
mutableObject.$on('$destroy', () => {
unobserve();
});
if (provider !== undefined
&& provider.observe !== undefined
&& this.SYNCHRONIZED_OBJECT_TYPES.includes(object.type)) {
let unobserve = provider.observe(identifier, (updatedModel) => {
if (updatedModel.persisted > mutableObject.modified) {
//Don't replace with a stale model. This can happen on slow connections when multiple mutations happen
//in rapid succession and intermediate persistence states are returned by the observe function.
mutableObject.$refresh(updatedModel);
}
});
mutableObject.$on('$_destroy', () => {
unobserve();
});
}
}
return mutableObject;

View File

@ -163,14 +163,22 @@ describe("The Object API", () => {
key: 'test-key'
},
name: 'test object',
type: 'notebook',
otherAttribute: 'other-attribute-value',
modified: 0,
persisted: 0,
objectAttribute: {
embeddedObject: {
embeddedKey: 'embedded-value'
}
}
};
updatedTestObject = Object.assign({otherAttribute: 'changed-attribute-value'}, testObject);
updatedTestObject = Object.assign({
otherAttribute: 'changed-attribute-value'
}, testObject);
updatedTestObject.modified = 1;
updatedTestObject.persisted = 1;
mockProvider = jasmine.createSpyObj("mock provider", [
"get",
"create",
@ -182,6 +190,8 @@ describe("The Object API", () => {
mockProvider.observeObjectChanges.and.callFake(() => {
callbacks[0](updatedTestObject);
callbacks.splice(0, 1);
return () => {};
});
mockProvider.observe.and.callFake((id, callback) => {
if (callbacks.length === 0) {
@ -189,6 +199,8 @@ describe("The Object API", () => {
} else {
callbacks[0] = callback;
}
return () => {};
});
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);

View File

@ -21,8 +21,7 @@
&__outer {
@include abs();
background: $overlayColorBg;
color: $overlayColorFg;
background: $colorBodyBg;
display: flex;
flex-direction: column;
padding: $overlayInnerMargin;
@ -30,7 +29,6 @@
&__close-button {
$p: $interiorMargin + 2px;
color: $overlayColorFg;
font-size: 1.5em;
position: absolute;
top: $p; right: $p;
@ -82,11 +80,6 @@
}
}
.c-button,
.c-click-icon {
filter: $overlayBrightnessAdjust;
}
.c-object-label__name {
filter: $objectLabelNameFilter;
}
@ -103,6 +96,7 @@ body.desktop {
}
// Overlay types, styling for desktop. Appended to .l-overlay-wrapper element.
.l-overlay-large,
.l-overlay-small,
.l-overlay-fit {
.c-overlay__outer {
@ -124,12 +118,8 @@ body.desktop {
$tbPad: floor($pad * 0.8);
$lrPad: $pad;
.c-overlay {
&__blocker {
display: none;
}
&__outer {
@include overlaySizing($overlayOuterMarginFullscreen);
@include overlaySizing($overlayOuterMarginLarge);
padding: $tbPad $lrPad;
}

View File

@ -22,7 +22,7 @@ describe("The Status API", () => {
});
afterEach(() => {
resetApplicationState(openmct);
return resetApplicationState(openmct);
});
describe("set function", () => {

View File

@ -504,6 +504,26 @@ define([
return this.getLimitEvaluator(domainObject);
};
/**
* Get a limits for this domain object.
* Limits help you display limits and alarms of
* telemetry for display purposes without having to interact directly
* with the Limit API.
*
* This method is optional.
* If a provider does not implement this method, it is presumed
* that no limits are defined for this domain object's telemetry.
*
* @param {module:openmct.DomainObject} domainObject the domain
* object for which to get limits
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
* @method limits
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/
TelemetryAPI.prototype.limitDefinition = function (domainObject) {
return this.getLimits(domainObject);
};
/**
* Get a limit evaluator for this domain object.
* Limit Evaluators help you evaluate limit and alarm status of individual
@ -531,5 +551,45 @@ define([
return provider.getLimitEvaluator(domainObject);
};
/**
* Get a limit definitions for this domain object.
* Limit Definitions help you indicate limits and alarms of
* telemetry for display purposes without having to interact directly
* with the Limit API.
*
* This method is optional.
* If a provider does not implement this method, it is presumed
* that no limits are defined for this domain object's telemetry.
*
* @param {module:openmct.DomainObject} domainObject the domain
* object for which to display limits
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
* @method limits returns a limits object of
* type {
* level1: {
* low: { key1: value1, key2: value2, color: <supportedColor> },
* high: { key1: value1, key2: value2, color: <supportedColor> }
* },
* level2: {
* low: { key1: value1, key2: value2 },
* high: { key1: value1, key2: value2 }
* }
* }
* supported colors are purple, red, orange, yellow and cyan
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/
TelemetryAPI.prototype.getLimits = function (domainObject) {
const provider = this.findLimitEvaluator(domainObject);
if (!provider || !provider.getLimits) {
return {
limits: function () {
return Promise.resolve(undefined);
}
};
}
return provider.getLimits(domainObject);
};
return TelemetryAPI;
});

View File

@ -0,0 +1,185 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* Class defining an image exporter for JPG/PNG output.
* Originally created by hudsonfoo on 09/02/16
*/
function replaceDotsWithUnderscores(filename) {
const regex = /\./gi;
return filename.replace(regex, '_');
}
import {saveAs} from 'file-saver/FileSaver';
import html2canvas from 'html2canvas';
import uuid from 'uuid';
class ImageExporter {
constructor(openmct) {
this.openmct = openmct;
}
/**
* Converts an HTML element into a PNG or JPG Blob.
* @private
* @param {node} element that will be converted to an image
* @param {object} options Image options.
* @returns {promise}
*/
renderElement(element, { imageType, className, thumbnailSize }) {
const self = this;
const overlays = this.openmct.overlays;
const dialog = overlays.dialog({
iconClass: 'info',
message: 'Caputuring an image',
buttons: [
{
label: 'Cancel',
emphasis: true,
callback: function () {
dialog.dismiss();
}
}
]
});
let mimeType = 'image/png';
if (imageType === 'jpg') {
mimeType = 'image/jpeg';
}
let exportId = undefined;
let oldId = undefined;
if (className) {
const newUUID = uuid();
exportId = `$export-element-${newUUID}`;
oldId = element.id;
element.id = exportId;
}
return html2canvas(element, {
onclone: function (document) {
if (className) {
const clonedElement = document.getElementById(exportId);
clonedElement.classList.add(className);
}
element.id = oldId;
},
removeContainer: true // Set to false to debug what html2canvas renders
}).then(function (canvas) {
dialog.dismiss();
return new Promise(function (resolve, reject) {
if (thumbnailSize) {
const thumbnail = self.getThumbnail(canvas, mimeType, thumbnailSize);
return canvas.toBlob(blob => resolve({
blob,
thumbnail
}), mimeType);
}
return canvas.toBlob(blob => resolve({ blob }), mimeType);
});
}, function (error) {
console.log('error capturing image', error);
dialog.dismiss();
const errorDialog = overlays.dialog({
iconClass: 'error',
message: 'Image was not captured successfully!',
buttons: [
{
label: "OK",
emphasis: true,
callback: function () {
errorDialog.dismiss();
}
}
]
});
});
}
getThumbnail(canvas, mimeType, size) {
const thumbnailCanvas = document.createElement('canvas');
thumbnailCanvas.setAttribute('width', size.width);
thumbnailCanvas.setAttribute('height', size.height);
const ctx = thumbnailCanvas.getContext('2d');
ctx.globalCompositeOperation = "copy";
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, size.width, size.height);
return thumbnailCanvas.toDataURL(mimeType);
}
/**
* Takes a screenshot of a DOM node and exports to JPG.
* @param {node} element to be exported
* @param {string} filename the exported image
* @param {string} className to be added to element before capturing (optional)
* @returns {promise}
*/
async exportJPG(element, filename, className) {
const processedFilename = replaceDotsWithUnderscores(filename);
const img = await this.renderElement(element, {
imageType: 'jpg',
className
});
saveAs(img.blob, processedFilename);
}
/**
* Takes a screenshot of a DOM node and exports to PNG.
* @param {node} element to be exported
* @param {string} filename the exported image
* @param {string} className to be added to element before capturing (optional)
* @returns {promise}
*/
async exportPNG(element, filename, className) {
const processedFilename = replaceDotsWithUnderscores(filename);
const img = await this.renderElement(element, {
imageType: 'png',
className
});
saveAs(img.blob, processedFilename);
}
/**
* Takes a screenshot of a DOM node in PNG format.
* @param {node} element to be exported
* @param {string} filename the exported image
* @returns {promise}
*/
exportPNGtoSRC(element, options) {
return this.renderElement(element, {
imageType: 'png',
...options
});
}
}
export default ImageExporter;

View File

@ -0,0 +1,58 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ImageExporter from './ImageExporter';
import { createOpenMct, resetApplicationState } from '../utils/testing';
describe('The Image Exporter', () => {
let openmct;
let imageExporter;
beforeEach(() => {
openmct = createOpenMct();
});
afterEach(() => {
return resetApplicationState(openmct);
});
describe("basic instatation", () => {
it("can be instatiated", () => {
imageExporter = new ImageExporter(openmct);
expect(imageExporter).not.toEqual(null);
});
it("can render an element to a blob", async () => {
const mockHeadElement = document.createElement("h1");
const mockTextNode = document.createTextNode('foo bar');
mockHeadElement.appendChild(mockTextNode);
document.body.appendChild(mockHeadElement);
imageExporter = new ImageExporter(openmct);
const returnedBlob = await imageExporter.renderElement(document.body, {
imageType: 'png'
});
expect(returnedBlob).not.toEqual(null);
expect(returnedBlob.blob).not.toEqual(null);
expect(returnedBlob.blob).toBeInstanceOf(Blob);
});
});
});

View File

@ -38,8 +38,6 @@ const DEFAULTS = [
'platform/exporters',
'platform/telemetry',
'platform/features/clock',
'platform/features/hyperlink',
'platform/features/timeline',
'platform/forms',
'platform/identity',
'platform/persistence/aggregator',
@ -82,9 +80,7 @@ define([
'../platform/exporters/bundle',
'../platform/features/clock/bundle',
'../platform/features/my-items/bundle',
'../platform/features/hyperlink/bundle',
'../platform/features/static-markup/bundle',
'../platform/features/timeline/bundle',
'../platform/forms/bundle',
'../platform/framework/bundle',
'../platform/framework/src/load/Bundle',

View File

@ -73,7 +73,7 @@ describe('the plugin', function () {
});
it('provides a folder to hold plans', () => {
openmct.objects.get(identifier).then((object) => {
return openmct.objects.get(identifier).then((object) => {
expect(object).toEqual({
identifier,
type: 'folder',
@ -83,7 +83,7 @@ describe('the plugin', function () {
});
it('provides composition for couch search folders', () => {
composition.load().then((objects) => {
return composition.load().then((objects) => {
expect(objects.length).toEqual(2);
});
});

View File

@ -19,8 +19,8 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import LadTableSet from './components/LadTableSet.vue';
import Vue from 'vue';
import LadTableSetView from './LadTableSetView';
export default function LADTableSetViewProvider(openmct) {
return {
@ -34,32 +34,7 @@ export default function LADTableSetViewProvider(openmct) {
return domainObject.type === 'LadTableSet';
},
view: function (domainObject, objectPath) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
LadTableSet: LadTableSet
},
data() {
return {
domainObject
};
},
provide: {
openmct,
objectPath
},
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
return new LadTableSetView(openmct, domainObject, objectPath);
},
priority: function () {
return 1;

View File

@ -0,0 +1,45 @@
import LadTable from './components/LADTable.vue';
import Vue from 'vue';
export default class LADTableView {
constructor(openmct, domainObject, objectPath) {
this.openmct = openmct;
this.domainObject = domainObject;
this.objectPath = objectPath;
this.component = undefined;
}
show(element) {
this.component = new Vue({
el: element,
components: {
LadTable
},
provide: {
openmct: this.openmct,
currentView: this
},
data: () => {
return {
domainObject: this.domainObject,
objectPath: this.objectPath
};
},
template: '<lad-table ref="ladTable" :domain-object="domainObject" :object-path="objectPath"></lad-table>'
});
}
getViewContext() {
if (!this.component) {
return {};
}
return this.component.$refs.ladTable.getViewContext();
}
destroy(element) {
this.component.$destroy();
this.component = undefined;
}
}

View File

@ -19,50 +19,30 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import LadTable from './components/LADTable.vue';
import Vue from 'vue';
export default function LADTableViewProvider(openmct) {
return {
key: 'LadTable',
name: 'LAD Table',
cssClass: 'icon-tabular-lad',
canView: function (domainObject) {
return domainObject.type === 'LadTable';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTable';
},
view: function (domainObject, objectPath) {
let component;
import LADTableView from './LADTableView';
return {
show: function (element) {
component = new Vue({
el: element,
components: {
LadTableComponent: LadTable
},
provide: {
openmct
},
data: () => {
return {
domainObject,
objectPath
};
},
template: '<lad-table-component :domain-object="domainObject" :object-path="objectPath"></lad-table-component>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
export default class LADTableViewProvider {
constructor(openmct) {
this.openmct = openmct;
this.name = 'LAD Table';
this.key = 'LadTable';
this.cssClass = 'icon-tabular-lad';
}
canView(domainObject) {
return domainObject.type === 'LadTable';
}
canEdit(domainObject) {
return domainObject.type === 'LadTable';
}
view(domainObject, objectPath) {
return new LADTableView(this.openmct, domainObject, objectPath);
}
priority(domainObject) {
return 1;
}
}

View File

@ -0,0 +1,45 @@
import LadTableSet from './components/LadTableSet.vue';
import Vue from 'vue';
export default class LadTableSetView {
constructor(openmct, domainObject, objectPath) {
this.openmct = openmct;
this.domainObject = domainObject;
this.objectPath = objectPath;
this.component = undefined;
}
show(element) {
this.component = new Vue({
el: element,
components: {
LadTableSet
},
provide: {
openmct: this.openmct,
objectPath: this.objectPath,
currentView: this
},
data: () => {
return {
domainObject: this.domainObject
};
},
template: '<lad-table-set ref="ladTableSet" :domain-object="domainObject"></lad-table-set>'
});
}
getViewContext() {
if (!this.component) {
return {};
}
return this.component.$refs.ladTableSet.getViewContext();
}
destroy(element) {
this.component.$destroy();
this.component = undefined;
}
}

View File

@ -50,7 +50,7 @@ const CONTEXT_MENU_ACTIONS = [
];
export default {
inject: ['openmct'],
inject: ['openmct', 'currentView'],
props: {
domainObject: {
type: Object,
@ -167,25 +167,23 @@ export default {
this.resetValues();
this.timestampKey = timeSystem.key;
},
getView() {
return {
getViewContext: () => {
return {
viewHistoricalData: true,
viewDatumAction: true,
getDatum: () => {
return this.datum;
}
};
updateViewContext() {
this.$emit('rowContextClick', {
viewHistoricalData: true,
viewDatumAction: true,
getDatum: () => {
return this.datum;
}
};
});
},
showContextMenu(event) {
let actionCollection = this.openmct.actions.get(this.objectPath, this.getView());
let allActions = actionCollection.getActionsObject();
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
this.updateViewContext();
this.openmct.menus.showMenu(event.x, event.y, applicableActions);
const actions = CONTEXT_MENU_ACTIONS.map(key => this.openmct.actions.getAction(key));
const menuItems = this.openmct.menus.actionsToMenuItems(actions, this.objectPath, this.currentView);
if (menuItems.length) {
this.openmct.menus.showMenu(event.x, event.y, menuItems);
}
},
resetValues() {
this.value = '---';

View File

@ -38,6 +38,7 @@
:domain-object="ladRow.domainObject"
:path-to-table="objectPath"
:has-units="hasUnits"
@rowContextClick="updateViewContext"
/>
</tbody>
</table>
@ -51,7 +52,7 @@ export default {
components: {
LadRow
},
inject: ['openmct'],
inject: ['openmct', 'currentView'],
props: {
domainObject: {
type: Object,
@ -64,7 +65,8 @@ export default {
},
data() {
return {
items: []
items: [],
viewContext: {}
};
},
computed: {
@ -114,6 +116,12 @@ export default {
let metadataWithUnits = valueMetadatas.filter(metadatum => metadatum.unit);
return metadataWithUnits.length > 0;
},
updateViewContext(rowContext) {
this.viewContext.row = rowContext;
},
getViewContext() {
return this.viewContext;
}
}
};

View File

@ -48,6 +48,7 @@
:domain-object="ladRow.domainObject"
:path-to-table="ladTable.objectPath"
:has-units="hasUnits"
@rowContextClick="updateViewContext"
/>
</template>
</tbody>
@ -61,7 +62,7 @@ export default {
components: {
LadRow
},
inject: ['openmct', 'objectPath'],
inject: ['openmct', 'objectPath', 'currentView'],
props: {
domainObject: {
type: Object,
@ -72,7 +73,8 @@ export default {
return {
ladTableObjects: [],
ladTelemetryObjects: {},
compositions: []
compositions: [],
viewContext: {}
};
},
computed: {
@ -166,6 +168,12 @@ export default {
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
};
},
updateViewContext(rowContext) {
this.viewContext.row = rowContext;
},
getViewContext() {
return this.viewContext;
}
}
};

View File

@ -67,10 +67,6 @@ describe("The LAD Table", () => {
// this setups up the app
beforeEach((done) => {
const appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
parent = document.createElement('div');
@ -90,7 +86,7 @@ describe("The LAD Table", () => {
});
openmct.on('start', done);
openmct.startHeadless(appHolder);
openmct.startHeadless();
});
afterEach(() => {
@ -113,7 +109,8 @@ describe("The LAD Table", () => {
beforeEach(() => {
ladTableCompositionCollection = openmct.composition.get(mockObj.ladTable);
ladTableCompositionCollection.load();
return ladTableCompositionCollection.load();
});
it("should accept telemetry producing objects", () => {
@ -192,8 +189,6 @@ describe("The LAD Table", () => {
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
await Vue.nextTick();
return;
});
it("should show one row per object in the composition", () => {
@ -242,13 +237,6 @@ describe("The LAD Table Set", () => {
let ladPlugin;
let parent;
let child;
let telemetryCount = 3;
let timeFormat = 'utc';
let mockTelemetry = getMockTelemetry({
count: telemetryCount,
format: timeFormat
});
let mockObj = getMockObjects({
objectKeyStrings: ['ladTable', 'ladTableSet', 'telemetry']
@ -264,38 +252,36 @@ describe("The LAD Table Set", () => {
mockObj.ladTableSet.composition.push(mockObj.ladTable.identifier);
beforeEach((done) => {
const appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
parent = document.createElement('div');
child = document.createElement('div');
parent.appendChild(child);
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
ladPlugin = new LadPlugin();
openmct.install(ladPlugin);
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
openmct.time.bounds({
start: bounds.start,
end: bounds.end
});
openmct.on('start', done);
openmct.start(appHolder);
openmct.startHeadless();
});
afterEach(() => {
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
return resetApplicationState(openmct);
});
it("should provide a lad table set view only for lad table set objects", () => {
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
let applicableViews = openmct.objectViews.get(mockObj.ladTableSet, []);
let ladTableSetView = applicableViews.find(
@ -310,8 +296,11 @@ describe("The LAD Table Set", () => {
let ladTableSetCompositionCollection;
beforeEach(() => {
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet);
ladTableSetCompositionCollection.load();
return ladTableSetCompositionCollection.load();
});
it("should accept lad table objects", () => {
@ -349,41 +338,17 @@ describe("The LAD Table Set", () => {
otherObj.ladTable.composition.push(mockObj.telemetry.identifier);
mockObj.ladTableSet.composition.push(otherObj.ladTable.identifier);
beforeEach(async () => {
let telemetryRequestResolve;
let ladObjectResolve;
let anotherLadObjectResolve;
beforeEach(() => {
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
let telemetryRequestPromise = new Promise((resolve) => {
telemetryRequestResolve = resolve;
});
let ladObjectPromise = new Promise((resolve) => {
ladObjectResolve = resolve;
});
let anotherLadObjectPromise = new Promise((resolve) => {
anotherLadObjectResolve = resolve;
});
openmct.telemetry.request.and.callFake(() => {
telemetryRequestResolve(mockTelemetry);
return telemetryRequestPromise;
});
openmct.objects.get.and.callFake((obj) => {
spyOn(openmct.objects, 'get').and.callFake((obj) => {
if (obj.key === 'lad-object') {
ladObjectResolve(mockObj.ladObject);
return ladObjectPromise;
return Promise.resolve(mockObj.ladTable);
} else if (obj.key === 'another-lad-object') {
anotherLadObjectResolve(otherObj.ladObject);
return anotherLadObjectPromise;
return Promise.resolve(otherObj.ladTable);
} else if (obj.key === 'telemetry-object') {
return Promise.resolve(mockObj.telemetry);
}
return Promise.resolve({});
});
openmct.time.bounds({
@ -394,20 +359,19 @@ describe("The LAD Table Set", () => {
applicableViews = openmct.objectViews.get(mockObj.ladTableSet, []);
ladTableSetViewProvider = applicableViews.find((viewProvider) => viewProvider.key === ladTableSetKey);
ladTableSetView = ladTableSetViewProvider.view(mockObj.ladTableSet, [mockObj.ladTableSet]);
ladTableSetView.show(child, true);
ladTableSetView.show(child);
await Promise.all([telemetryRequestPromise, ladObjectPromise, anotherLadObjectPromise]);
await Vue.nextTick();
return;
return Vue.nextTick();
});
it("should show one row per lad table object in the composition", () => {
const rowCount = parent.querySelectorAll(LAD_SET_TABLE_HEADERS).length;
const ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet);
expect(rowCount).toBe(mockObj.ladTableSet.composition.length);
pending();
return ladTableSetCompositionCollection.load().then(() => {
const rowCount = parent.querySelectorAll(LAD_SET_TABLE_HEADERS).length;
expect(rowCount).toBe(mockObj.ladTableSet.composition.length);
});
});
});
});

View File

@ -22,12 +22,14 @@
define(
[
"utils/testing",
"./URLIndicator",
"./URLIndicatorPlugin",
"../../MCT",
"zepto"
],
function (
testingUtils,
URLIndicator,
URLIndicatorPlugin,
MCT,
@ -44,7 +46,7 @@ define(
beforeEach(function () {
jasmine.clock().install();
openmct = new MCT();
openmct = new testingUtils.createOpenMct();
spyOn(openmct.indicators, 'add');
spyOn($, 'ajax');
$.ajax.and.callFake(function (options) {
@ -55,6 +57,8 @@ define(
afterEach(function () {
$.ajax = defaultAjaxFunction;
jasmine.clock().uninstall();
return testingUtils.resetApplicationState(openmct);
});
describe("on initialization", function () {

View File

@ -19,10 +19,6 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
getAllSearchParams,
setAllSearchParams
} from 'utils/openmctLocation';
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
const SEARCH_MODE = 'tc.mode';
@ -50,8 +46,8 @@ export default class URLTimeSettingsSynchronizer {
initialize() {
this.updateTimeSettings();
this.openmct.router.on('change:params', this.updateTimeSettings);
window.addEventListener('hashchange', this.updateTimeSettings);
TIME_EVENTS.forEach(event => {
this.openmct.time.on(event, this.setUrlFromTimeApi);
});
@ -59,7 +55,8 @@ export default class URLTimeSettingsSynchronizer {
}
destroy() {
window.removeEventListener('hashchange', this.updateTimeSettings);
this.openmct.router.off('change:params', this.updateTimeSettings);
this.openmct.off('start', this.initialize);
this.openmct.off('destroy', this.destroy);
@ -70,22 +67,18 @@ export default class URLTimeSettingsSynchronizer {
}
updateTimeSettings() {
// Prevent from triggering self
if (!this.isUrlUpdateInProgress) {
let timeParameters = this.parseParametersFromUrl();
let timeParameters = this.parseParametersFromUrl();
if (this.areTimeParametersValid(timeParameters)) {
this.setTimeApiFromUrl(timeParameters);
} else {
this.setUrlFromTimeApi();
}
if (this.areTimeParametersValid(timeParameters)) {
this.setTimeApiFromUrl(timeParameters);
this.openmct.router.setLocationFromUrl();
} else {
this.isUrlUpdateInProgress = false;
this.setUrlFromTimeApi();
}
}
parseParametersFromUrl() {
let searchParams = getAllSearchParams();
let searchParams = this.openmct.router.getAllSearchParams();
let mode = searchParams.get(SEARCH_MODE);
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
@ -148,7 +141,7 @@ export default class URLTimeSettingsSynchronizer {
}
setUrlFromTimeApi() {
let searchParams = getAllSearchParams();
let searchParams = this.openmct.router.getAllSearchParams();
let clock = this.openmct.time.clock();
let bounds = this.openmct.time.bounds();
let clockOffsets = this.openmct.time.clockOffsets();
@ -176,8 +169,7 @@ export default class URLTimeSettingsSynchronizer {
}
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
this.isUrlUpdateInProgress = true;
setAllSearchParams(searchParams);
this.openmct.router.setAllSearchParams(searchParams);
}
areTimeParametersValid(timeParameters) {

View File

@ -25,306 +25,118 @@ import {
} from 'utils/testing';
describe("The URLTimeSettingsSynchronizer", () => {
let appHolder;
let openmct;
let testClock;
let resolveFunction;
let oldHash;
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalTimeSystem());
testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
testClock.key = "test-clock";
testClock.currentValue.and.returnValue(0);
openmct.time.addClock(testClock);
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.on('start', done);
openmct.startHeadless();
appHolder = document.createElement("div");
openmct.start(appHolder);
});
afterEach(() => resetApplicationState(openmct));
afterEach(() => {
openmct.time.stopClock();
openmct.router.removeListener('change:hash', resolveFunction);
describe("realtime mode", () => {
it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
//Test expected initial conditions
appHolder = undefined;
openmct = undefined;
resolveFunction = undefined;
return resetApplicationState(openmct);
});
it("initial clock is set to fixed is reflected in URL", (done) => {
resolveFunction = () => {
oldHash = window.location.hash;
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
openmct.router.removeListener('change:hash', resolveFunction);
done();
};
openmct.router.on('change:hash', resolveFunction);
});
it("when the clock is set via the time API, it is reflected in the URL", (done) => {
let success;
resolveFunction = () => {
openmct.time.clock('local', {
start: -1000,
end: 100
});
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
});
it("when offsets are set via the time API, they are immediately reflected in the URL", () => {
//Test expected initial conditions
expect(window.location.hash.includes('tc.startDelta')).toBe(false);
expect(window.location.hash.includes('tc.endDelta')).toBe(false);
openmct.time.clock('local', {
start: -1000,
end: 100
});
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
openmct.time.clockOffsets({
start: -2000,
end: 200
});
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
});
describe("when set in the url", () => {
it("will change from fixed to realtime mode when the mode changes", () => {
expectLocationToBeInFixedMode();
const hasStartDelta = window.location.hash.includes('tc.startDelta=2000');
const hasEndDelta = window.location.hash.includes('tc.endDelta=200');
const hasLocalClock = window.location.hash.includes('tc.mode=local');
success = hasStartDelta && hasEndDelta && hasLocalClock;
if (success) {
expect(success).toBe(true);
return switchToRealtimeMode().then(() => {
let clock = openmct.time.clock();
openmct.router.removeListener('change:hash', resolveFunction);
done();
}
};
expect(clock).toBeDefined();
expect(clock.key).toBe('local');
});
});
it("the clock is correctly set in the API from the URL parameters", () => {
return switchToRealtimeMode().then(() => {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('clock', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.mode=local', 'tc.mode=test-clock');
window.location.hash = hash;
}).then(() => {
let clock = openmct.time.clock();
expect(clock).toBeDefined();
expect(clock.key).toBe('test-clock');
openmct.time.off('clock', resolveFunction);
});
});
});
it("the clock offsets are correctly set in the API from the URL parameters", () => {
return switchToRealtimeMode().then(() => {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('clockOffsets', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.startDelta=1000', 'tc.startDelta=2000');
hash = hash.replace('tc.endDelta=100', 'tc.endDelta=200');
window.location.hash = hash;
}).then(() => {
let clockOffsets = openmct.time.clockOffsets();
expect(clockOffsets).toBeDefined();
expect(clockOffsets.start).toBe(-2000);
expect(clockOffsets.end).toBe(200);
openmct.time.off('clockOffsets', resolveFunction);
});
});
});
it("the time system is correctly set in the API from the URL parameters", () => {
return switchToRealtimeMode().then(() => {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('timeSystem', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
window.location.hash = hash;
}).then(() => {
let timeSystem = openmct.time.timeSystem();
expect(timeSystem).toBeDefined();
expect(timeSystem.key).toBe('local');
openmct.time.off('timeSystem', resolveFunction);
});
});
});
});
});
describe("fixed timespan mode", () => {
beforeEach(() => {
openmct.time.stopClock();
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
});
it("when bounds are set via the time API, they are immediately reflected in the URL", () => {
//Test expected initial conditions
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
openmct.time.bounds({
start: 10,
end: 20
});
expect(window.location.hash.includes('tc.startBound=10')).toBe(true);
expect(window.location.hash.includes('tc.endBound=20')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.startBound=0')).toBe(false);
expect(window.location.hash.includes('tc.endBound=1')).toBe(false);
});
it("when time system is set via the time API, it is immediately reflected in the URL", () => {
//Test expected initial conditions
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(true);
openmct.time.timeSystem('local', {
start: 20,
end: 30
});
expect(window.location.hash.includes('tc.timeSystem=local')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
});
describe("when set in the url", () => {
it("time system changes are reflected in the API", () => {
let resolveFunction;
return new Promise((resolve) => {
let timeSystem = openmct.time.timeSystem();
resolveFunction = resolve;
expect(timeSystem.key).toBe('utc');
window.location.hash = window.location.hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
openmct.time.on('timeSystem', resolveFunction);
}).then(() => {
let timeSystem = openmct.time.timeSystem();
expect(timeSystem.key).toBe('local');
openmct.time.off('timeSystem', resolveFunction);
});
});
it("mode can be changed from realtime to fixed", () => {
return switchToRealtimeMode().then(() => {
expectLocationToBeInRealtimeMode();
expect(openmct.time.clock()).toBeDefined();
}).then(switchToFixedMode).then(() => {
let clock = openmct.time.clock();
expect(clock).not.toBeDefined();
});
});
it("bounds are correctly set in the API from the URL parameters", () => {
let resolveFunction;
expectLocationToBeInFixedMode();
return new Promise((resolve) => {
resolveFunction = resolve;
openmct.time.on('bounds', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.startBound=0', 'tc.startBound=222')
.replace('tc.endBound=1', 'tc.endBound=333');
window.location.hash = hash;
}).then(() => {
let bounds = openmct.time.bounds();
expect(bounds).toBeDefined();
expect(bounds.start).toBe(222);
expect(bounds.end).toBe(333);
});
});
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => {
let resolveFunction;
expectLocationToBeInFixedMode();
return new Promise((resolve) => {
resolveFunction = resolve;
openmct.time.on('bounds', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.endBound=1', 'tc.endBound=333');
window.location.hash = hash;
}).then(() => {
let bounds = openmct.time.bounds();
expect(bounds).toBeDefined();
expect(bounds.start).toBe(0);
expect(bounds.end).toBe(333);
});
});
});
openmct.router.on('change:hash', resolveFunction);
});
function setRealtimeLocationParameters() {
let hash = window.location.hash.toString()
.replace('tc.mode=fixed', 'tc.mode=local')
.replace('tc.startBound=0', 'tc.startDelta=1000')
.replace('tc.endBound=1', 'tc.endDelta=100');
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
let success;
window.location.hash = hash;
}
resolveFunction = () => {
let hash = window.location.hash;
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
window.location.hash = hash;
function setFixedLocationParameters() {
let hash = window.location.hash.toString()
.replace('tc.mode=local', 'tc.mode=fixed')
.replace('tc.timeSystem=utc', 'tc.timeSystem=local')
.replace('tc.startDelta=1000', 'tc.startBound=50')
.replace('tc.endDelta=100', 'tc.endBound=60');
success = window.location.hash.includes('tc.mode=local');
if (success) {
expect(success).toBe(true);
done();
}
};
window.location.hash = hash;
}
openmct.router.on('change:hash', resolveFunction);
});
function switchToRealtimeMode() {
let resolveFunction;
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
let success;
return new Promise((resolve) => {
resolveFunction = resolve;
openmct.time.on('clock', resolveFunction);
setRealtimeLocationParameters();
}).then(() => {
openmct.time.off('clock', resolveFunction);
});
}
resolveFunction = () => {
let hash = window.location.hash;
function switchToFixedMode() {
let resolveFunction;
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
window.location.hash = hash;
success = window.location.hash.includes('tc.mode=local');
if (success) {
expect(success).toBe(true);
done();
}
};
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('clock', resolveFunction);
setFixedLocationParameters();
}).then(() => {
openmct.time.off('clock', resolveFunction);
});
}
openmct.router.on('change:hash', resolveFunction);
});
function expectLocationToBeInRealtimeMode() {
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
}
it("reset hash", (done) => {
let success;
function expectLocationToBeInFixedMode() {
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
expect(window.location.hash.includes('tc.mode=local')).toBe(false);
}
window.location.hash = oldHash;
resolveFunction = () => {
success = window.location.hash === oldHash;
if (success) {
expect(success).toBe(true);
done();
}
};
openmct.router.on('change:hash', resolveFunction);
});
});

View File

@ -28,8 +28,10 @@ import {
resetApplicationState,
spyOnBuiltins
} from 'utils/testing';
import Vue from 'vue';
describe("AutoflowTabularPlugin", () => {
// TODO lots of its without expects
xdescribe("AutoflowTabularPlugin", () => {
let testType;
let testObject;
let mockmct;
@ -51,7 +53,7 @@ describe("AutoflowTabularPlugin", () => {
});
afterEach(() => {
resetApplicationState(mockmct);
return resetApplicationState(mockmct);
});
it("installs a view provider", () => {
@ -101,7 +103,7 @@ describe("AutoflowTabularPlugin", () => {
});
}
beforeEach((done) => {
beforeEach(() => {
callbacks = {};
spyOnBuiltins(['requestAnimationFrame']);
@ -180,7 +182,7 @@ describe("AutoflowTabularPlugin", () => {
view = provider.view(testObject);
view.show(testContainer);
return done();
return Vue.nextTick();
});
afterEach(() => {

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, United States Government
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -20,37 +20,40 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
import Clock from './components/Clock.vue';
import Vue from 'vue';
/**
* Designates a specific timer for following. Timelines, for example,
* use the actively followed timer to display a time-of-interest line
* and interpret time conductor bounds in the Timeline's relative
* time frame.
*
* @implements {Action}
* @memberof platform/features/clock
* @constructor
* @param {ActionContext} context the context for this action
*/
function FollowTimerAction(timerService, context) {
var domainObject =
context.domainObject
&& context.domainObject.useCapability('adapter');
this.perform =
timerService.setTimer.bind(timerService, domainObject);
export default function ClockViewProvider(openmct) {
return {
key: 'clock.view',
name: 'Clock',
cssClass: 'icon-clock',
canView(domainObject) {
return domainObject.type === 'clock';
},
view: function (domainObject) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
Clock
},
provide: {
openmct,
domainObject
},
template: '<clock></clock>'
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
};
}
FollowTimerAction.appliesTo = function (context) {
var model =
(context.domainObject && context.domainObject.getModel())
|| {};
return model.type === 'timer';
};
return FollowTimerAction;
}
);
};
}

View File

@ -0,0 +1,99 @@
<!--
Open MCT, Copyright (c) 2014-2021, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div class="l-angular-ov-wrapper">
<div class="u-contents">
<div class="c-clock l-time-display u-style-receiver js-style-receiver">
<div class="c-clock__timezone">
{{ timeZoneAbbr }}
</div>
<div class="c-clock__value">
{{ timeTextValue }}
</div>
<div class="c-clock__ampm">
{{ timeAmPm }}
</div>
</div>
</div>
</div>
</template>
<script>
import moment from 'moment';
import momentTimezone from 'moment-timezone';
export default {
inject: ['openmct', 'domainObject'],
data() {
return {
lastTimestamp: null
};
},
computed: {
configuration() {
return this.domainObject.configuration;
},
baseFormat() {
return this.configuration.baseFormat;
},
use24() {
return this.configuration.use24 === 'clock24';
},
timezone() {
return this.configuration.timezone;
},
timeFormat() {
return this.use24 ? this.baseFormat.replace('hh', "HH") : this.baseFormat;
},
zoneName() {
return momentTimezone.tz.names().includes(this.timezone) ? this.timezone : "UTC";
},
momentTime() {
return this.zoneName ? moment.utc(this.lastTimestamp).tz(this.zoneName) : moment.utc(this.lastTimestamp);
},
timeZoneAbbr() {
return this.momentTime.zoneAbbr();
},
timeTextValue() {
return this.timeFormat && this.momentTime.format(this.timeFormat);
},
timeAmPm() {
return this.use24 ? '' : this.momentTime.format("A");
}
},
mounted() {
const TickerService = this.openmct.$injector.get('tickerService');
this.unlisten = TickerService.listen(this.tick);
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
},
methods: {
tick(timestamp) {
this.lastTimestamp = timestamp;
}
}
};
</script>

View File

@ -0,0 +1,64 @@
<!--
Open MCT, Copyright (c) 2014-2021, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div class="c-indicator t-indicator-clock icon-clock no-minify c-indicator--not-clickable">
<span class="label c-indicator__label">
{{ timeTextValue }}
</span>
</div>
</template>
<script>
import moment from 'moment';
export default {
inject: ['openmct'],
props: {
indicatorFormat: {
type: String,
required: true
}
},
data() {
return {
timeTextValue: null
};
},
mounted() {
this.openmct.on('start', () => {
const TickerService = this.openmct.$injector.get('tickerService');
this.unlisten = TickerService.listen(this.tick);
});
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
},
methods: {
tick(timestamp) {
this.timeTextValue = `${moment.utc(timestamp).format(this.indicatorFormat)} UTC`;
}
}
};
</script>

154
src/plugins/clock/plugin.js Normal file
View File

@ -0,0 +1,154 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ClockViewProvider from './ClockViewProvider';
import ClockIndicator from './components/ClockIndicator.vue';
import momentTimezone from 'moment-timezone';
import Vue from 'vue';
export default function ClockPlugin(options) {
return function install(openmct) {
const CLOCK_INDICATOR_FORMAT = 'YYYY/MM/DD HH:mm:ss';
openmct.types.addType('clock', {
name: 'Clock',
description: 'A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.',
creatable: true,
cssClass: 'icon-clock',
initialize: function (domainObject) {
domainObject.configuration = {
baseFormat: 'YYYY/MM/DD hh:mm:ss',
use24: 'clock12',
timezone: 'UTC'
};
},
"form": [
{
"key": "displayFormat",
"name": "Display Format",
control: 'select',
options: [
{
value: 'YYYY/MM/DD hh:mm:ss',
name: 'YYYY/MM/DD hh:mm:ss'
},
{
value: 'YYYY/DDD hh:mm:ss',
name: 'YYYY/DDD hh:mm:ss'
},
{
value: 'hh:mm:ss',
name: 'hh:mm:ss'
}
],
cssClass: 'l-inline',
property: [
'configuration',
'baseFormat'
]
},
{
control: 'select',
options: [
{
value: 'clock12',
name: '12hr'
},
{
value: 'clock24',
name: '24hr'
}
],
cssClass: 'l-inline',
property: [
'configuration',
'use24'
]
},
{
"key": "timezone",
"name": "Timezone",
"control": "autocomplete",
"options": momentTimezone.tz.names(),
property: [
'configuration',
'timezone'
]
}
]
});
openmct.objectViews.addProvider(new ClockViewProvider(openmct));
if (options && options.enableClockIndicator) {
const clockIndicator = new Vue ({
components: {
ClockIndicator
},
provide: {
openmct
},
data() {
return {
indicatorFormat: CLOCK_INDICATOR_FORMAT
};
},
template: '<ClockIndicator :indicator-format="indicatorFormat"></ClockIndicator>'
});
const indicator = {
element: clockIndicator.$mount().$el,
key: 'clock-indicator'
};
openmct.indicators.add(indicator);
}
openmct.objects.addGetInterceptor({
appliesTo: (identifier, domainObject) => {
return domainObject && domainObject.type === 'clock';
},
invoke: (identifier, domainObject) => {
if (domainObject.configuration) {
return domainObject;
}
if (domainObject.clockFormat
&& domainObject.timezone) {
const baseFormat = domainObject.clockFormat[0];
const use24 = domainObject.clockFormat[1];
const timezone = domainObject.timezone;
domainObject.configuration = {
baseFormat,
use24,
timezone
};
openmct.objects.mutate(domainObject, 'configuration', domainObject.configuration);
}
return domainObject;
}
});
};
}

View File

@ -41,7 +41,7 @@ export default class ConditionManager extends EventEmitter {
this.subscriptions = {};
this.telemetryObjects = {};
this.testData = {
conditionTestData: [],
conditionTestInputs: this.conditionSetDomainObject.configuration.conditionTestData,
applied: false
};
this.initialize();
@ -154,8 +154,10 @@ export default class ConditionManager extends EventEmitter {
updateConditionDescription(condition) {
const found = this.conditionSetDomainObject.configuration.conditionCollection.find(conditionConfiguration => (conditionConfiguration.id === condition.id));
found.summary = condition.description;
this.persistConditions();
if (found.summary !== condition.description) {
found.summary = condition.description;
this.persistConditions();
}
}
initCondition(conditionConfiguration, index) {
@ -414,8 +416,10 @@ export default class ConditionManager extends EventEmitter {
}
updateTestData(testData) {
this.testData = testData;
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
if (!_.isEqual(testData, this.testData)) {
this.testData = testData;
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
}
}
persistConditions() {

View File

@ -27,15 +27,17 @@ export default class StyleRuleManager extends EventEmitter {
super();
this.openmct = openmct;
this.callback = callback;
this.refreshData = this.refreshData.bind(this);
this.toggleSubscription = this.toggleSubscription.bind(this);
if (suppressSubscriptionOnEdit) {
this.openmct.editor.on('isEditing', this.toggleSubscription.bind(this));
this.openmct.editor.on('isEditing', this.toggleSubscription);
this.isEditing = this.openmct.editor.editing;
}
if (styleConfiguration) {
this.initialize(styleConfiguration);
if (styleConfiguration.conditionSetIdentifier) {
this.openmct.time.on("bounds", this.refreshData.bind(this));
this.openmct.time.on("bounds", this.refreshData);
this.subscribeToConditionSet();
} else {
this.applyStaticStyle();

View File

@ -215,7 +215,8 @@ export default {
},
isEditing: {
type: Boolean,
required: true
required: true,
default: false
},
telemetry: {
type: Array,

View File

@ -52,7 +52,6 @@
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
<a v-if="conditionSetDomainObject"
class="c-object-label icon-conditional"
:href="navigateToPath"
@click="navigateOrPreview"
>
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
@ -286,6 +285,8 @@ export default {
if (this.openmct.editor.isEditing()) {
event.preventDefault();
this.previewAction.invoke(this.objectPath);
} else {
this.openmct.router.navigate(this.navigateToPath);
}
},
removeConditionSet() {

View File

@ -66,7 +66,6 @@
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
<a v-if="conditionSetDomainObject"
class="c-object-label"
:href="navigateToPath"
@click="navigateOrPreview"
>
<span class="c-object-label__type-icon icon-conditional"></span>
@ -309,6 +308,8 @@ export default {
if (this.openmct.editor.isEditing()) {
event.preventDefault();
this.previewAction.invoke(this.objectPath);
} else {
this.openmct.router.navigate(this.navigateToPath);
}
},
isItemType(type, item) {

View File

@ -46,7 +46,7 @@ xdescribe("the plugin", () => {
});
afterEach(() => {
resetApplicationState(openmct);
return resetApplicationState(openmct);
});
it('installs the new folder action', () => {

View File

@ -20,71 +20,78 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./components/AlphanumericFormatView.vue',
'vue'
], function (AlphanumericFormatView, Vue) {
import AlphanumericFormat from './components/AlphanumericFormat.vue';
function AlphanumericFormatViewProvider(openmct, options) {
function isTelemetryObject(selectionPath) {
let selectedObject = selectionPath[0].context.item;
let parentObject = selectionPath[1].context.item;
let selectedLayoutItem = selectionPath[0].context.layoutItem;
import Vue from 'vue';
return parentObject
&& parentObject.type === 'layout'
&& selectedObject
&& selectedLayoutItem
&& selectedLayoutItem.type === 'telemetry-view'
&& openmct.telemetry.isTelemetryObject(selectedObject)
&& !options.showAsView.includes(selectedObject.type);
}
return {
key: 'alphanumeric-format',
name: 'Alphanumeric Format',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 1) {
return false;
}
return selection.every(isTelemetryObject);
},
view: function (domainObject, objectPath) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
AlphanumericFormatView: AlphanumericFormatView.default
},
provide: {
openmct,
objectPath
},
template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>'
});
},
getViewContext() {
if (component) {
return component.$refs.alphanumericFormatView.getViewContext();
} else {
return {};
}
},
destroy: function () {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
class AlphanumericFormatView {
constructor(openmct, domainObject, objectPath) {
this.openmct = openmct;
this.domainObject = domainObject;
this.objectPath = objectPath;
this.component = undefined;
}
return AlphanumericFormatViewProvider;
});
show(element) {
this.component = new Vue({
el: element,
name: 'AlphanumericFormat',
components: {
AlphanumericFormat
},
provide: {
openmct: this.openmct,
objectPath: this.objectPath,
currentView: this
},
template: '<alphanumeric-format ref="alphanumericFormat"></alphanumeric-format>'
});
}
getViewContext() {
if (this.component) {
return {};
}
return this.component.$refs.alphanumericFormat.getViewContext();
}
destroy() {
this.component.$destroy();
this.component = undefined;
}
}
export default function AlphanumericFormatViewProvider(openmct, options) {
function isTelemetryObject(selectionPath) {
let selectedObject = selectionPath[0].context.item;
let parentObject = selectionPath[1].context.item;
let selectedLayoutItem = selectionPath[0].context.layoutItem;
return parentObject
&& parentObject.type === 'layout'
&& selectedObject
&& selectedLayoutItem
&& selectedLayoutItem.type === 'telemetry-view'
&& openmct.telemetry.isTelemetryObject(selectedObject)
&& !options.showAsView.includes(selectedObject.type);
}
return {
key: 'alphanumeric-format',
name: 'Alphanumeric Format',
canView: function (selection) {
if (selection.length === 0 || selection[0].length === 1) {
return false;
}
return selection.every(isTelemetryObject);
},
view: function (domainObject, objectPath) {
return new AlphanumericFormatView(openmct, domainObject, objectPath);
},
priority: function () {
return 1;
}
};
}

View File

@ -235,7 +235,7 @@ define(['lodash'], function (_) {
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
label: 'OK',
emphasis: 'true',
callback: function () {
removeItem(getAllTypes(selection));

View File

@ -14,7 +14,7 @@ export default class CopyToClipboardAction {
invoke(objectPath, view = {}) {
const viewContext = view.getViewContext && view.getViewContext();
const formattedValue = viewContext.formattedValueForCopy();
const formattedValue = viewContext.row.formattedValueForCopy();
clipboard.updateClipboard(formattedValue)
.then(() => {
@ -26,9 +26,13 @@ export default class CopyToClipboardAction {
}
appliesTo(objectPath, view = {}) {
let viewContext = view.getViewContext && view.getViewContext();
const viewContext = view.getViewContext && view.getViewContext();
const row = viewContext && viewContext.row;
if (!row) {
return false;
}
return viewContext && viewContext.formattedValueForCopy
&& typeof viewContext.formattedValueForCopy === 'function';
return row.formattedValueForCopy
&& typeof row.formattedValueForCopy === 'function';
}
}

View File

@ -52,7 +52,8 @@
<script>
export default {
inject: ['openmct'],
name: 'AlphanumericFormat',
inject: ['openmct', 'objectPath'],
data() {
return {
isEditing: this.openmct.editor.isEditing(),

View File

@ -56,6 +56,7 @@
:index="index"
:multi-select="selectedLayoutItems.length > 1"
:is-editing="isEditing"
@contextClick="updateViewContext"
@move="move"
@endMove="endMove"
@endLineResize="endLineResize"
@ -140,7 +141,7 @@ function getItemDefinition(itemType, ...options) {
export default {
components: components,
inject: ['openmct', 'options', 'objectPath'],
inject: ['openmct', 'objectPath', 'options', 'objectUtils', 'currentView'],
props: {
domainObject: {
type: Object,
@ -155,7 +156,8 @@ export default {
return {
initSelectIndex: undefined,
selection: [],
showGrid: true
showGrid: true,
viewContext: {}
};
},
computed: {
@ -819,6 +821,12 @@ export default {
},
toggleGrid() {
this.showGrid = !this.showGrid;
},
updateViewContext(viewContext) {
this.viewContext.row = viewContext;
},
getViewContext() {
return this.viewContext;
}
}
};

View File

@ -102,7 +102,7 @@ export default {
LayoutFrame
},
mixins: [conditionalStylesMixin],
inject: ['openmct', 'objectPath'],
inject: ['openmct', 'objectPath', 'currentView'],
props: {
item: {
type: Object,
@ -294,16 +294,6 @@ export default {
this.requestHistoricalData(this.domainObject);
}
},
getView() {
return {
getViewContext: () => {
return {
viewHistoricalData: true,
formattedValueForCopy: this.formattedValueForCopy
};
}
};
},
setObject(domainObject) {
this.domainObject = domainObject;
this.mutablePromise = undefined;
@ -338,30 +328,38 @@ export default {
this.$emit('formatChanged', this.item, format);
},
updateViewContext() {
this.$emit('contextClick', {
viewHistoricalData: true,
formattedValueForCopy: this.formattedValueForCopy
});
},
async getContextMenuActions() {
const defaultNotebook = getDefaultNotebook();
const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier);
const actionCollection = this.openmct.actions.get(this.currentObjectPath, this.getView());
const actionsObject = actionCollection.getActionsObject();
let copyToNotebookAction = actionsObject.copyToNotebook;
let defaultNotebookName;
if (defaultNotebook) {
const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
copyToNotebookAction.name = `Copy to Notebook ${defaultPath}`;
} else {
actionsObject.copyToNotebook = undefined;
delete actionsObject.copyToNotebook;
defaultNotebookName = `Copy to Notebook ${defaultPath}`;
}
return CONTEXT_MENU_ACTIONS.map(actionKey => {
return actionsObject[actionKey];
}).filter(action => action !== undefined);
return CONTEXT_MENU_ACTIONS
.map(actionKey => {
const action = this.openmct.actions.getAction(actionKey);
if (action.key === 'copyToNotebook') {
action.name = defaultNotebookName;
}
return action;
})
.filter(action => action.name !== undefined);
},
async showContextMenu(event) {
this.updateViewContext();
const contextMenuActions = await this.getContextMenuActions();
this.openmct.menus.showMenu(event.x, event.y, contextMenuActions);
const menuItems = this.openmct.menus.actionsToMenuItems(contextMenuActions, this.currentObjectPath, this.currentView);
this.openmct.menus.showMenu(event.x, event.y, menuItems);
},
setStatus(status) {
this.status = status;

View File

@ -21,6 +21,10 @@
margin-left: $interiorMargin;
}
&__value {
@include isLimit();
}
.c-frame & {
@include abs();
border: 1px solid transparent;

View File

@ -20,13 +20,81 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Layout from './components/DisplayLayout.vue';
import Vue from 'vue';
import objectUtils from 'objectUtils';
import DisplayLayoutType from './DisplayLayoutType.js';
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js';
import CopyToClipboardAction from './actions/CopyToClipboardAction';
import DisplayLayout from './components/DisplayLayout.vue';
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js';
import DisplayLayoutType from './DisplayLayoutType.js';
import objectUtils from 'objectUtils';
import Vue from 'vue';
class DisplayLayoutView {
constructor(openmct, domainObject, objectPath, options) {
this.openmct = openmct;
this.domainObject = domainObject;
this.objectPath = objectPath;
this.options = options;
this.component = undefined;
}
show(container, isEditing) {
this.component = new Vue({
el: container,
components: {
DisplayLayout
},
provide: {
openmct: this.openmct,
objectPath: this.objectPath,
options: this.options,
objectUtils,
currentView: this
},
data: () => {
return {
domainObject: this.domainObject,
isEditing
};
},
template: '<display-layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></display-layout>'
});
}
getViewContext() {
if (!this.component) {
return {};
}
return this.component.$refs.displayLayout.getViewContext();
}
getSelectionContext() {
return {
item: this.domainObject,
supportsMultiSelect: true,
addElement: this.component && this.component.$refs.displayLayout.addElement,
removeItem: this.component && this.component.$refs.displayLayout.removeItem,
orderItem: this.component && this.component.$refs.displayLayout.orderItem,
duplicateItem: this.component && this.component.$refs.displayLayout.duplicateItem,
switchViewType: this.component && this.component.$refs.displayLayout.switchViewType,
mergeMultipleTelemetryViews: this.component && this.component.$refs.displayLayout.mergeMultipleTelemetryViews,
mergeMultipleOverlayPlots: this.component && this.component.$refs.displayLayout.mergeMultipleOverlayPlots,
toggleGrid: this.component && this.component.$refs.displayLayout.toggleGrid
};
}
onEditModeChange(isEditing) {
this.component.isEditing = isEditing;
}
destroy() {
this.component.$destroy();
this.component = undefined;
}
}
export default function DisplayLayoutPlugin(options) {
return function (openmct) {
@ -41,51 +109,7 @@ export default function DisplayLayoutPlugin(options) {
return domainObject.type === 'layout';
},
view: function (domainObject, objectPath) {
let component;
return {
show(container) {
component = new Vue({
el: container,
components: {
Layout
},
provide: {
openmct,
objectUtils,
options,
objectPath
},
data() {
return {
domainObject: domainObject,
isEditing: openmct.editor.isEditing()
};
},
template: '<layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></layout>'
});
},
getSelectionContext() {
return {
item: domainObject,
supportsMultiSelect: true,
addElement: component && component.$refs.displayLayout.addElement,
removeItem: component && component.$refs.displayLayout.removeItem,
orderItem: component && component.$refs.displayLayout.orderItem,
duplicateItem: component && component.$refs.displayLayout.duplicateItem,
switchViewType: component && component.$refs.displayLayout.switchViewType,
mergeMultipleTelemetryViews: component && component.$refs.displayLayout.mergeMultipleTelemetryViews,
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots,
toggleGrid: component && component.$refs.displayLayout.toggleGrid
};
},
onEditModeChange: function (isEditing) {
component.isEditing = isEditing;
},
destroy() {
component.$destroy();
}
};
return new DisplayLayoutView(openmct, domainObject, objectPath, options);
},
priority() {
return 100;

View File

@ -37,7 +37,15 @@ export default class DuplicateAction {
let duplicationTask = new DuplicateTask(this.openmct);
let originalObject = objectPath[0];
let parent = objectPath[1];
let userInput = await this.getUserInput(originalObject, parent);
let userInput;
try {
userInput = await this.getUserInput(originalObject, parent);
} catch (error) {
// user most likely canceled
return;
}
let newParent = userInput.location;
let inNavigationPath = this.inNavigationPath(originalObject);
@ -71,7 +79,8 @@ export default class DuplicateAction {
updateNameCheck(object, name) {
if (object.name !== name) {
this.openmct.objects.mutate(object, 'name', name);
object.name = name;
this.openmct.objects.save(object);
}
}
@ -95,7 +104,7 @@ export default class DuplicateAction {
cssClass: "l-input-lg"
},
{
name: "location",
name: "Location",
cssClass: "grows",
control: "locator",
validate: this.validate(object, parent),

View File

@ -112,7 +112,7 @@ describe("The Duplicate Action plugin", () => {
});
afterEach(() => {
resetApplicationState(openmct);
return resetApplicationState(openmct);
});
it("should be defined", () => {
@ -121,10 +121,9 @@ describe("The Duplicate Action plugin", () => {
describe("when moving an object to a new parent", () => {
beforeEach(async (done) => {
beforeEach(async () => {
duplicateTask = new DuplicateTask(openmct);
await duplicateTask.duplicate(parentObject, anotherParentObject);
done();
});
it("the duplicate child object's name (when not changing) should be the same as the original object", async () => {
@ -143,15 +142,15 @@ describe("The Duplicate Action plugin", () => {
});
describe("when a new name is provided for the duplicated object", () => {
const NEW_NAME = 'New Name';
it("the name is updated", () => {
const NEW_NAME = 'New Name';
let childName;
beforeEach(() => {
duplicateTask = new DuplicateAction(openmct);
duplicateTask.updateNameCheck(parentObject, NEW_NAME);
});
it("the name is updated", () => {
let childName = parentObject.name;
childName = parentObject.name;
expect(childName).toEqual(NEW_NAME);
});
});

View File

@ -97,7 +97,7 @@ function ToolbarProvider(openmct) {
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
buttons: [
{
label: 'Ok',
label: 'OK',
emphasis: 'true',
callback: function () {
deleteFrameAction(primary.context.frameId);
@ -162,7 +162,7 @@ function ToolbarProvider(openmct) {
message: 'This action will permanently delete this container from this Flexible Layout',
buttons: [
{
label: 'Ok',
label: 'OK',
emphasis: 'true',
callback: function () {
removeContainer(containerId);

View File

@ -5,7 +5,7 @@
'is-alias': item.isAlias === true,
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
}, statusClass]"
:href="objectLink"
@click="navigate"
>
<div
class="c-grid-item__type-icon"
@ -49,11 +49,17 @@ import statusListener from './status-listener';
export default {
mixins: [contextMenuGesture, objectLink, statusListener],
inject: ['openmct'],
props: {
item: {
type: Object,
required: true
}
},
methods: {
navigate() {
this.openmct.router.navigate(this.objectLink);
}
}
};
</script>

View File

@ -11,7 +11,7 @@
ref="objectLink"
class="c-object-label"
:class="[statusClass]"
:href="objectLink"
@click="navigate"
>
<div
class="c-object-label__type-icon c-list-item__name__type-icon"
@ -45,6 +45,7 @@ import statusListener from './status-listener';
export default {
mixins: [contextMenuGesture, objectLink, statusListener],
inject: ['openmct'],
props: {
item: {
type: Object,
@ -56,7 +57,7 @@ export default {
return moment(timestamp).format(format);
},
navigate() {
this.$refs.objectLink.click();
this.openmct.router.navigate(this.objectLink);
}
}
};

View File

@ -19,6 +19,15 @@
margin: 0 $interiorMargin $interiorMargin 0;
}
}
body.mobile & {
flex: 1 0 auto;
}
[class*='l-overlay'] & {
// When this view is in an overlay, prevent navigation
pointer-events: none;
}
}
/******************************* GRID ITEMS */

View File

@ -22,4 +22,9 @@
@include isAlias();
}
}
[class*='l-overlay'] & {
// When this view is in an overlay, prevent navigation
pointer-events: none;
}
}

View File

@ -41,7 +41,7 @@ export default class GoToOriginalAction {
.slice(1)
.join('/');
window.location.href = url;
this._openmct.router.navigate(url);
});
}
appliesTo(objectPath) {

View File

@ -24,10 +24,15 @@ import {
resetApplicationState
} from 'utils/testing';
describe("the plugin", () => {
describe("the goToOriginalAction plugin", () => {
let openmct;
let goToFolderAction;
let goToOriginalAction;
let mockRootFolder;
let mockSubFolder;
let mockSubSubFolder;
let mockObject;
let mockObjectPath;
let hash;
beforeEach((done) => {
openmct = createOpenMct();
@ -35,7 +40,7 @@ describe("the plugin", () => {
openmct.on('start', done);
openmct.startHeadless();
goToFolderAction = openmct.actions._allActions.goToOriginal;
goToOriginalAction = openmct.actions._allActions.goToOriginal;
});
afterEach(() => {
@ -43,31 +48,153 @@ describe("the plugin", () => {
});
it('installs the go to folder action', () => {
expect(goToFolderAction).toBeDefined();
expect(goToOriginalAction).toBeDefined();
});
describe('when invoked', () => {
beforeEach(() => {
mockObjectPath = [{
name: 'mock folder',
type: 'folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
}];
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({
identifier: {
namespace: '',
key: 'test'
}
}));
goToFolderAction.invoke(mockObjectPath);
mockRootFolder = getMockObject('mock-root');
mockSubFolder = getMockObject('mock-sub');
mockSubSubFolder = getMockObject('mock-sub-sub');
mockObject = getMockObject('mock-table');
mockObjectPath = [
mockObject,
mockSubSubFolder,
mockSubFolder,
mockRootFolder
];
spyOn(openmct.objects, 'get').and.callFake(identifier => {
const mockedObject = getMockObject(identifier);
return Promise.resolve(mockedObject);
});
spyOn(openmct.router, 'navigate').and.callFake(navigateTo => {
hash = navigateTo;
});
return goToOriginalAction.invoke(mockObjectPath);
});
it('goes to the original location', () => {
expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
const originalLocationHash = '#/browse/mock-root/mock-table';
return waitForNavigation(() => {
return hash === originalLocationHash;
}).then(() => {
expect(hash).toEqual(originalLocationHash);
});
});
});
function waitForNavigation(navigated) {
return new Promise((resolve, reject) => {
const start = Date.now();
checkNavigated();
function checkNavigated() {
const elapsed = Date.now() - start;
if (navigated()) {
resolve();
} else if (elapsed >= jasmine.DEFAULT_TIMEOUT_INTERVAL - 1000) {
reject("didn't navigate in time");
} else {
setTimeout(checkNavigated);
}
}
});
}
function getMockObject(key) {
const id = typeof key === 'string' ? key : key.key;
const mockMCTObjects = {
"ROOT": {
"composition": [
{
"namespace": "",
"key": "mock-root"
}
],
"identifier": {
"namespace": "",
"key": "mock-root"
}
},
"mock-root": {
"composition": [
{
"namespace": "",
"key": "mock-sub"
},
{
"namespace": "",
"key": "mock-table"
}
],
"name": "root",
"type": "folder",
"id": "mock-root",
"location": "ROOT",
"identifier": {
"namespace": "",
"key": "mock-root"
}
},
"mock-sub": {
"composition": [
{
"namespace": "",
"key": "mock-sub-sub"
},
{
"namespace": "",
"key": "mock-table"
}
],
"name": "sub",
"type": "folder",
"location": "mock-root",
"identifier": {
"namespace": "",
"key": "mock-sub"
}
},
"mock-table": {
"composition": [],
"configuration": {
"columnWidths": {},
"hiddenColumns": {}
},
"name": "table",
"type": "table",
"location": "mock-root",
"identifier": {
"namespace": "",
"key": "mock-table"
}
},
"mock-sub-sub": {
"composition": [
{
"namespace": "",
"key": "mock-table"
}
],
"name": "sub sub",
"type": "folder",
"location": "mock-sub",
"identifier": {
"namespace": "",
"key": "mock-sub-sub"
}
}
};
return mockMCTObjects[id];
}
});

Some files were not shown because too many files have changed in this diff Show More