Compare commits
	
		
			3 Commits
		
	
	
		
			custom-col
			...
			sprint-1.7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ab93ca9024 | ||
| 
						 | 
					80451935a9 | ||
| 
						 | 
					783e0fd4f8 | 
@@ -1,69 +1,36 @@
 | 
			
		||||
version: 2.1
 | 
			
		||||
executors:
 | 
			
		||||
  linux:
 | 
			
		||||
    docker:
 | 
			
		||||
      - image: cimg/base:stable
 | 
			
		||||
orbs:
 | 
			
		||||
  node: circleci/node@4.5.1
 | 
			
		||||
  browser-tools: circleci/browser-tools@1.1.3
 | 
			
		||||
version: 2
 | 
			
		||||
jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    parameters:
 | 
			
		||||
      node-version:
 | 
			
		||||
        type: string
 | 
			
		||||
      browser:
 | 
			
		||||
        type: string
 | 
			
		||||
      always-pass:
 | 
			
		||||
        type: boolean  
 | 
			
		||||
    executor: linux
 | 
			
		||||
  build:
 | 
			
		||||
    docker:
 | 
			
		||||
        - image: circleci/node:13-browsers
 | 
			
		||||
          environment:
 | 
			
		||||
            CHROME_BIN: "/usr/bin/google-chrome"
 | 
			
		||||
    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/
 | 
			
		||||
        - 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:
 | 
			
		||||
  matrix-tests:
 | 
			
		||||
  version: 2
 | 
			
		||||
  test:
 | 
			
		||||
    jobs:
 | 
			
		||||
      - 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      - build
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										44
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -1,44 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
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
									
									
								
							
							
						
						
									
										5
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +0,0 @@
 | 
			
		||||
blank_issues_enabled: true
 | 
			
		||||
contact_links:
 | 
			
		||||
  - name: Discussions
 | 
			
		||||
    url: https://github.com/nasa/openmct/discussions
 | 
			
		||||
    about: Got a question?
 | 
			
		||||
							
								
								
									
										20
									
								
								.github/ISSUE_TEMPLATE/enhancement-request.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/ISSUE_TEMPLATE/enhancement-request.md
									
									
									
									
										vendored
									
									
								
							@@ -1,20 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
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.
 | 
			
		||||
							
								
								
									
										23
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							@@ -1,23 +0,0 @@
 | 
			
		||||
<!--- 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
									
									
								
							
							
						
						
									
										12
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -1,12 +0,0 @@
 | 
			
		||||
### 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
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/lighthouse.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,20 +0,0 @@
 | 
			
		||||
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
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -40,7 +40,4 @@ npm-debug.log
 | 
			
		||||
# karma reports
 | 
			
		||||
report.*.json
 | 
			
		||||
 | 
			
		||||
# Lighthouse reports
 | 
			
		||||
.lighthouseci
 | 
			
		||||
 | 
			
		||||
package-lock.json
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								API.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								API.md
									
									
									
									
									
								
							@@ -595,17 +595,9 @@ section.
 | 
			
		||||
 | 
			
		||||
#### Limit Evaluators **draft**
 | 
			
		||||
 | 
			
		||||
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`.
 | 
			
		||||
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`.
 | 
			
		||||
 | 
			
		||||
### Telemetry Consumer APIs **draft**
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ accept changes from external contributors.
 | 
			
		||||
 | 
			
		||||
The short version:
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
1. Write your contribution.
 | 
			
		||||
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,7 +18,6 @@ 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
 | 
			
		||||
 | 
			
		||||
@@ -116,7 +115,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 request’s __author__. If no issue exists, [create one](https://github.com/nasa/openmct/issues/new/choose).
 | 
			
		||||
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull request’s __author__. If no issue exists, create one.
 | 
			
		||||
* 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 request’s __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.
 | 
			
		||||
@@ -297,12 +296,23 @@ 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." 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.
 | 
			
		||||
* _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.
 | 
			
		||||
 | 
			
		||||
## Check Lists
 | 
			
		||||
 | 
			
		||||
@@ -312,19 +322,16 @@ checklist).
 | 
			
		||||
 | 
			
		||||
### Author Checklist
 | 
			
		||||
 | 
			
		||||
[Within PR Template](.github/PULL_REQUEST_TEMPLATE.md)
 | 
			
		||||
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?
 | 
			
		||||
 | 
			
		||||
### Reviewer Checklist
 | 
			
		||||
 | 
			
		||||
* [ ] 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.```
 | 
			
		||||
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)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,9 @@
 | 
			
		||||
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0) [](https://lgtm.com/projects/g/nasa/openmct/context:javascript)
 | 
			
		||||
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0)
 | 
			
		||||
 | 
			
		||||
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/).
 | 
			
		||||
@@ -46,7 +44,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/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).
 | 
			
		||||
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).
 | 
			
		||||
 | 
			
		||||
## Building Applications With Open MCT
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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  and sprint branch        |
 | 
			
		||||
| __3__ | Per-sprint testing        | Triage |     | _Per-sprint testing*_        | Ship and merge sprint branch to master|
 | 
			
		||||
| 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        |
 | 
			
		||||
 | 
			
		||||
* If necessary.
 | 
			
		||||
 | 
			
		||||
@@ -105,20 +105,14 @@ 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, 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.
 | 
			
		||||
  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.
 | 
			
		||||
* [__Per-release Testing.__](testing/plan.md#per-release-testing)
 | 
			
		||||
  Structured testing with predefined
 | 
			
		||||
  success criteria. No release should ship without passing
 | 
			
		||||
@@ -132,8 +126,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 using the sprint branch, test 
 | 
			
		||||
    exploratorily for regressions, et cetera.
 | 
			
		||||
    completed work from this sprint, 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
 | 
			
		||||
@@ -149,7 +143,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. Smoke tests collected from issues/PRs
 | 
			
		||||
  Pre-release Testing if the first round is not passed.
 | 
			
		||||
* __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
 | 
			
		||||
 
 | 
			
		||||
@@ -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. The outcome of Smoke Testing should be a simplified list of Acceptance Tests which could be executed by another team member with sufficient context.
 | 
			
		||||
is present.
 | 
			
		||||
 | 
			
		||||
### 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/openmct/issues/new/choose)
 | 
			
		||||
[report issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
 | 
			
		||||
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/openmct/issues/new/choose)
 | 
			
		||||
[reported as issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
 | 
			
		||||
and reviewed for severity.
 | 
			
		||||
 | 
			
		||||
## Test Performance
 | 
			
		||||
 
 | 
			
		||||
@@ -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. Checkout branch created for the last sprint that has been successfully tested.
 | 
			
		||||
  2. Remove a `-SNAPSHOT` suffix from the version in `package.json`.
 | 
			
		||||
  1. Create a new branch off the `master` branch.
 | 
			
		||||
  2. Remove `-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,8 +131,7 @@ 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`) 
 | 
			
		||||
     NOTE: Use the `--tag unstable` flag to the npm publishj if this is a prerelease.
 | 
			
		||||
  5. Publish the package to the npmjs registry (e.g. `npm publish --access public`)
 | 
			
		||||
  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.
 | 
			
		||||
 
 | 
			
		||||
@@ -26,26 +26,14 @@ define([
 | 
			
		||||
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    var PURPLE = {
 | 
			
		||||
            sin: 2.2,
 | 
			
		||||
            cos: 2.2
 | 
			
		||||
        },
 | 
			
		||||
        RED = {
 | 
			
		||||
    var 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",
 | 
			
		||||
@@ -105,70 +93,5 @@ 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;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ define([
 | 
			
		||||
 | 
			
		||||
    StateGeneratorProvider.prototype.request = function (domainObject, options) {
 | 
			
		||||
        var start = options.start;
 | 
			
		||||
        var end = Math.min(Date.now(), options.end); // no future values
 | 
			
		||||
        var end = options.end;
 | 
			
		||||
        var duration = domainObject.telemetry.duration * 1000;
 | 
			
		||||
        if (options.strategy === 'latest' || options.size === 1) {
 | 
			
		||||
            start = end;
 | 
			
		||||
 
 | 
			
		||||
@@ -49,10 +49,6 @@ 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('/');
 | 
			
		||||
@@ -63,9 +59,6 @@ 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
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Vue from 'Vue';
 | 
			
		||||
import HelloWorld from './HelloWorld.vue';
 | 
			
		||||
 | 
			
		||||
function SimpleVuePlugin() {
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,6 @@
 | 
			
		||||
        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"
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,9 @@
 | 
			
		||||
/*global module,process*/
 | 
			
		||||
 | 
			
		||||
const devMode = process.env.NODE_ENV !== 'production';
 | 
			
		||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
 | 
			
		||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'FirefoxHeadless'];
 | 
			
		||||
const coverageEnabled = process.env.COVERAGE === 'true';
 | 
			
		||||
const reporters = ['progress', 'html', 'junit'];
 | 
			
		||||
const reporters = ['progress', 'html'];
 | 
			
		||||
 | 
			
		||||
if (coverageEnabled) {
 | 
			
		||||
    reporters.push('coverage-istanbul');
 | 
			
		||||
@@ -59,8 +59,7 @@ module.exports = (config) => {
 | 
			
		||||
        browsers: browsers,
 | 
			
		||||
        client: {
 | 
			
		||||
            jasmine: {
 | 
			
		||||
                random: false,
 | 
			
		||||
                timeoutInterval: 30000
 | 
			
		||||
                random: false
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        customLaunchers: {
 | 
			
		||||
@@ -68,10 +67,6 @@ module.exports = (config) => {
 | 
			
		||||
                base: 'Chrome',
 | 
			
		||||
                flags: ['--remote-debugging-port=9222'],
 | 
			
		||||
                debug: true
 | 
			
		||||
            },
 | 
			
		||||
            FirefoxESR: {
 | 
			
		||||
                base: 'FirefoxHeadless',
 | 
			
		||||
                name: 'FirefoxESR'
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        colors: true,
 | 
			
		||||
@@ -83,21 +78,11 @@ 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: {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,96 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
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'
 | 
			
		||||
							
								
								
									
										24
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								package.json
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "openmct",
 | 
			
		||||
  "version": "1.7.6-SNAPSHOT",
 | 
			
		||||
  "version": "1.7.3-SNAPSHOT",
 | 
			
		||||
  "description": "The Open MCT core platform",
 | 
			
		||||
  "dependencies": {},
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
@@ -34,21 +34,20 @@
 | 
			
		||||
    "git-rev-sync": "^1.4.0",
 | 
			
		||||
    "glob": ">= 3.0.0",
 | 
			
		||||
    "html-loader": "^0.5.5",
 | 
			
		||||
    "html2canvas": "^1.0.0-rc.7",
 | 
			
		||||
    "html2canvas": "^1.0.0-alpha.12",
 | 
			
		||||
    "imports-loader": "^0.8.0",
 | 
			
		||||
    "istanbul-instrumenter-loader": "^3.0.1",
 | 
			
		||||
    "jasmine-core": "^3.7.1",
 | 
			
		||||
    "jasmine-core": "^3.1.0",
 | 
			
		||||
    "jsdoc": "^3.3.2",
 | 
			
		||||
    "karma": "6.3.4",
 | 
			
		||||
    "karma": "5.1.1",
 | 
			
		||||
    "karma-chrome-launcher": "3.1.0",
 | 
			
		||||
    "karma-firefox-launcher": "2.1.1",
 | 
			
		||||
    "karma-firefox-launcher": "1.3.0",
 | 
			
		||||
    "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": "4.0.1",
 | 
			
		||||
    "karma-sourcemap-loader": "0.3.8",
 | 
			
		||||
    "karma-jasmine": "3.3.1",
 | 
			
		||||
    "karma-sourcemap-loader": "0.3.7",
 | 
			
		||||
    "karma-webpack": "4.0.2",
 | 
			
		||||
    "location-bar": "^3.0.1",
 | 
			
		||||
    "lodash": "^4.17.12",
 | 
			
		||||
@@ -61,7 +60,7 @@
 | 
			
		||||
    "moment-timezone": "0.5.28",
 | 
			
		||||
    "node-bourbon": "^4.2.3",
 | 
			
		||||
    "node-sass": "^4.14.1",
 | 
			
		||||
    "painterro": "^1.2.56",
 | 
			
		||||
    "painterro": "^1.0.35",
 | 
			
		||||
    "printj": "^1.2.1",
 | 
			
		||||
    "raw-loader": "^0.5.1",
 | 
			
		||||
    "request": "^2.69.0",
 | 
			
		||||
@@ -79,8 +78,7 @@
 | 
			
		||||
    "zepto": "^1.2.0"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "clean": "rm -rf ./dist /node_modules; rm package-lock.json",
 | 
			
		||||
    "clean-test-lint": "npm run clean; npm install ; npm run test; npm run lint",
 | 
			
		||||
    "clean": "rm -rf ./dist",
 | 
			
		||||
    "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",
 | 
			
		||||
@@ -90,7 +88,6 @@
 | 
			
		||||
    "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",
 | 
			
		||||
@@ -102,9 +99,6 @@
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "https://github.com/nasa/openmct.git"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": ">=10.10.2 <16.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "",
 | 
			
		||||
  "license": "Apache-2.0",
 | 
			
		||||
  "private": true
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ 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",
 | 
			
		||||
@@ -36,6 +37,7 @@ define([
 | 
			
		||||
    NavigationService,
 | 
			
		||||
    NavigateAction,
 | 
			
		||||
    OrphanNavigationHandler,
 | 
			
		||||
    NewTabAction,
 | 
			
		||||
    browseTemplate,
 | 
			
		||||
    browseObjectTemplate,
 | 
			
		||||
    objectHeaderTemplate,
 | 
			
		||||
@@ -126,6 +128,23 @@ 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": [
 | 
			
		||||
 
 | 
			
		||||
@@ -20,40 +20,39 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import HyperlinkLayout from './HyperlinkLayout.vue';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
/**
 | 
			
		||||
 * Module defining NewTabAction (Originally NewWindowAction). Created by vwoeltje on 11/18/14.
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
        /**
 | 
			
		||||
         * The new tab action allows a domain object to be opened
 | 
			
		||||
         * into a new browser tab.
 | 
			
		||||
         * @memberof platform/commonUI/browse
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @implements {Action}
 | 
			
		||||
         */
 | 
			
		||||
        function NewTabAction(urlService, $window, context) {
 | 
			
		||||
            context = context || {};
 | 
			
		||||
 | 
			
		||||
export default function HyperlinkProvider(openmct) {
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        key: 'hyperlink.view',
 | 
			
		||||
        name: 'Hyperlink',
 | 
			
		||||
        cssClass: 'icon-chain-links',
 | 
			
		||||
        canView(domainObject) {
 | 
			
		||||
            return domainObject.type === 'hyperlink';
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        view: function (domainObject) {
 | 
			
		||||
            let component;
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                show: function (element) {
 | 
			
		||||
                    component = new Vue({
 | 
			
		||||
                        el: element,
 | 
			
		||||
                        components: {
 | 
			
		||||
                            HyperlinkLayout
 | 
			
		||||
                        },
 | 
			
		||||
                        provide: {
 | 
			
		||||
                            domainObject
 | 
			
		||||
                        },
 | 
			
		||||
                        template: '<hyperlink-layout></hyperlink-layout>'
 | 
			
		||||
                    });
 | 
			
		||||
                },
 | 
			
		||||
                destroy: function () {
 | 
			
		||||
                    component.$destroy();
 | 
			
		||||
                    component = undefined;
 | 
			
		||||
                }
 | 
			
		||||
            this.urlService = urlService;
 | 
			
		||||
            this.open = function () {
 | 
			
		||||
                arguments[0] += "&hideTree=true&hideInspector=true";
 | 
			
		||||
                $window.open.apply($window, arguments);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // Choose the object to be opened into a new tab
 | 
			
		||||
            this.domainObject = context.selectedObject || context.domainObject;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        NewTabAction.prototype.perform = function () {
 | 
			
		||||
            this.open(
 | 
			
		||||
                this.urlService.urlForNewTab("browse", this.domainObject),
 | 
			
		||||
                "_blank"
 | 
			
		||||
            );
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return NewTabAction;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										75
									
								
								platform/commonUI/browse/test/windowing/NewTabActionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								platform/commonUI/browse/test/windowing/NewTabActionSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -86,7 +86,7 @@ define(
 | 
			
		||||
                        })
 | 
			
		||||
                        .join('/');
 | 
			
		||||
 | 
			
		||||
                openmct.router.navigate(url);
 | 
			
		||||
                window.location.href = url;
 | 
			
		||||
 | 
			
		||||
                if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
 | 
			
		||||
                    openmct.editor.edit();
 | 
			
		||||
 
 | 
			
		||||
@@ -23,11 +23,13 @@
 | 
			
		||||
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",
 | 
			
		||||
@@ -37,11 +39,13 @@ define([
 | 
			
		||||
], function (
 | 
			
		||||
    MomentTimezone,
 | 
			
		||||
    ClockIndicator,
 | 
			
		||||
    FollowIndicator,
 | 
			
		||||
    TickerService,
 | 
			
		||||
    TimerService,
 | 
			
		||||
    ClockController,
 | 
			
		||||
    TimerController,
 | 
			
		||||
    RefreshingController,
 | 
			
		||||
    FollowTimerAction,
 | 
			
		||||
    StartTimerAction,
 | 
			
		||||
    RestartTimerAction,
 | 
			
		||||
    StopTimerAction,
 | 
			
		||||
@@ -140,6 +144,15 @@ define([
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "actions": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer.follow",
 | 
			
		||||
                        "implementation": FollowTimerAction,
 | 
			
		||||
                        "depends": ["timerService"],
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "name": "Follow Timer",
 | 
			
		||||
                        "cssClass": "icon-clock",
 | 
			
		||||
                        "priority": "optional"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer.start",
 | 
			
		||||
                        "implementation": StartTimerAction,
 | 
			
		||||
@@ -181,7 +194,7 @@ define([
 | 
			
		||||
                        ],
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "name": "Stop",
 | 
			
		||||
                        "cssClass": "icon-box-round-corners",
 | 
			
		||||
                        "cssClass": "icon-box",
 | 
			
		||||
                        "priority": "preferred"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
@@ -286,7 +299,10 @@ define([
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "runs": [],
 | 
			
		||||
                "runs": [{
 | 
			
		||||
                    "implementation": FollowIndicator,
 | 
			
		||||
                    "depends": ["openmct", "timerService"]
 | 
			
		||||
                }],
 | 
			
		||||
                "licenses": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "moment-duration-format",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
@@ -19,20 +19,38 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import objectPathToUrl from '/src/tools/url';
 | 
			
		||||
export default class OpenInNewTab {
 | 
			
		||||
    constructor(openmct) {
 | 
			
		||||
        this.name = 'Open In New Tab';
 | 
			
		||||
        this.key = 'newTab';
 | 
			
		||||
        this.description = 'Open in a new browser tab';
 | 
			
		||||
        this.group = "windowing";
 | 
			
		||||
        this.priority = 10;
 | 
			
		||||
        this.cssClass = "icon-new-window";
 | 
			
		||||
 | 
			
		||||
        this._openmct = openmct;
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FollowTimerAction.appliesTo = function (context) {
 | 
			
		||||
            var model =
 | 
			
		||||
                (context.domainObject && context.domainObject.getModel())
 | 
			
		||||
                || {};
 | 
			
		||||
 | 
			
		||||
            return model.type === 'timer';
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return FollowTimerAction;
 | 
			
		||||
    }
 | 
			
		||||
    invoke(objectPath) {
 | 
			
		||||
        let url = objectPathToUrl(this._openmct, objectPath);
 | 
			
		||||
        window.open(url);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
);
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-2018, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
@@ -20,40 +20,32 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Module defining url handling.
 | 
			
		||||
 */
 | 
			
		||||
define([], function () {
 | 
			
		||||
 | 
			
		||||
export function paramsToArray(openmct) {
 | 
			
		||||
    // parse urParams from an object to an array.
 | 
			
		||||
    let urlParams = openmct.router.getParams();
 | 
			
		||||
    let newTabParams = [];
 | 
			
		||||
    for (let key in urlParams) {
 | 
			
		||||
        if ({}.hasOwnProperty.call(urlParams, key)) {
 | 
			
		||||
            let param = `${key}=${urlParams[key]}`;
 | 
			
		||||
            newTabParams.push(param);
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicator that displays the active timer, as well as its
 | 
			
		||||
     * current state.
 | 
			
		||||
     * @memberof platform/features/clock
 | 
			
		||||
     */
 | 
			
		||||
    return function installFollowIndicator(openmct, timerService) {
 | 
			
		||||
        var indicator = openmct.indicators.simpleIndicator();
 | 
			
		||||
        var timer = timerService.getTimer();
 | 
			
		||||
        setIndicatorStatus(timer);
 | 
			
		||||
 | 
			
		||||
        function setIndicatorStatus(newTimer) {
 | 
			
		||||
            if (newTimer !== undefined) {
 | 
			
		||||
                indicator.iconClass('icon-timer');
 | 
			
		||||
                indicator.statusClass('s-status-on');
 | 
			
		||||
                indicator.text('Following timer ' + newTimer.name);
 | 
			
		||||
            } else {
 | 
			
		||||
                indicator.iconClass('icon-timer');
 | 
			
		||||
                indicator.statusClass('s-status-disabled');
 | 
			
		||||
                indicator.text('No timer being followed');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return newTabParams;
 | 
			
		||||
}
 | 
			
		||||
        timerService.on('change', setIndicatorStatus);
 | 
			
		||||
 | 
			
		||||
export function identifierToString(openmct, objectPath) {
 | 
			
		||||
    let identifier = '#/browse/' + objectPath.map(function (o) {
 | 
			
		||||
        return o && openmct.objects.makeKeyString(o.identifier);
 | 
			
		||||
    })
 | 
			
		||||
        .reverse()
 | 
			
		||||
        .join('/');
 | 
			
		||||
 | 
			
		||||
    return identifier;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function objectPathToUrl(openmct, objectPath) {
 | 
			
		||||
    let url = identifierToString(openmct, objectPath);
 | 
			
		||||
    let urlParams = paramsToArray(openmct);
 | 
			
		||||
    if (urlParams.length) {
 | 
			
		||||
        url += '?' + urlParams.join('&');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return url;
 | 
			
		||||
}
 | 
			
		||||
        openmct.indicators.add(indicator);
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,89 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -101,7 +101,7 @@ define(
 | 
			
		||||
                    name: "Pause"
 | 
			
		||||
                });
 | 
			
		||||
                mockStop.getMetadata.and.returnValue({
 | 
			
		||||
                    cssClass: "icon-box-round-corners",
 | 
			
		||||
                    cssClass: "icon-box",
 | 
			
		||||
                    name: "Stop"
 | 
			
		||||
                });
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,96 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										120
									
								
								platform/features/hyperlink/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								platform/features/hyperlink/bundle.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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"]
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2020, 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.
 | 
			
		||||
 | 
			
		||||
@@ -19,34 +19,10 @@
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<li class="c-inspect-properties__row">
 | 
			
		||||
    <div class="c-inspect-properties__label">
 | 
			
		||||
        {{ label }}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="c-inspect-properties__value">
 | 
			
		||||
        {{ value }}
 | 
			
		||||
    </div>
 | 
			
		||||
</li>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        label: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            default() {
 | 
			
		||||
                return '';
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        value: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            default() {
 | 
			
		||||
                return '';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<a class="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
 | 
			
		||||
   ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
 | 
			
		||||
   ng-class="{
 | 
			
		||||
   'c-hyperlink--button u-fills-container' : hyperlink.isButton(),
 | 
			
		||||
   'c-hyperlink--link' : !hyperlink.isButton() }">
 | 
			
		||||
    <span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span>
 | 
			
		||||
</a>
 | 
			
		||||
							
								
								
									
										61
									
								
								platform/features/hyperlink/src/HyperlinkController.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								platform/features/hyperlink/src/HyperlinkController.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										89
									
								
								platform/features/hyperlink/test/HyperlinkControllerSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								platform/features/hyperlink/test/HyperlinkControllerSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										70
									
								
								platform/features/timeline/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								platform/features/timeline/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
@@ -20,10 +20,33 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import ViewLargeAction from './viewLargeAction.js';
 | 
			
		||||
 | 
			
		||||
export default function plugin() {
 | 
			
		||||
    return function install(openmct) {
 | 
			
		||||
        openmct.actions.register(new ViewLargeAction(openmct));
 | 
			
		||||
define([
 | 
			
		||||
    "./res/templates/deprecated-timeline-message.html"
 | 
			
		||||
], function (
 | 
			
		||||
    deprecatedTimelineMessage
 | 
			
		||||
) {
 | 
			
		||||
    return {
 | 
			
		||||
        name: 'platform/features/timeline',
 | 
			
		||||
        definition: {
 | 
			
		||||
            extensions: {
 | 
			
		||||
                types: [
 | 
			
		||||
                    {
 | 
			
		||||
                        key: "timeline",
 | 
			
		||||
                        name: "Timeline",
 | 
			
		||||
                        description: "Timeline, Activity and Activity Mode objects have been deprecated and will no longer be supported. (07/18/2018)",
 | 
			
		||||
                        priority: 502
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                views: [
 | 
			
		||||
                    {
 | 
			
		||||
                        key: "timeline",
 | 
			
		||||
                        name: "Timeline",
 | 
			
		||||
                        type: "timeline",
 | 
			
		||||
                        description: "Timeline, Activity and Activity Mode objects have been deprecated and will no longer be supported. (07/18/2018)",
 | 
			
		||||
                        template: deprecatedTimelineMessage
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
<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>
 | 
			
		||||
							
								
								
									
										10
									
								
								src/MCT.js
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/MCT.js
									
									
									
									
									
								
							@@ -122,7 +122,6 @@ define([
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.destroy = this.destroy.bind(this);
 | 
			
		||||
        /**
 | 
			
		||||
         * Tracks current selection state of the application.
 | 
			
		||||
         * @private
 | 
			
		||||
@@ -253,7 +252,7 @@ define([
 | 
			
		||||
 | 
			
		||||
        this.status = new api.StatusAPI(this);
 | 
			
		||||
 | 
			
		||||
        this.router = new ApplicationRouter(this);
 | 
			
		||||
        this.router = new ApplicationRouter();
 | 
			
		||||
 | 
			
		||||
        this.branding = BrandingAPI.default;
 | 
			
		||||
 | 
			
		||||
@@ -263,7 +262,7 @@ define([
 | 
			
		||||
        // Plugins that are installed by default
 | 
			
		||||
 | 
			
		||||
        this.install(this.plugins.Plot());
 | 
			
		||||
        this.install(this.plugins.TelemetryTable.default());
 | 
			
		||||
        this.install(this.plugins.TelemetryTable());
 | 
			
		||||
        this.install(PreviewPlugin.default());
 | 
			
		||||
        this.install(LegacyIndicatorsPlugin());
 | 
			
		||||
        this.install(LicensesPlugin.default());
 | 
			
		||||
@@ -275,7 +274,6 @@ 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());
 | 
			
		||||
@@ -284,7 +282,6 @@ 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());
 | 
			
		||||
    }
 | 
			
		||||
@@ -436,8 +433,6 @@ define([
 | 
			
		||||
                    Browse(this);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                window.addEventListener('beforeunload', this.destroy);
 | 
			
		||||
 | 
			
		||||
                this.router.start();
 | 
			
		||||
                this.emit('start');
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
@@ -461,7 +456,6 @@ define([
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    MCT.prototype.destroy = function () {
 | 
			
		||||
        window.removeEventListener('beforeunload', this.destroy);
 | 
			
		||||
        this.emit('destroy');
 | 
			
		||||
        this.router.destroy();
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,8 @@ define([
 | 
			
		||||
    './views/installLegacyViews',
 | 
			
		||||
    './policies/LegacyCompositionPolicyAdapter',
 | 
			
		||||
    './actions/LegacyActionAdapter',
 | 
			
		||||
    './services/LegacyPersistenceAdapter'
 | 
			
		||||
    './services/LegacyPersistenceAdapter',
 | 
			
		||||
    './services/ExportImageService'
 | 
			
		||||
], function (
 | 
			
		||||
    ActionDialogDecorator,
 | 
			
		||||
    AdapterCapability,
 | 
			
		||||
@@ -53,7 +54,8 @@ define([
 | 
			
		||||
    installLegacyViews,
 | 
			
		||||
    legacyCompositionPolicyAdapter,
 | 
			
		||||
    LegacyActionAdapter,
 | 
			
		||||
    LegacyPersistenceAdapter
 | 
			
		||||
    LegacyPersistenceAdapter,
 | 
			
		||||
    ExportImageService
 | 
			
		||||
) {
 | 
			
		||||
    return {
 | 
			
		||||
        name: 'src/adapter',
 | 
			
		||||
@@ -82,6 +84,13 @@ define([
 | 
			
		||||
                            "identifierService",
 | 
			
		||||
                            "cacheService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "exportImageService",
 | 
			
		||||
                        "implementation": ExportImageService,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "dialogService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                components: [
 | 
			
		||||
 
 | 
			
		||||
@@ -161,23 +161,6 @@ 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());
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										186
									
								
								src/adapter/services/ExportImageService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/adapter/services/ExportImageService.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,186 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -46,6 +46,8 @@ class ActionCollection extends EventEmitter {
 | 
			
		||||
            this._observeObjectPath();
 | 
			
		||||
            this.openmct.editor.on('isEditing', this._updateActions);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._initializeActions();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    disable(actionKeys) {
 | 
			
		||||
@@ -154,10 +156,19 @@ 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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -119,8 +119,7 @@ describe('The ActionCollection', () => {
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        actionCollection.destroy();
 | 
			
		||||
 | 
			
		||||
        return resetApplicationState(openmct);
 | 
			
		||||
        resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe("disable method invoked with action keys", () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ class ActionsAPI extends EventEmitter {
 | 
			
		||||
        this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json'];
 | 
			
		||||
 | 
			
		||||
        this.register = this.register.bind(this);
 | 
			
		||||
        this.getActionsCollection = this.getActionsCollection.bind(this);
 | 
			
		||||
        this.get = this.get.bind(this);
 | 
			
		||||
        this._applicableActions = this._applicableActions.bind(this);
 | 
			
		||||
        this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
@@ -43,14 +43,12 @@ class ActionsAPI extends EventEmitter {
 | 
			
		||||
        this._allActions[actionDefinition.key] = actionDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getAction(key) {
 | 
			
		||||
        return this._allActions[key];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getActionsCollection(objectPath, view) {
 | 
			
		||||
    get(objectPath, view) {
 | 
			
		||||
        if (view) {
 | 
			
		||||
 | 
			
		||||
            return this._getCachedActionCollection(objectPath, view) || this._newActionCollection(objectPath, view, true);
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
            return this._newActionCollection(objectPath, view, true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -59,6 +57,15 @@ 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);
 | 
			
		||||
 | 
			
		||||
@@ -68,17 +75,7 @@ class ActionsAPI extends EventEmitter {
 | 
			
		||||
    _newActionCollection(objectPath, view, skipEnvironmentObservers) {
 | 
			
		||||
        let applicableActions = this._applicableActions(objectPath, view);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
        return new ActionCollection(applicableActions, objectPath, view, this._openmct, skipEnvironmentObservers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _updateCachedActionCollections(key) {
 | 
			
		||||
 
 | 
			
		||||
@@ -99,14 +99,14 @@ describe('The Actions API', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        return resetApplicationState(openmct);
 | 
			
		||||
        resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe("register method", () => {
 | 
			
		||||
        it("adds action to ActionsAPI", () => {
 | 
			
		||||
            actionsAPI.register(mockAction);
 | 
			
		||||
 | 
			
		||||
            let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let actionCollection = actionsAPI.get(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.getActionsCollection(mockObjectPath);
 | 
			
		||||
            let actionCollection = actionsAPI.get(mockObjectPath);
 | 
			
		||||
            let instanceOfActionCollection = actionCollection instanceof ActionCollection;
 | 
			
		||||
 | 
			
		||||
            expect(instanceOfActionCollection).toBeTrue();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("returns an ActionCollection when invoked with an objectPath and view", () => {
 | 
			
		||||
            let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let instanceOfActionCollection = actionCollection instanceof ActionCollection;
 | 
			
		||||
 | 
			
		||||
            expect(instanceOfActionCollection).toBeTrue();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("returns relevant actions when invoked with objectPath only", () => {
 | 
			
		||||
            let actionCollection = actionsAPI.getActionsCollection(mockObjectPath);
 | 
			
		||||
            let actionCollection = actionsAPI.get(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.getActionsCollection(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let action = actionCollection.getActionsObject()[mockAction.key];
 | 
			
		||||
 | 
			
		||||
            expect(action.key).toEqual(mockAction.key);
 | 
			
		||||
 
 | 
			
		||||
@@ -215,12 +215,12 @@ define([
 | 
			
		||||
     * @memberof {module:openmct.CompositionCollection#}
 | 
			
		||||
     * @name load
 | 
			
		||||
     */
 | 
			
		||||
    CompositionCollection.prototype.load = function (abortSignal) {
 | 
			
		||||
    CompositionCollection.prototype.load = function () {
 | 
			
		||||
        this.cleanUpMutables();
 | 
			
		||||
 | 
			
		||||
        return this.provider.load(this.domainObject)
 | 
			
		||||
            .then(function (children) {
 | 
			
		||||
                return Promise.all(children.map((c) => this.publicAPI.objects.get(c, abortSignal)));
 | 
			
		||||
                return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
 | 
			
		||||
            }.bind(this))
 | 
			
		||||
            .then(function (childObjects) {
 | 
			
		||||
                childObjects.forEach(c => this.add(c, true));
 | 
			
		||||
 
 | 
			
		||||
@@ -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} onItemClicked callback function: invoked when item is clicked
 | 
			
		||||
 * @property {Function} callBack callback function: invoked when item is clicked
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -66,27 +66,12 @@ 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, items, menuOptions) {
 | 
			
		||||
        this._createMenuComponent(x, y, items, menuOptions);
 | 
			
		||||
    showMenu(x, y, actions, menuOptions) {
 | 
			
		||||
        this._createMenuComponent(x, y, actions, 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
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,7 @@ describe ('The Menu API', () => {
 | 
			
		||||
                name: 'Test Action 1',
 | 
			
		||||
                cssClass: 'icon-clock',
 | 
			
		||||
                description: 'This is a test action',
 | 
			
		||||
                onItemClicked: () => {
 | 
			
		||||
                callBack: () => {
 | 
			
		||||
                    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',
 | 
			
		||||
                onItemClicked: () => {
 | 
			
		||||
                callBack: () => {
 | 
			
		||||
                    result = 'Test Action 2 Invoked';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -76,7 +76,7 @@ describe ('The Menu API', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        return resetApplicationState(openmct);
 | 
			
		||||
        resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe("showMenu method", () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
                :key="action.name"
 | 
			
		||||
                :class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
 | 
			
		||||
                :title="action.description"
 | 
			
		||||
                @click="action.onItemClicked"
 | 
			
		||||
                @click="action.callBack"
 | 
			
		||||
            >
 | 
			
		||||
                {{ action.name }}
 | 
			
		||||
            </li>
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
            :key="action.name"
 | 
			
		||||
            :class="action.cssClass"
 | 
			
		||||
            :title="action.description"
 | 
			
		||||
            @click="action.onItemClicked"
 | 
			
		||||
            @click="action.callBack"
 | 
			
		||||
        >
 | 
			
		||||
            {{ action.name }}
 | 
			
		||||
        </li>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
                :key="action.name"
 | 
			
		||||
                :class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
 | 
			
		||||
                :title="action.description"
 | 
			
		||||
                @click="action.onItemClicked"
 | 
			
		||||
                @click="action.callBack"
 | 
			
		||||
                @mouseover="toggleItemDescription(action)"
 | 
			
		||||
                @mouseleave="toggleItemDescription()"
 | 
			
		||||
            >
 | 
			
		||||
@@ -42,7 +42,7 @@
 | 
			
		||||
            :key="action.name"
 | 
			
		||||
            :class="action.cssClass"
 | 
			
		||||
            :title="action.description"
 | 
			
		||||
            @click="action.onItemClicked"
 | 
			
		||||
            @click="action.callBack"
 | 
			
		||||
            @mouseover="toggleItemDescription(action)"
 | 
			
		||||
            @mouseleave="toggleItemDescription()"
 | 
			
		||||
        >
 | 
			
		||||
 
 | 
			
		||||
@@ -71,12 +71,12 @@ class Menu extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    showMenu() {
 | 
			
		||||
        this.component = new Vue({
 | 
			
		||||
            components: {
 | 
			
		||||
                MenuComponent
 | 
			
		||||
            },
 | 
			
		||||
            provide: {
 | 
			
		||||
                options: this.options
 | 
			
		||||
            },
 | 
			
		||||
            components: {
 | 
			
		||||
                MenuComponent
 | 
			
		||||
            },
 | 
			
		||||
            template: '<menu-component />'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -85,12 +85,12 @@ class Menu extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    showSuperMenu() {
 | 
			
		||||
        this.component = new Vue({
 | 
			
		||||
            components: {
 | 
			
		||||
                SuperMenuComponent
 | 
			
		||||
            },
 | 
			
		||||
            provide: {
 | 
			
		||||
                options: this.options
 | 
			
		||||
            },
 | 
			
		||||
            components: {
 | 
			
		||||
                SuperMenuComponent
 | 
			
		||||
            },
 | 
			
		||||
            template: '<super-menu-component />'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -45,8 +45,6 @@ function ObjectAPI(typeRegistry, openmct) {
 | 
			
		||||
    this.rootProvider = new RootObjectProvider(this.rootRegistry);
 | 
			
		||||
    this.cache = {};
 | 
			
		||||
    this.interceptorRegistry = new InterceptorRegistry();
 | 
			
		||||
 | 
			
		||||
    this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan'];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -399,25 +397,20 @@ 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
 | 
			
		||||
            && 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();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    if (provider !== undefined
 | 
			
		||||
        && provider.observe !== undefined) {
 | 
			
		||||
        let unobserve = provider.observe(identifier, (updatedModel) => {
 | 
			
		||||
            mutableObject.$refresh(updatedModel);
 | 
			
		||||
        });
 | 
			
		||||
        mutableObject.$on('$destroy', () => {
 | 
			
		||||
            unobserve();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return mutableObject;
 | 
			
		||||
 
 | 
			
		||||
@@ -163,22 +163,14 @@ 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.modified = 1;
 | 
			
		||||
            updatedTestObject.persisted = 1;
 | 
			
		||||
 | 
			
		||||
            updatedTestObject = Object.assign({otherAttribute: 'changed-attribute-value'}, testObject);
 | 
			
		||||
            mockProvider = jasmine.createSpyObj("mock provider", [
 | 
			
		||||
                "get",
 | 
			
		||||
                "create",
 | 
			
		||||
@@ -190,8 +182,6 @@ 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) {
 | 
			
		||||
@@ -199,8 +189,6 @@ describe("The Object API", () => {
 | 
			
		||||
                } else {
 | 
			
		||||
                    callbacks[0] = callback;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return () => {};
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,8 @@
 | 
			
		||||
 | 
			
		||||
    &__outer {
 | 
			
		||||
        @include abs();
 | 
			
		||||
        background: $colorBodyBg;
 | 
			
		||||
        background: $overlayColorBg;
 | 
			
		||||
        color: $overlayColorFg;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        padding: $overlayInnerMargin;
 | 
			
		||||
@@ -29,6 +30,7 @@
 | 
			
		||||
 | 
			
		||||
    &__close-button {
 | 
			
		||||
        $p: $interiorMargin + 2px;
 | 
			
		||||
        color: $overlayColorFg;
 | 
			
		||||
        font-size: 1.5em;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: $p; right: $p;
 | 
			
		||||
@@ -80,6 +82,11 @@
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .c-button,
 | 
			
		||||
    .c-click-icon {
 | 
			
		||||
        filter: $overlayBrightnessAdjust;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .c-object-label__name {
 | 
			
		||||
        filter: $objectLabelNameFilter;
 | 
			
		||||
    }
 | 
			
		||||
@@ -96,7 +103,6 @@ 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 {
 | 
			
		||||
@@ -118,8 +124,12 @@ body.desktop {
 | 
			
		||||
        $tbPad: floor($pad * 0.8);
 | 
			
		||||
        $lrPad: $pad;
 | 
			
		||||
        .c-overlay {
 | 
			
		||||
            &__blocker {
 | 
			
		||||
                display: none;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &__outer {
 | 
			
		||||
                @include overlaySizing($overlayOuterMarginLarge);
 | 
			
		||||
                @include overlaySizing($overlayOuterMarginFullscreen);
 | 
			
		||||
                padding: $tbPad $lrPad;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ describe("The Status API", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        return resetApplicationState(openmct);
 | 
			
		||||
        resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe("set function", () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,6 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
const { TelemetryCollection } = require("./TelemetryCollection");
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    '../../plugins/displayLayout/CustomStringFormatter',
 | 
			
		||||
    './TelemetryMetadataManager',
 | 
			
		||||
@@ -275,28 +273,6 @@ define([
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Request telemetry collection for a domain object.
 | 
			
		||||
     * The `options` argument allows you to specify filters
 | 
			
		||||
     * (start, end, etc.), sort order, and strategies for retrieving
 | 
			
		||||
     * telemetry (aggregation, latest available, etc.).
 | 
			
		||||
     *
 | 
			
		||||
     * @method requestTelemetryCollection
 | 
			
		||||
     * @memberof module:openmct.TelemetryAPI~TelemetryProvider#
 | 
			
		||||
     * @param {module:openmct.DomainObject} domainObject the object
 | 
			
		||||
     *        which has associated telemetry
 | 
			
		||||
     * @param {module:openmct.TelemetryAPI~TelemetryRequest} options
 | 
			
		||||
     *        options for this telemetry collection request
 | 
			
		||||
     * @returns {TelemetryCollection} a TelemetryCollection instance
 | 
			
		||||
     */
 | 
			
		||||
    TelemetryAPI.prototype.requestTelemetryCollection = function (domainObject, options = {}) {
 | 
			
		||||
        return new TelemetryCollection(
 | 
			
		||||
            this.openmct,
 | 
			
		||||
            domainObject,
 | 
			
		||||
            options
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Request historical telemetry for a domain object.
 | 
			
		||||
     * The `options` argument allows you to specify filters
 | 
			
		||||
@@ -528,26 +504,6 @@ 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
 | 
			
		||||
@@ -575,45 +531,5 @@ 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;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,366 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2020, 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 _ from 'lodash';
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
 | 
			
		||||
/** Class representing a Telemetry Collection. */
 | 
			
		||||
 | 
			
		||||
export class TelemetryCollection extends EventEmitter {
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Telemetry Collection
 | 
			
		||||
     *
 | 
			
		||||
     * @param  {object} openmct - Openm MCT
 | 
			
		||||
     * @param  {object} domainObject - Domain Object to user for telemetry collection
 | 
			
		||||
     * @param  {object} options - Any options passed in for request/subscribe
 | 
			
		||||
     */
 | 
			
		||||
    constructor(openmct, domainObject, options) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this.loaded = false;
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.domainObject = domainObject;
 | 
			
		||||
        this.boundedTelemetry = [];
 | 
			
		||||
        this.futureBuffer = [];
 | 
			
		||||
        this.parseTime = undefined;
 | 
			
		||||
        this.metadata = this.openmct.telemetry.getMetadata(domainObject);
 | 
			
		||||
        this.unsubscribe = undefined;
 | 
			
		||||
        this.historicalProvider = undefined;
 | 
			
		||||
        this.options = options;
 | 
			
		||||
        this.pageState = undefined;
 | 
			
		||||
        this.lastBounds = undefined;
 | 
			
		||||
        this.requestAbort = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This will start the requests for historical and realtime data,
 | 
			
		||||
     * as well as setting up initial values and watchers
 | 
			
		||||
     */
 | 
			
		||||
    load() {
 | 
			
		||||
        if (this.loaded) {
 | 
			
		||||
            throw new Error('Telemetry Collection has already been loaded.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._timeSystem(this.openmct.time.timeSystem());
 | 
			
		||||
        this.lastBounds = this.openmct.time.bounds();
 | 
			
		||||
 | 
			
		||||
        this._watchBounds();
 | 
			
		||||
        this._watchTimeSystem();
 | 
			
		||||
 | 
			
		||||
        this._initiateHistoricalRequests();
 | 
			
		||||
        this._initiateSubscriptionTelemetry();
 | 
			
		||||
 | 
			
		||||
        this.loaded = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * can/should be called by the requester of the telemetry collection
 | 
			
		||||
     * to remove any listeners
 | 
			
		||||
     */
 | 
			
		||||
    destroy() {
 | 
			
		||||
        if (this.requestAbort) {
 | 
			
		||||
            this.requestAbort.abort();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._unwatchBounds();
 | 
			
		||||
        this._unwatchTimeSystem();
 | 
			
		||||
        if (this.unsubscribe) {
 | 
			
		||||
            this.unsubscribe();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.removeAllListeners();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This will start the requests for historical and realtime data,
 | 
			
		||||
     * as well as setting up initial values and watchers
 | 
			
		||||
     */
 | 
			
		||||
    getAll() {
 | 
			
		||||
        return this.boundedTelemetry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up  the telemetry collection for historical requests,
 | 
			
		||||
     * this uses the "standardizeRequestOptions" from Telemetry API
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _initiateHistoricalRequests() {
 | 
			
		||||
        this.openmct.telemetry.standardizeRequestOptions(this.options);
 | 
			
		||||
        this.historicalProvider = this.openmct.telemetry.
 | 
			
		||||
            findRequestProvider(this.domainObject, this.options);
 | 
			
		||||
 | 
			
		||||
        this._requestHistoricalTelemetry();
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * If a historical provider exists, then historical requests will be made
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    async _requestHistoricalTelemetry() {
 | 
			
		||||
        if (!this.historicalProvider) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let historicalData;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.requestAbort = new AbortController();
 | 
			
		||||
            this.options.abortSignal = this.requestAbort.signal;
 | 
			
		||||
            historicalData = await this.historicalProvider.request(this.domainObject, this.options);
 | 
			
		||||
            this.requestAbort = undefined;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            console.error('Error requesting telemetry data...');
 | 
			
		||||
            this.requestAbort = undefined;
 | 
			
		||||
            throw new Error(error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._processNewTelemetry(historicalData);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * This uses the built in subscription function from Telemetry API
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _initiateSubscriptionTelemetry() {
 | 
			
		||||
 | 
			
		||||
        if (this.unsubscribe) {
 | 
			
		||||
            this.unsubscribe();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.unsubscribe = this.openmct.telemetry
 | 
			
		||||
            .subscribe(
 | 
			
		||||
                this.domainObject,
 | 
			
		||||
                datum => this._processNewTelemetry(datum),
 | 
			
		||||
                this.options
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Filter any new telemetry (add/page, historical, subscription) based on
 | 
			
		||||
     * time bounds and dupes
 | 
			
		||||
     *
 | 
			
		||||
     * @param  {(Object|Object[])} telemetryData - telemetry data object or
 | 
			
		||||
     * array of telemetry data objects
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _processNewTelemetry(telemetryData) {
 | 
			
		||||
        let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData];
 | 
			
		||||
        let parsedValue;
 | 
			
		||||
        let beforeStartOfBounds;
 | 
			
		||||
        let afterEndOfBounds;
 | 
			
		||||
        let added = [];
 | 
			
		||||
 | 
			
		||||
        for (let datum of data) {
 | 
			
		||||
            parsedValue = this.parseTime(datum);
 | 
			
		||||
            beforeStartOfBounds = parsedValue < this.lastBounds.start;
 | 
			
		||||
            afterEndOfBounds = parsedValue > this.lastBounds.end;
 | 
			
		||||
 | 
			
		||||
            if (!afterEndOfBounds && !beforeStartOfBounds) {
 | 
			
		||||
                let isDuplicate = false;
 | 
			
		||||
                let startIndex = this._sortedIndex(datum);
 | 
			
		||||
                let endIndex = undefined;
 | 
			
		||||
 | 
			
		||||
                // dupe check
 | 
			
		||||
                if (startIndex !== this.boundedTelemetry.length) {
 | 
			
		||||
                    endIndex = _.sortedLastIndexBy(
 | 
			
		||||
                        this.boundedTelemetry,
 | 
			
		||||
                        datum,
 | 
			
		||||
                        boundedDatum => this.parseTime(boundedDatum)
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    if (endIndex > startIndex) {
 | 
			
		||||
                        let potentialDupes = this.boundedTelemetry.slice(startIndex, endIndex);
 | 
			
		||||
 | 
			
		||||
                        isDuplicate = potentialDupes.some(_.isEqual(undefined, datum));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!isDuplicate) {
 | 
			
		||||
                    let index = endIndex || startIndex;
 | 
			
		||||
 | 
			
		||||
                    this.boundedTelemetry.splice(index, 0, datum);
 | 
			
		||||
                    added.push(datum);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            } else if (afterEndOfBounds) {
 | 
			
		||||
                this.futureBuffer.push(datum);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (added.length) {
 | 
			
		||||
            this.emit('add', added);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Finds the correct insertion point for the given telemetry datum.
 | 
			
		||||
     * Leverages lodash's `sortedIndexBy` function which implements a binary search.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _sortedIndex(datum) {
 | 
			
		||||
        if (this.boundedTelemetry.length === 0) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let parsedValue = this.parseTime(datum);
 | 
			
		||||
        let lastValue = this.parseTime(this.boundedTelemetry[this.boundedTelemetry.length - 1]);
 | 
			
		||||
 | 
			
		||||
        if (parsedValue > lastValue || parsedValue === lastValue) {
 | 
			
		||||
            return this.boundedTelemetry.length;
 | 
			
		||||
        } else {
 | 
			
		||||
            return _.sortedIndexBy(
 | 
			
		||||
                this.boundedTelemetry,
 | 
			
		||||
                datum,
 | 
			
		||||
                boundedDatum => this.parseTime(boundedDatum)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * when the start time, end time, or both have been updated.
 | 
			
		||||
     * data could be added OR removed here we update the current
 | 
			
		||||
     * bounded telemetry
 | 
			
		||||
     *
 | 
			
		||||
     * @param  {TimeConductorBounds} bounds The newly updated bounds
 | 
			
		||||
     * @param  {boolean} [tick] `true` if the bounds update was due to
 | 
			
		||||
     * a "tick" event (ie. was an automatic update), false otherwise.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _bounds(bounds, isTick) {
 | 
			
		||||
        let startChanged = this.lastBounds.start !== bounds.start;
 | 
			
		||||
        let endChanged = this.lastBounds.end !== bounds.end;
 | 
			
		||||
 | 
			
		||||
        this.lastBounds = bounds;
 | 
			
		||||
 | 
			
		||||
        if (isTick) {
 | 
			
		||||
            // need to check futureBuffer and need to check
 | 
			
		||||
            // if anything has fallen out of bounds
 | 
			
		||||
            let startIndex = 0;
 | 
			
		||||
            let endIndex = 0;
 | 
			
		||||
 | 
			
		||||
            let discarded = [];
 | 
			
		||||
            let added = [];
 | 
			
		||||
            let testDatum = {};
 | 
			
		||||
 | 
			
		||||
            if (startChanged) {
 | 
			
		||||
                testDatum[this.timeKey] = bounds.start;
 | 
			
		||||
                // Calculate the new index of the first item within the bounds
 | 
			
		||||
                startIndex = _.sortedIndexBy(
 | 
			
		||||
                    this.boundedTelemetry,
 | 
			
		||||
                    testDatum,
 | 
			
		||||
                    datum => this.parseTime(datum)
 | 
			
		||||
                );
 | 
			
		||||
                discarded = this.boundedTelemetry.splice(0, startIndex);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (endChanged) {
 | 
			
		||||
                testDatum[this.timeKey] = bounds.end;
 | 
			
		||||
                // Calculate the new index of the last item in bounds
 | 
			
		||||
                endIndex = _.sortedLastIndexBy(
 | 
			
		||||
                    this.futureBuffer,
 | 
			
		||||
                    testDatum,
 | 
			
		||||
                    datum => this.parseTime(datum)
 | 
			
		||||
                );
 | 
			
		||||
                added = this.futureBuffer.splice(0, endIndex);
 | 
			
		||||
                this.boundedTelemetry = [...this.boundedTelemetry, ...added];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (discarded.length > 0) {
 | 
			
		||||
                this.emit('remove', discarded);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (added.length > 0) {
 | 
			
		||||
                this.emit('add', added);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
            // user bounds change, reset
 | 
			
		||||
            this._reset();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * whenever the time system is updated need to update related values in
 | 
			
		||||
     * the Telemetry Collection and reset the telemetry collection
 | 
			
		||||
     *
 | 
			
		||||
     * @param  {TimeSystem} timeSystem - the value of the currently applied
 | 
			
		||||
     * Time System
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _timeSystem(timeSystem) {
 | 
			
		||||
        this.timeKey = timeSystem.key;
 | 
			
		||||
        let metadataValue = this.metadata.value(this.timeKey) || { format: this.timeKey };
 | 
			
		||||
        let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
 | 
			
		||||
 | 
			
		||||
        this.parseTime = (datum) => {
 | 
			
		||||
            return valueFormatter.parse(datum);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this._reset();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reset the telemetry data of the collection, and re-request
 | 
			
		||||
     * historical telemetry
 | 
			
		||||
     * @private
 | 
			
		||||
     *
 | 
			
		||||
     * @todo handle subscriptions more granually
 | 
			
		||||
     */
 | 
			
		||||
    _reset() {
 | 
			
		||||
        this.boundedTelemetry = [];
 | 
			
		||||
        this.futureBuffer = [];
 | 
			
		||||
 | 
			
		||||
        this._requestHistoricalTelemetry();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * adds the _bounds callback to the 'bounds' timeAPI listener
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _watchBounds() {
 | 
			
		||||
        this.openmct.time.on('bounds', this._bounds, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * removes the _bounds callback from the 'bounds' timeAPI listener
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _unwatchBounds() {
 | 
			
		||||
        this.openmct.time.off('bounds', this._bounds, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * adds the _timeSystem callback to the 'timeSystem' timeAPI listener
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _watchTimeSystem() {
 | 
			
		||||
        this.openmct.time.on('timeSystem', this._timeSystem, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * removes the _timeSystem callback from the 'timeSystem' timeAPI listener
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _unwatchTimeSystem() {
 | 
			
		||||
        this.openmct.time.off('timeSystem', this._timeSystem, this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,188 +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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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, {
 | 
			
		||||
            useCORS: true,
 | 
			
		||||
            allowTaint: true,
 | 
			
		||||
            logging: false,
 | 
			
		||||
            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;
 | 
			
		||||
 | 
			
		||||
@@ -1,58 +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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -38,6 +38,8 @@ const DEFAULTS = [
 | 
			
		||||
    'platform/exporters',
 | 
			
		||||
    'platform/telemetry',
 | 
			
		||||
    'platform/features/clock',
 | 
			
		||||
    'platform/features/hyperlink',
 | 
			
		||||
    'platform/features/timeline',
 | 
			
		||||
    'platform/forms',
 | 
			
		||||
    'platform/identity',
 | 
			
		||||
    'platform/persistence/aggregator',
 | 
			
		||||
@@ -80,7 +82,9 @@ 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',
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ describe('the plugin', function () {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('provides a folder to hold plans', () => {
 | 
			
		||||
        return openmct.objects.get(identifier).then((object) => {
 | 
			
		||||
        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', () => {
 | 
			
		||||
        return composition.load().then((objects) => {
 | 
			
		||||
        composition.load().then((objects) => {
 | 
			
		||||
            expect(objects.length).toEqual(2);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -19,8 +19,8 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import LadTableSetView from './LadTableSetView';
 | 
			
		||||
import LadTableSet from './components/LadTableSet.vue';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default function LADTableSetViewProvider(openmct) {
 | 
			
		||||
    return {
 | 
			
		||||
@@ -34,7 +34,32 @@ export default function LADTableSetViewProvider(openmct) {
 | 
			
		||||
            return domainObject.type === 'LadTableSet';
 | 
			
		||||
        },
 | 
			
		||||
        view: function (domainObject, objectPath) {
 | 
			
		||||
            return new LadTableSetView(openmct, 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;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        priority: function () {
 | 
			
		||||
            return 1;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -19,30 +19,50 @@
 | 
			
		||||
 * 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';
 | 
			
		||||
 | 
			
		||||
import LADTableView from './LADTableView';
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
            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;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
<thead>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th>Name</th>
 | 
			
		||||
        <th>Timestamp</th>
 | 
			
		||||
        <th v-for="name in columnNames"
 | 
			
		||||
            :key="name"
 | 
			
		||||
        >
 | 
			
		||||
            {{ name }}
 | 
			
		||||
        </th>
 | 
			
		||||
        <th v-if="hasUnits">Unit</th>
 | 
			
		||||
    </tr>
 | 
			
		||||
</thead>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        item: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: true
 | 
			
		||||
        },
 | 
			
		||||
        columnNames: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
            required: true
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {};
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        hasUnits() {
 | 
			
		||||
            // let itemsWithUnits = this.items.filter((item) => {
 | 
			
		||||
            //     let metadata = this.openmct.telemetry.getMetadata(item.domainObject);
 | 
			
		||||
 | 
			
		||||
            //     return this.metadataHasUnits(metadata.valueMetadatas);
 | 
			
		||||
 | 
			
		||||
            // });
 | 
			
		||||
 | 
			
		||||
            // return itemsWithUnits.length !== 0;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        // console.log(this.names);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
@@ -29,11 +29,9 @@
 | 
			
		||||
    <td class="js-first-data">{{ domainObject.name }}</td>
 | 
			
		||||
    <td class="js-second-data">{{ formattedTimestamp }}</td>
 | 
			
		||||
    <td
 | 
			
		||||
        v-for="name in columnNames"
 | 
			
		||||
        :key="name"
 | 
			
		||||
        class="js-third-data"
 | 
			
		||||
        :class="valueClass"
 | 
			
		||||
    >{{ value[name] }}</td>
 | 
			
		||||
    >{{ value }}</td>
 | 
			
		||||
    <td
 | 
			
		||||
        v-if="hasUnits"
 | 
			
		||||
        class="js-units"
 | 
			
		||||
@@ -52,7 +50,7 @@ const CONTEXT_MENU_ACTIONS = [
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['openmct', 'currentView'],
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -65,10 +63,6 @@ export default {
 | 
			
		||||
        hasUnits: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            requred: true
 | 
			
		||||
        },
 | 
			
		||||
        columnNames: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
            required: true
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
@@ -88,7 +82,6 @@ export default {
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        console.log(this.colNames);
 | 
			
		||||
        this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
 | 
			
		||||
        this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
 | 
			
		||||
        this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
 | 
			
		||||
@@ -103,17 +96,12 @@ export default {
 | 
			
		||||
 | 
			
		||||
        this.timestampKey = this.openmct.time.timeSystem().key;
 | 
			
		||||
 | 
			
		||||
        // this.valueMetadata = this
 | 
			
		||||
        //     .metadata
 | 
			
		||||
        //     .valuesForHints(['range'])[0];
 | 
			
		||||
 | 
			
		||||
        // this.valueKey = this.valueMetadata.key;
 | 
			
		||||
        this.valueMetadata = this
 | 
			
		||||
            .metadata
 | 
			
		||||
            .valuesForHints(['range']);
 | 
			
		||||
        // console.log(this.valueMetadata);
 | 
			
		||||
        this.valueKey = this.valueMetadata.map(value => value.key);
 | 
			
		||||
        // console.log(this.valueKey);
 | 
			
		||||
            .valuesForHints(['range'])[0];
 | 
			
		||||
 | 
			
		||||
        this.valueKey = this.valueMetadata.key;
 | 
			
		||||
 | 
			
		||||
        this.unsubscribe = this.openmct
 | 
			
		||||
            .telemetry
 | 
			
		||||
            .subscribe(this.domainObject, this.updateValues);
 | 
			
		||||
@@ -135,18 +123,9 @@ export default {
 | 
			
		||||
            let limit;
 | 
			
		||||
 | 
			
		||||
            if (this.shouldUpdate(newTimestamp)) {
 | 
			
		||||
                // console.log(datum);
 | 
			
		||||
                this.datum = datum;
 | 
			
		||||
                this.timestamp = newTimestamp;
 | 
			
		||||
                // console.log(this.formats);
 | 
			
		||||
                // this.value = this.formats[this.valueKey].format(datum);
 | 
			
		||||
                this.value = {};
 | 
			
		||||
                this.valueKey.forEach(key => {
 | 
			
		||||
                    let formattedDatum = this.formats[key].format(datum);
 | 
			
		||||
                    this.value[key] = formattedDatum;
 | 
			
		||||
                    limit = this.limitEvaluator.evaluate(formattedDatum, this.valueMetadata);
 | 
			
		||||
                });
 | 
			
		||||
                // console.log(this.value);
 | 
			
		||||
                this.value = this.formats[this.valueKey].format(datum);
 | 
			
		||||
                limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
 | 
			
		||||
                if (limit) {
 | 
			
		||||
                    this.valueClass = limit.cssClass;
 | 
			
		||||
@@ -188,23 +167,25 @@ export default {
 | 
			
		||||
            this.resetValues();
 | 
			
		||||
            this.timestampKey = timeSystem.key;
 | 
			
		||||
        },
 | 
			
		||||
        updateViewContext() {
 | 
			
		||||
            this.$emit('rowContextClick', {
 | 
			
		||||
                viewHistoricalData: true,
 | 
			
		||||
                viewDatumAction: true,
 | 
			
		||||
                getDatum: () => {
 | 
			
		||||
                    return this.datum;
 | 
			
		||||
        getView() {
 | 
			
		||||
            return {
 | 
			
		||||
                getViewContext: () => {
 | 
			
		||||
                    return {
 | 
			
		||||
                        viewHistoricalData: true,
 | 
			
		||||
                        viewDatumAction: true,
 | 
			
		||||
                        getDatum: () => {
 | 
			
		||||
                            return this.datum;
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        showContextMenu(event) {
 | 
			
		||||
            this.updateViewContext();
 | 
			
		||||
            let actionCollection = this.openmct.actions.get(this.objectPath, this.getView());
 | 
			
		||||
            let allActions = actionCollection.getActionsObject();
 | 
			
		||||
            let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
            }
 | 
			
		||||
            this.openmct.menus.showMenu(event.x, event.y, applicableActions);
 | 
			
		||||
        },
 | 
			
		||||
        resetValues() {
 | 
			
		||||
            this.value = '---';
 | 
			
		||||
 
 | 
			
		||||
@@ -22,21 +22,22 @@
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-lad-table-wrapper u-style-receiver js-style-receiver">
 | 
			
		||||
    <table v-for="ladRow in items"
 | 
			
		||||
           :key="ladRow.key"
 | 
			
		||||
           class="c-table c-lad-table"
 | 
			
		||||
    >
 | 
			
		||||
        <lad-head
 | 
			
		||||
            :item="ladRow"
 | 
			
		||||
            :column-names="columnNames(ladRow)"
 | 
			
		||||
        />
 | 
			
		||||
    <table class="c-table c-lad-table">
 | 
			
		||||
        <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>Name</th>
 | 
			
		||||
                <th>Timestamp</th>
 | 
			
		||||
                <th>Value</th>
 | 
			
		||||
                <th v-if="hasUnits">Unit</th>
 | 
			
		||||
            </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
            <lad-row
 | 
			
		||||
                :column-names="columnNames(ladRow)"
 | 
			
		||||
                v-for="ladRow in items"
 | 
			
		||||
                :key="ladRow.key"
 | 
			
		||||
                :domain-object="ladRow.domainObject"
 | 
			
		||||
                :path-to-table="objectPath"
 | 
			
		||||
                :has-units="hasUnits"
 | 
			
		||||
                @rowContextClick="updateViewContext"
 | 
			
		||||
            />
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
@@ -45,14 +46,12 @@
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import LadRow from './LADRow.vue';
 | 
			
		||||
import LadHead from './LADHead.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        LadRow,
 | 
			
		||||
        LadHead
 | 
			
		||||
        LadRow
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct', 'currentView'],
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -65,8 +64,7 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            items: [],
 | 
			
		||||
            viewContext: {}
 | 
			
		||||
            items: []
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -94,13 +92,6 @@ export default {
 | 
			
		||||
        this.composition.off('reorder', this.reorder);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        columnNames(item) {
 | 
			
		||||
            let metadata = this.openmct.telemetry.getMetadata(item.domainObject);
 | 
			
		||||
            let valueMetadata = metadata
 | 
			
		||||
                .valuesForHints(['range']);
 | 
			
		||||
 | 
			
		||||
            return valueMetadata.map(value => value.key);
 | 
			
		||||
        },
 | 
			
		||||
        addItem(domainObject) {
 | 
			
		||||
            let item = {};
 | 
			
		||||
            item.domainObject = domainObject;
 | 
			
		||||
@@ -123,12 +114,6 @@ export default {
 | 
			
		||||
            let metadataWithUnits = valueMetadatas.filter(metadatum => metadatum.unit);
 | 
			
		||||
 | 
			
		||||
            return metadataWithUnits.length > 0;
 | 
			
		||||
        },
 | 
			
		||||
        updateViewContext(rowContext) {
 | 
			
		||||
            this.viewContext.row = rowContext;
 | 
			
		||||
        },
 | 
			
		||||
        getViewContext() {
 | 
			
		||||
            return this.viewContext;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,6 @@
 | 
			
		||||
                :domain-object="ladRow.domainObject"
 | 
			
		||||
                :path-to-table="ladTable.objectPath"
 | 
			
		||||
                :has-units="hasUnits"
 | 
			
		||||
                @rowContextClick="updateViewContext"
 | 
			
		||||
            />
 | 
			
		||||
        </template>
 | 
			
		||||
    </tbody>
 | 
			
		||||
@@ -62,7 +61,7 @@ export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        LadRow
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct', 'objectPath', 'currentView'],
 | 
			
		||||
    inject: ['openmct', 'objectPath'],
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -73,8 +72,7 @@ export default {
 | 
			
		||||
        return {
 | 
			
		||||
            ladTableObjects: [],
 | 
			
		||||
            ladTelemetryObjects: {},
 | 
			
		||||
            compositions: [],
 | 
			
		||||
            viewContext: {}
 | 
			
		||||
            compositions: []
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -168,12 +166,6 @@ export default {
 | 
			
		||||
 | 
			
		||||
                this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        updateViewContext(rowContext) {
 | 
			
		||||
            this.viewContext.row = rowContext;
 | 
			
		||||
        },
 | 
			
		||||
        getViewContext() {
 | 
			
		||||
            return this.viewContext;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,10 @@ 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');
 | 
			
		||||
@@ -86,7 +90,7 @@ describe("The LAD Table", () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        openmct.on('start', done);
 | 
			
		||||
        openmct.startHeadless();
 | 
			
		||||
        openmct.startHeadless(appHolder);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
@@ -109,8 +113,7 @@ describe("The LAD Table", () => {
 | 
			
		||||
 | 
			
		||||
        beforeEach(() => {
 | 
			
		||||
            ladTableCompositionCollection = openmct.composition.get(mockObj.ladTable);
 | 
			
		||||
 | 
			
		||||
            return ladTableCompositionCollection.load();
 | 
			
		||||
            ladTableCompositionCollection.load();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("should accept telemetry producing objects", () => {
 | 
			
		||||
@@ -189,6 +192,8 @@ describe("The LAD Table", () => {
 | 
			
		||||
 | 
			
		||||
            await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
 | 
			
		||||
            await Vue.nextTick();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("should show one row per object in the composition", () => {
 | 
			
		||||
@@ -237,6 +242,13 @@ 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']
 | 
			
		||||
@@ -252,36 +264,38 @@ 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.startHeadless();
 | 
			
		||||
        openmct.start(appHolder);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    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(
 | 
			
		||||
@@ -296,11 +310,8 @@ describe("The LAD Table Set", () => {
 | 
			
		||||
        let ladTableSetCompositionCollection;
 | 
			
		||||
 | 
			
		||||
        beforeEach(() => {
 | 
			
		||||
            spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
 | 
			
		||||
 | 
			
		||||
            ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet);
 | 
			
		||||
 | 
			
		||||
            return ladTableSetCompositionCollection.load();
 | 
			
		||||
            ladTableSetCompositionCollection.load();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("should accept lad table objects", () => {
 | 
			
		||||
@@ -338,17 +349,41 @@ describe("The LAD Table Set", () => {
 | 
			
		||||
        otherObj.ladTable.composition.push(mockObj.telemetry.identifier);
 | 
			
		||||
        mockObj.ladTableSet.composition.push(otherObj.ladTable.identifier);
 | 
			
		||||
 | 
			
		||||
        beforeEach(() => {
 | 
			
		||||
            spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
 | 
			
		||||
        beforeEach(async () => {
 | 
			
		||||
            let telemetryRequestResolve;
 | 
			
		||||
            let ladObjectResolve;
 | 
			
		||||
            let anotherLadObjectResolve;
 | 
			
		||||
 | 
			
		||||
            spyOn(openmct.objects, 'get').and.callFake((obj) => {
 | 
			
		||||
            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) => {
 | 
			
		||||
                if (obj.key === 'lad-object') {
 | 
			
		||||
                    return Promise.resolve(mockObj.ladTable);
 | 
			
		||||
                    ladObjectResolve(mockObj.ladObject);
 | 
			
		||||
 | 
			
		||||
                    return ladObjectPromise;
 | 
			
		||||
                } else if (obj.key === 'another-lad-object') {
 | 
			
		||||
                    return Promise.resolve(otherObj.ladTable);
 | 
			
		||||
                } else if (obj.key === 'telemetry-object') {
 | 
			
		||||
                    return Promise.resolve(mockObj.telemetry);
 | 
			
		||||
                    anotherLadObjectResolve(otherObj.ladObject);
 | 
			
		||||
 | 
			
		||||
                    return anotherLadObjectPromise;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Promise.resolve({});
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            openmct.time.bounds({
 | 
			
		||||
@@ -359,19 +394,20 @@ 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);
 | 
			
		||||
            ladTableSetView.show(child, true);
 | 
			
		||||
 | 
			
		||||
            return Vue.nextTick();
 | 
			
		||||
            await Promise.all([telemetryRequestPromise, ladObjectPromise, anotherLadObjectPromise]);
 | 
			
		||||
            await Vue.nextTick();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("should show one row per lad table object in the composition", () => {
 | 
			
		||||
            const ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet);
 | 
			
		||||
            const rowCount = parent.querySelectorAll(LAD_SET_TABLE_HEADERS).length;
 | 
			
		||||
 | 
			
		||||
            return ladTableSetCompositionCollection.load().then(() => {
 | 
			
		||||
                const rowCount = parent.querySelectorAll(LAD_SET_TABLE_HEADERS).length;
 | 
			
		||||
 | 
			
		||||
                expect(rowCount).toBe(mockObj.ladTableSet.composition.length);
 | 
			
		||||
            });
 | 
			
		||||
            expect(rowCount).toBe(mockObj.ladTableSet.composition.length);
 | 
			
		||||
            pending();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,14 +22,12 @@
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    [
 | 
			
		||||
        "utils/testing",
 | 
			
		||||
        "./URLIndicator",
 | 
			
		||||
        "./URLIndicatorPlugin",
 | 
			
		||||
        "../../MCT",
 | 
			
		||||
        "zepto"
 | 
			
		||||
    ],
 | 
			
		||||
    function (
 | 
			
		||||
        testingUtils,
 | 
			
		||||
        URLIndicator,
 | 
			
		||||
        URLIndicatorPlugin,
 | 
			
		||||
        MCT,
 | 
			
		||||
@@ -46,7 +44,7 @@ define(
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                jasmine.clock().install();
 | 
			
		||||
                openmct = new testingUtils.createOpenMct();
 | 
			
		||||
                openmct = new MCT();
 | 
			
		||||
                spyOn(openmct.indicators, 'add');
 | 
			
		||||
                spyOn($, 'ajax');
 | 
			
		||||
                $.ajax.and.callFake(function (options) {
 | 
			
		||||
@@ -57,8 +55,6 @@ define(
 | 
			
		||||
            afterEach(function () {
 | 
			
		||||
                $.ajax = defaultAjaxFunction;
 | 
			
		||||
                jasmine.clock().uninstall();
 | 
			
		||||
 | 
			
		||||
                return testingUtils.resetApplicationState(openmct);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("on initialization", function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,10 @@
 | 
			
		||||
 * 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';
 | 
			
		||||
@@ -46,8 +50,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);
 | 
			
		||||
        });
 | 
			
		||||
@@ -55,8 +59,7 @@ export default class URLTimeSettingsSynchronizer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        this.openmct.router.off('change:params', this.updateTimeSettings);
 | 
			
		||||
 | 
			
		||||
        window.removeEventListener('hashchange', this.updateTimeSettings);
 | 
			
		||||
        this.openmct.off('start', this.initialize);
 | 
			
		||||
        this.openmct.off('destroy', this.destroy);
 | 
			
		||||
 | 
			
		||||
@@ -67,18 +70,22 @@ export default class URLTimeSettingsSynchronizer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateTimeSettings() {
 | 
			
		||||
        let timeParameters = this.parseParametersFromUrl();
 | 
			
		||||
        // Prevent from triggering self
 | 
			
		||||
        if (!this.isUrlUpdateInProgress) {
 | 
			
		||||
            let timeParameters = this.parseParametersFromUrl();
 | 
			
		||||
 | 
			
		||||
        if (this.areTimeParametersValid(timeParameters)) {
 | 
			
		||||
            this.setTimeApiFromUrl(timeParameters);
 | 
			
		||||
            this.openmct.router.setLocationFromUrl();
 | 
			
		||||
            if (this.areTimeParametersValid(timeParameters)) {
 | 
			
		||||
                this.setTimeApiFromUrl(timeParameters);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.setUrlFromTimeApi();
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.setUrlFromTimeApi();
 | 
			
		||||
            this.isUrlUpdateInProgress = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    parseParametersFromUrl() {
 | 
			
		||||
        let searchParams = this.openmct.router.getAllSearchParams();
 | 
			
		||||
        let searchParams = getAllSearchParams();
 | 
			
		||||
 | 
			
		||||
        let mode = searchParams.get(SEARCH_MODE);
 | 
			
		||||
        let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
 | 
			
		||||
@@ -141,7 +148,7 @@ export default class URLTimeSettingsSynchronizer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setUrlFromTimeApi() {
 | 
			
		||||
        let searchParams = this.openmct.router.getAllSearchParams();
 | 
			
		||||
        let searchParams = getAllSearchParams();
 | 
			
		||||
        let clock = this.openmct.time.clock();
 | 
			
		||||
        let bounds = this.openmct.time.bounds();
 | 
			
		||||
        let clockOffsets = this.openmct.time.clockOffsets();
 | 
			
		||||
@@ -169,7 +176,8 @@ export default class URLTimeSettingsSynchronizer {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
 | 
			
		||||
        this.openmct.router.setAllSearchParams(searchParams);
 | 
			
		||||
        this.isUrlUpdateInProgress = true;
 | 
			
		||||
        setAllSearchParams(searchParams);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    areTimeParametersValid(timeParameters) {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,118 +25,306 @@ import {
 | 
			
		||||
} from 'utils/testing';
 | 
			
		||||
 | 
			
		||||
describe("The URLTimeSettingsSynchronizer", () => {
 | 
			
		||||
    let appHolder;
 | 
			
		||||
    let openmct;
 | 
			
		||||
    let resolveFunction;
 | 
			
		||||
    let oldHash;
 | 
			
		||||
    let testClock;
 | 
			
		||||
 | 
			
		||||
    beforeEach((done) => {
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
        openmct.install(openmct.plugins.MyItems());
 | 
			
		||||
        openmct.install(openmct.plugins.LocalTimeSystem());
 | 
			
		||||
        openmct.install(openmct.plugins.UTCTimeSystem());
 | 
			
		||||
        testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
 | 
			
		||||
        testClock.key = "test-clock";
 | 
			
		||||
        testClock.currentValue.and.returnValue(0);
 | 
			
		||||
 | 
			
		||||
        openmct.time.addClock(testClock);
 | 
			
		||||
 | 
			
		||||
        openmct.on('start', done);
 | 
			
		||||
 | 
			
		||||
        appHolder = document.createElement("div");
 | 
			
		||||
        openmct.start(appHolder);
 | 
			
		||||
        openmct.startHeadless();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        openmct.time.stopClock();
 | 
			
		||||
        openmct.router.removeListener('change:hash', resolveFunction);
 | 
			
		||||
    afterEach(() => resetApplicationState(openmct));
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
    describe("realtime mode", () => {
 | 
			
		||||
        it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
 | 
			
		||||
            //Test expected initial conditions
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
            //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();
 | 
			
		||||
 | 
			
		||||
                openmct.router.removeListener('change:hash', resolveFunction);
 | 
			
		||||
                done();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
                return switchToRealtimeMode().then(() => {
 | 
			
		||||
                    let clock = openmct.time.clock();
 | 
			
		||||
 | 
			
		||||
        openmct.router.on('change:hash', resolveFunction);
 | 
			
		||||
                    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);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("when the clock mode is set to local, it is reflected in the URL", (done) => {
 | 
			
		||||
        let success;
 | 
			
		||||
    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');
 | 
			
		||||
 | 
			
		||||
        resolveFunction = () => {
 | 
			
		||||
            let hash = window.location.hash;
 | 
			
		||||
            hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
 | 
			
		||||
            window.location.hash = hash;
 | 
			
		||||
        window.location.hash = hash;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
            success = window.location.hash.includes('tc.mode=local');
 | 
			
		||||
            if (success) {
 | 
			
		||||
                expect(success).toBe(true);
 | 
			
		||||
                done();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    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');
 | 
			
		||||
 | 
			
		||||
        openmct.router.on('change:hash', resolveFunction);
 | 
			
		||||
    });
 | 
			
		||||
        window.location.hash = hash;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it("when the clock mode is set to local, it is reflected in the URL", (done) => {
 | 
			
		||||
        let success;
 | 
			
		||||
    function switchToRealtimeMode() {
 | 
			
		||||
        let resolveFunction;
 | 
			
		||||
 | 
			
		||||
        resolveFunction = () => {
 | 
			
		||||
            let hash = window.location.hash;
 | 
			
		||||
        return new Promise((resolve) => {
 | 
			
		||||
            resolveFunction = resolve;
 | 
			
		||||
            openmct.time.on('clock', resolveFunction);
 | 
			
		||||
            setRealtimeLocationParameters();
 | 
			
		||||
        }).then(() => {
 | 
			
		||||
            openmct.time.off('clock', 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();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    function switchToFixedMode() {
 | 
			
		||||
        let resolveFunction;
 | 
			
		||||
 | 
			
		||||
        openmct.router.on('change:hash', 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);
 | 
			
		||||
            setFixedLocationParameters();
 | 
			
		||||
        }).then(() => {
 | 
			
		||||
            openmct.time.off('clock', resolveFunction);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it("reset hash", (done) => {
 | 
			
		||||
        let success;
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        window.location.hash = oldHash;
 | 
			
		||||
        resolveFunction = () => {
 | 
			
		||||
            success = window.location.hash === oldHash;
 | 
			
		||||
            if (success) {
 | 
			
		||||
                expect(success).toBe(true);
 | 
			
		||||
                done();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        openmct.router.on('change:hash', resolveFunction);
 | 
			
		||||
    });
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -28,10 +28,8 @@ import {
 | 
			
		||||
    resetApplicationState,
 | 
			
		||||
    spyOnBuiltins
 | 
			
		||||
} from 'utils/testing';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
// TODO lots of its without expects
 | 
			
		||||
xdescribe("AutoflowTabularPlugin", () => {
 | 
			
		||||
describe("AutoflowTabularPlugin", () => {
 | 
			
		||||
    let testType;
 | 
			
		||||
    let testObject;
 | 
			
		||||
    let mockmct;
 | 
			
		||||
@@ -53,7 +51,7 @@ xdescribe("AutoflowTabularPlugin", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        return resetApplicationState(mockmct);
 | 
			
		||||
        resetApplicationState(mockmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("installs a view provider", () => {
 | 
			
		||||
@@ -103,7 +101,7 @@ xdescribe("AutoflowTabularPlugin", () => {
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(() => {
 | 
			
		||||
            beforeEach((done) => {
 | 
			
		||||
                callbacks = {};
 | 
			
		||||
 | 
			
		||||
                spyOnBuiltins(['requestAnimationFrame']);
 | 
			
		||||
@@ -182,7 +180,7 @@ xdescribe("AutoflowTabularPlugin", () => {
 | 
			
		||||
                view = provider.view(testObject);
 | 
			
		||||
                view.show(testContainer);
 | 
			
		||||
 | 
			
		||||
                return Vue.nextTick();
 | 
			
		||||
                return done();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            afterEach(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
        this.subscriptions = {};
 | 
			
		||||
        this.telemetryObjects = {};
 | 
			
		||||
        this.testData = {
 | 
			
		||||
            conditionTestInputs: this.conditionSetDomainObject.configuration.conditionTestData,
 | 
			
		||||
            conditionTestData: [],
 | 
			
		||||
            applied: false
 | 
			
		||||
        };
 | 
			
		||||
        this.initialize();
 | 
			
		||||
@@ -154,10 +154,8 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    updateConditionDescription(condition) {
 | 
			
		||||
        const found = this.conditionSetDomainObject.configuration.conditionCollection.find(conditionConfiguration => (conditionConfiguration.id === condition.id));
 | 
			
		||||
        if (found.summary !== condition.description) {
 | 
			
		||||
            found.summary = condition.description;
 | 
			
		||||
            this.persistConditions();
 | 
			
		||||
        }
 | 
			
		||||
        found.summary = condition.description;
 | 
			
		||||
        this.persistConditions();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initCondition(conditionConfiguration, index) {
 | 
			
		||||
@@ -416,10 +414,8 @@ export default class ConditionManager extends EventEmitter {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateTestData(testData) {
 | 
			
		||||
        if (!_.isEqual(testData, this.testData)) {
 | 
			
		||||
            this.testData = testData;
 | 
			
		||||
            this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
 | 
			
		||||
        }
 | 
			
		||||
        this.testData = testData;
 | 
			
		||||
        this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    persistConditions() {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,17 +27,15 @@ 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);
 | 
			
		||||
            this.openmct.editor.on('isEditing', this.toggleSubscription.bind(this));
 | 
			
		||||
            this.isEditing = this.openmct.editor.editing;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (styleConfiguration) {
 | 
			
		||||
            this.initialize(styleConfiguration);
 | 
			
		||||
            if (styleConfiguration.conditionSetIdentifier) {
 | 
			
		||||
                this.openmct.time.on("bounds", this.refreshData);
 | 
			
		||||
                this.openmct.time.on("bounds", this.refreshData.bind(this));
 | 
			
		||||
                this.subscribeToConditionSet();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.applyStaticStyle();
 | 
			
		||||
 
 | 
			
		||||
@@ -215,8 +215,7 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        isEditing: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            required: true,
 | 
			
		||||
            default: false
 | 
			
		||||
            required: true
 | 
			
		||||
        },
 | 
			
		||||
        telemetry: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,7 @@
 | 
			
		||||
        <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>
 | 
			
		||||
@@ -285,8 +286,6 @@ export default {
 | 
			
		||||
            if (this.openmct.editor.isEditing()) {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
                this.previewAction.invoke(this.objectPath);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.openmct.router.navigate(this.navigateToPath);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        removeConditionSet() {
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,7 @@
 | 
			
		||||
        <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>
 | 
			
		||||
@@ -308,8 +309,6 @@ export default {
 | 
			
		||||
            if (this.openmct.editor.isEditing()) {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
                this.previewAction.invoke(this.objectPath);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.openmct.router.navigate(this.navigateToPath);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        isItemType(type, item) {
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ xdescribe("the plugin", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        return resetApplicationState(openmct);
 | 
			
		||||
        resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('installs the new folder action', () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,78 +20,71 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import AlphanumericFormat from './components/AlphanumericFormat.vue';
 | 
			
		||||
define([
 | 
			
		||||
    './components/AlphanumericFormatView.vue',
 | 
			
		||||
    'vue'
 | 
			
		||||
], function (AlphanumericFormatView, Vue) {
 | 
			
		||||
 | 
			
		||||
import Vue from '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;
 | 
			
		||||
 | 
			
		||||
class AlphanumericFormatView {
 | 
			
		||||
    constructor(openmct, domainObject, objectPath) {
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.domainObject = domainObject;
 | 
			
		||||
        this.objectPath = objectPath;
 | 
			
		||||
        this.component = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 parentObject
 | 
			
		||||
                && parentObject.type === 'layout'
 | 
			
		||||
                && selectedObject
 | 
			
		||||
                && selectedLayoutItem
 | 
			
		||||
                && selectedLayoutItem.type === 'telemetry-view'
 | 
			
		||||
                && openmct.telemetry.isTelemetryObject(selectedObject)
 | 
			
		||||
                && !options.showAsView.includes(selectedObject.type);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.component.$refs.alphanumericFormat.getViewContext();
 | 
			
		||||
    }
 | 
			
		||||
        return {
 | 
			
		||||
            key: 'alphanumeric-format',
 | 
			
		||||
            name: 'Alphanumeric Format',
 | 
			
		||||
            canView: function (selection) {
 | 
			
		||||
                if (selection.length === 0 || selection[0].length === 1) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        this.component.$destroy();
 | 
			
		||||
        this.component = undefined;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
                return selection.every(isTelemetryObject);
 | 
			
		||||
            },
 | 
			
		||||
            view: function (domainObject, objectPath) {
 | 
			
		||||
                let component;
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
                    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;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
            return selection.every(isTelemetryObject);
 | 
			
		||||
        },
 | 
			
		||||
        view: function (domainObject, objectPath) {
 | 
			
		||||
            return new AlphanumericFormatView(openmct, domainObject, objectPath);
 | 
			
		||||
        },
 | 
			
		||||
        priority: function () {
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
    return AlphanumericFormatViewProvider;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ export default class CopyToClipboardAction {
 | 
			
		||||
 | 
			
		||||
    invoke(objectPath, view = {}) {
 | 
			
		||||
        const viewContext = view.getViewContext && view.getViewContext();
 | 
			
		||||
        const formattedValue = viewContext.row.formattedValueForCopy();
 | 
			
		||||
        const formattedValue = viewContext.formattedValueForCopy();
 | 
			
		||||
 | 
			
		||||
        clipboard.updateClipboard(formattedValue)
 | 
			
		||||
            .then(() => {
 | 
			
		||||
@@ -26,13 +26,9 @@ export default class CopyToClipboardAction {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    appliesTo(objectPath, view = {}) {
 | 
			
		||||
        const viewContext = view.getViewContext && view.getViewContext();
 | 
			
		||||
        const row = viewContext && viewContext.row;
 | 
			
		||||
        if (!row) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        let viewContext = view.getViewContext && view.getViewContext();
 | 
			
		||||
 | 
			
		||||
        return row.formattedValueForCopy
 | 
			
		||||
            && typeof row.formattedValueForCopy === 'function';
 | 
			
		||||
        return viewContext && viewContext.formattedValueForCopy
 | 
			
		||||
            && typeof viewContext.formattedValueForCopy === 'function';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -52,8 +52,7 @@
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    name: 'AlphanumericFormat',
 | 
			
		||||
    inject: ['openmct', 'objectPath'],
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            isEditing: this.openmct.editor.isEditing(),
 | 
			
		||||
@@ -56,7 +56,6 @@
 | 
			
		||||
        :index="index"
 | 
			
		||||
        :multi-select="selectedLayoutItems.length > 1"
 | 
			
		||||
        :is-editing="isEditing"
 | 
			
		||||
        @contextClick="updateViewContext"
 | 
			
		||||
        @move="move"
 | 
			
		||||
        @endMove="endMove"
 | 
			
		||||
        @endLineResize="endLineResize"
 | 
			
		||||
@@ -141,7 +140,7 @@ function getItemDefinition(itemType, ...options) {
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: components,
 | 
			
		||||
    inject: ['openmct', 'objectPath', 'options', 'objectUtils', 'currentView'],
 | 
			
		||||
    inject: ['openmct', 'options', 'objectPath'],
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -156,8 +155,7 @@ export default {
 | 
			
		||||
        return {
 | 
			
		||||
            initSelectIndex: undefined,
 | 
			
		||||
            selection: [],
 | 
			
		||||
            showGrid: true,
 | 
			
		||||
            viewContext: {}
 | 
			
		||||
            showGrid: true
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -821,12 +819,6 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        toggleGrid() {
 | 
			
		||||
            this.showGrid = !this.showGrid;
 | 
			
		||||
        },
 | 
			
		||||
        updateViewContext(viewContext) {
 | 
			
		||||
            this.viewContext.row = viewContext;
 | 
			
		||||
        },
 | 
			
		||||
        getViewContext() {
 | 
			
		||||
            return this.viewContext;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,7 @@
 | 
			
		||||
<script>
 | 
			
		||||
import LayoutFrame from './LayoutFrame.vue';
 | 
			
		||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
 | 
			
		||||
import { getDefaultNotebook, getNotebookSectionAndPage } from '@/plugins/notebook/utils/notebook-storage.js';
 | 
			
		||||
import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js';
 | 
			
		||||
 | 
			
		||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5];
 | 
			
		||||
const DEFAULT_POSITION = [1, 1];
 | 
			
		||||
@@ -102,7 +102,7 @@ export default {
 | 
			
		||||
        LayoutFrame
 | 
			
		||||
    },
 | 
			
		||||
    mixins: [conditionalStylesMixin],
 | 
			
		||||
    inject: ['openmct', 'objectPath', 'currentView'],
 | 
			
		||||
    inject: ['openmct', 'objectPath'],
 | 
			
		||||
    props: {
 | 
			
		||||
        item: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -294,6 +294,16 @@ export default {
 | 
			
		||||
                this.requestHistoricalData(this.domainObject);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        getView() {
 | 
			
		||||
            return {
 | 
			
		||||
                getViewContext: () => {
 | 
			
		||||
                    return {
 | 
			
		||||
                        viewHistoricalData: true,
 | 
			
		||||
                        formattedValueForCopy: this.formattedValueForCopy
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        setObject(domainObject) {
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.mutablePromise = undefined;
 | 
			
		||||
@@ -328,41 +338,30 @@ 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 domainObject = await this.openmct.objects.get(defaultNotebook.identifier);
 | 
			
		||||
                const { section, page } = getNotebookSectionAndPage(domainObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId);
 | 
			
		||||
                if (section && page) {
 | 
			
		||||
                    const defaultPath = domainObject && `${domainObject.name} - ${section.name} - ${page.name}`;
 | 
			
		||||
                    defaultNotebookName = `Copy to Notebook ${defaultPath}`;
 | 
			
		||||
                }
 | 
			
		||||
                const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
 | 
			
		||||
                copyToNotebookAction.name = `Copy to Notebook ${defaultPath}`;
 | 
			
		||||
            } else {
 | 
			
		||||
                actionsObject.copyToNotebook = undefined;
 | 
			
		||||
                delete actionsObject.copyToNotebook;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
            return CONTEXT_MENU_ACTIONS.map(actionKey => {
 | 
			
		||||
                return actionsObject[actionKey];
 | 
			
		||||
            }).filter(action => action !== undefined);
 | 
			
		||||
        },
 | 
			
		||||
        async showContextMenu(event) {
 | 
			
		||||
            this.updateViewContext();
 | 
			
		||||
            const contextMenuActions = await this.getContextMenuActions();
 | 
			
		||||
            const menuItems = this.openmct.menus.actionsToMenuItems(contextMenuActions, this.currentObjectPath, this.currentView);
 | 
			
		||||
            this.openmct.menus.showMenu(event.x, event.y, menuItems);
 | 
			
		||||
 | 
			
		||||
            this.openmct.menus.showMenu(event.x, event.y, contextMenuActions);
 | 
			
		||||
        },
 | 
			
		||||
        setStatus(status) {
 | 
			
		||||
            this.status = status;
 | 
			
		||||
 
 | 
			
		||||
@@ -21,10 +21,6 @@
 | 
			
		||||
        margin-left: $interiorMargin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &__value {
 | 
			
		||||
        @include isLimit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .c-frame & {
 | 
			
		||||
        @include abs();
 | 
			
		||||
        border: 1px solid transparent;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,81 +20,13 @@
 | 
			
		||||
 * 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) {
 | 
			
		||||
@@ -109,7 +41,51 @@ export default function DisplayLayoutPlugin(options) {
 | 
			
		||||
                return domainObject.type === 'layout';
 | 
			
		||||
            },
 | 
			
		||||
            view: function (domainObject, objectPath) {
 | 
			
		||||
                return new DisplayLayoutView(openmct, domainObject, objectPath, options);
 | 
			
		||||
                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();
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            },
 | 
			
		||||
            priority() {
 | 
			
		||||
                return 100;
 | 
			
		||||
 
 | 
			
		||||
@@ -37,15 +37,7 @@ export default class DuplicateAction {
 | 
			
		||||
        let duplicationTask = new DuplicateTask(this.openmct);
 | 
			
		||||
        let originalObject = objectPath[0];
 | 
			
		||||
        let parent = objectPath[1];
 | 
			
		||||
        let userInput;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            userInput = await this.getUserInput(originalObject, parent);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            // user most likely canceled
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let userInput = await this.getUserInput(originalObject, parent);
 | 
			
		||||
        let newParent = userInput.location;
 | 
			
		||||
        let inNavigationPath = this.inNavigationPath(originalObject);
 | 
			
		||||
 | 
			
		||||
@@ -79,8 +71,7 @@ export default class DuplicateAction {
 | 
			
		||||
 | 
			
		||||
    updateNameCheck(object, name) {
 | 
			
		||||
        if (object.name !== name) {
 | 
			
		||||
            object.name = name;
 | 
			
		||||
            this.openmct.objects.save(object);
 | 
			
		||||
            this.openmct.objects.mutate(object, 'name', name);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -104,7 +95,7 @@ export default class DuplicateAction {
 | 
			
		||||
                            cssClass: "l-input-lg"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            name: "Location",
 | 
			
		||||
                            name: "location",
 | 
			
		||||
                            cssClass: "grows",
 | 
			
		||||
                            control: "locator",
 | 
			
		||||
                            validate: this.validate(object, parent),
 | 
			
		||||
 
 | 
			
		||||
@@ -112,7 +112,7 @@ describe("The Duplicate Action plugin", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        return resetApplicationState(openmct);
 | 
			
		||||
        resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("should be defined", () => {
 | 
			
		||||
@@ -121,9 +121,10 @@ describe("The Duplicate Action plugin", () => {
 | 
			
		||||
 | 
			
		||||
    describe("when moving an object to a new parent", () => {
 | 
			
		||||
 | 
			
		||||
        beforeEach(async () => {
 | 
			
		||||
        beforeEach(async (done) => {
 | 
			
		||||
            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 () => {
 | 
			
		||||
@@ -142,15 +143,15 @@ describe("The Duplicate Action plugin", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe("when a new name is provided for the duplicated object", () => {
 | 
			
		||||
        it("the name is updated", () => {
 | 
			
		||||
            const NEW_NAME = 'New Name';
 | 
			
		||||
            let childName;
 | 
			
		||||
        const NEW_NAME = 'New Name';
 | 
			
		||||
 | 
			
		||||
        beforeEach(() => {
 | 
			
		||||
            duplicateTask = new DuplicateAction(openmct);
 | 
			
		||||
            duplicateTask.updateNameCheck(parentObject, NEW_NAME);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
            childName = parentObject.name;
 | 
			
		||||
 | 
			
		||||
        it("the name is updated", () => {
 | 
			
		||||
            let childName = parentObject.name;
 | 
			
		||||
            expect(childName).toEqual(NEW_NAME);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
        'is-alias': item.isAlias === true,
 | 
			
		||||
        'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
 | 
			
		||||
    }, statusClass]"
 | 
			
		||||
    @click="navigate"
 | 
			
		||||
    :href="objectLink"
 | 
			
		||||
>
 | 
			
		||||
    <div
 | 
			
		||||
        class="c-grid-item__type-icon"
 | 
			
		||||
@@ -49,17 +49,11 @@ 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>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
            ref="objectLink"
 | 
			
		||||
            class="c-object-label"
 | 
			
		||||
            :class="[statusClass]"
 | 
			
		||||
            @click="navigate"
 | 
			
		||||
            :href="objectLink"
 | 
			
		||||
        >
 | 
			
		||||
            <div
 | 
			
		||||
                class="c-object-label__type-icon c-list-item__name__type-icon"
 | 
			
		||||
@@ -45,7 +45,6 @@ import statusListener from './status-listener';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    mixins: [contextMenuGesture, objectLink, statusListener],
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    props: {
 | 
			
		||||
        item: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -57,7 +56,7 @@ export default {
 | 
			
		||||
            return moment(timestamp).format(format);
 | 
			
		||||
        },
 | 
			
		||||
        navigate() {
 | 
			
		||||
            this.openmct.router.navigate(this.objectLink);
 | 
			
		||||
            this.$refs.objectLink.click();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -19,15 +19,6 @@
 | 
			
		||||
            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 */
 | 
			
		||||
 
 | 
			
		||||
@@ -22,9 +22,4 @@
 | 
			
		||||
            @include isAlias();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [class*='l-overlay'] & {
 | 
			
		||||
        // When this view is in an overlay, prevent navigation
 | 
			
		||||
        pointer-events: none;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ export default class GoToOriginalAction {
 | 
			
		||||
                    .slice(1)
 | 
			
		||||
                    .join('/');
 | 
			
		||||
 | 
			
		||||
                this._openmct.router.navigate(url);
 | 
			
		||||
                window.location.href = url;
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
    appliesTo(objectPath) {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,15 +24,10 @@ import {
 | 
			
		||||
    resetApplicationState
 | 
			
		||||
} from 'utils/testing';
 | 
			
		||||
 | 
			
		||||
describe("the goToOriginalAction plugin", () => {
 | 
			
		||||
describe("the plugin", () => {
 | 
			
		||||
    let openmct;
 | 
			
		||||
    let goToOriginalAction;
 | 
			
		||||
    let mockRootFolder;
 | 
			
		||||
    let mockSubFolder;
 | 
			
		||||
    let mockSubSubFolder;
 | 
			
		||||
    let mockObject;
 | 
			
		||||
    let goToFolderAction;
 | 
			
		||||
    let mockObjectPath;
 | 
			
		||||
    let hash;
 | 
			
		||||
 | 
			
		||||
    beforeEach((done) => {
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
@@ -40,7 +35,7 @@ describe("the goToOriginalAction plugin", () => {
 | 
			
		||||
        openmct.on('start', done);
 | 
			
		||||
        openmct.startHeadless();
 | 
			
		||||
 | 
			
		||||
        goToOriginalAction = openmct.actions._allActions.goToOriginal;
 | 
			
		||||
        goToFolderAction = openmct.actions._allActions.goToOriginal;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
@@ -48,153 +43,31 @@ describe("the goToOriginalAction plugin", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('installs the go to folder action', () => {
 | 
			
		||||
        expect(goToOriginalAction).toBeDefined();
 | 
			
		||||
        expect(goToFolderAction).toBeDefined();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('when invoked', () => {
 | 
			
		||||
 | 
			
		||||
        beforeEach(() => {
 | 
			
		||||
            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);
 | 
			
		||||
            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);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('goes to the original location', () => {
 | 
			
		||||
            const originalLocationHash = '#/browse/mock-root/mock-table';
 | 
			
		||||
 | 
			
		||||
            return waitForNavigation(() => {
 | 
			
		||||
                return hash === originalLocationHash;
 | 
			
		||||
            }).then(() => {
 | 
			
		||||
                expect(hash).toEqual(originalLocationHash);
 | 
			
		||||
            });
 | 
			
		||||
            expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    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];
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,51 +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.
 | 
			
		||||
*****************************************************************************/
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
 | 
			
		||||
<a class="c-hyperlink"
 | 
			
		||||
   :class="{
 | 
			
		||||
       'c-hyperlink--button' : isButton
 | 
			
		||||
   }"
 | 
			
		||||
   :target="domainObject.linkTarget"
 | 
			
		||||
   :href="domainObject.url"
 | 
			
		||||
>
 | 
			
		||||
    <span class="c-hyperlink__label">{{ domainObject.displayText }}</span>
 | 
			
		||||
</a>
 | 
			
		||||
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['domainObject'],
 | 
			
		||||
    computed: {
 | 
			
		||||
        isButton() {
 | 
			
		||||
            if (this.domainObject.displayFormat === "link") {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,89 +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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import HyperlinkProvider from './HyperlinkProvider';
 | 
			
		||||
 | 
			
		||||
export default function () {
 | 
			
		||||
    return function install(openmct) {
 | 
			
		||||
        openmct.types.addType('hyperlink', {
 | 
			
		||||
            name: 'Hyperlink',
 | 
			
		||||
            key: 'hyperlink',
 | 
			
		||||
            description: 'A hyperlink to redirect to a different link',
 | 
			
		||||
            creatable: true,
 | 
			
		||||
            cssClass: 'icon-chain-links',
 | 
			
		||||
            initialize: function (domainObject) {
 | 
			
		||||
                domainObject.displayFormat = "link";
 | 
			
		||||
                domainObject.linkTarget = "_self";
 | 
			
		||||
            },
 | 
			
		||||
            form: [
 | 
			
		||||
                {
 | 
			
		||||
                    "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"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "name": "Button",
 | 
			
		||||
                            "value": "button"
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    "cssClass": "l-inline"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "linkTarget",
 | 
			
		||||
                    "name": "Tab to Open Hyperlink",
 | 
			
		||||
                    "control": "select",
 | 
			
		||||
                    "options": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "name": "Open in this tab",
 | 
			
		||||
                            "value": "_self"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "name": "Open in a new tab",
 | 
			
		||||
                            "value": "_blank"
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    "cssClass": "l-inline"
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        });
 | 
			
		||||
        openmct.objectViews.addProvider(new HyperlinkProvider(openmct));
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user