Compare commits
	
		
			64 Commits
		
	
	
		
			example-im
			...
			fix-4090
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					81f4207456 | ||
| 
						 | 
					4ba8f893a6 | ||
| 
						 | 
					c4b9be18f1 | ||
| 
						 | 
					eabdf6cd04 | ||
| 
						 | 
					e56c673005 | ||
| 
						 | 
					dad9f12a5c | ||
| 
						 | 
					aa5edb0b83 | ||
| 
						 | 
					b315803180 | ||
| 
						 | 
					b27317631b | ||
| 
						 | 
					953a9daafb | ||
| 
						 | 
					63f9cd449f | ||
| 
						 | 
					54220f547b | ||
| 
						 | 
					93d967c2b3 | ||
| 
						 | 
					1226459c6f | ||
| 
						 | 
					d7c9c9cb98 | ||
| 
						 | 
					2131ef2397 | ||
| 
						 | 
					48c22369a1 | ||
| 
						 | 
					6506077f4d | ||
| 
						 | 
					b1b4266ff3 | ||
| 
						 | 
					42b0148f93 | ||
| 
						 | 
					9461ad8edd | ||
| 
						 | 
					40055ba955 | ||
| 
						 | 
					9cb85ad176 | ||
| 
						 | 
					f2b2953a5d | ||
| 
						 | 
					62de310686 | ||
| 
						 | 
					4b9ff67e49 | ||
| 
						 | 
					d5e32ec494 | ||
| 
						 | 
					38880ba3d1 | ||
| 
						 | 
					a99ce7733c | ||
| 
						 | 
					9f48764210 | ||
| 
						 | 
					a1aaa0dd41 | ||
| 
						 | 
					bee15e98c8 | ||
| 
						 | 
					092bbe547d | ||
| 
						 | 
					6cbe05317c | ||
| 
						 | 
					3b92fcdf6c | ||
| 
						 | 
					6dde54bd25 | ||
| 
						 | 
					359e7377ac | ||
| 
						 | 
					9f4190f781 | ||
| 
						 | 
					f3fc991a74 | ||
| 
						 | 
					2564e75fc9 | ||
| 
						 | 
					f42fe78acf | ||
| 
						 | 
					fe928a1386 | ||
| 
						 | 
					b329ed6ed5 | ||
| 
						 | 
					9b7a0d7e4c | ||
| 
						 | 
					5c15e53abb | ||
| 
						 | 
					f58b3881f2 | ||
| 
						 | 
					071a13b219 | ||
| 
						 | 
					ca66898e51 | ||
| 
						 | 
					94c7b2343a | ||
| 
						 | 
					c397c336ab | ||
| 
						 | 
					eea23f2caf | ||
| 
						 | 
					6665641c02 | ||
| 
						 | 
					c3ebf52dd2 | ||
| 
						 | 
					f8f2e7da9b | ||
| 
						 | 
					240f58b2d0 | ||
| 
						 | 
					7d3baee7b5 | ||
| 
						 | 
					1f5cb7ca42 | ||
| 
						 | 
					4a7ebe326c | ||
| 
						 | 
					10da314a4a | ||
| 
						 | 
					b3ceccd7fb | ||
| 
						 | 
					1bde4c9a0c | ||
| 
						 | 
					4b85360446 | ||
| 
						 | 
					41b860a547 | ||
| 
						 | 
					254b3db966 | 
@@ -1,36 +1,93 @@
 | 
			
		||||
version: 2
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
version: 2.1
 | 
			
		||||
executors:
 | 
			
		||||
  linux:
 | 
			
		||||
    docker:
 | 
			
		||||
        - image: circleci/node:13-browsers
 | 
			
		||||
          environment:
 | 
			
		||||
            CHROME_BIN: "/usr/bin/google-chrome"
 | 
			
		||||
    steps:
 | 
			
		||||
        - checkout
 | 
			
		||||
        - run:
 | 
			
		||||
            name: Update npm
 | 
			
		||||
            command: 'sudo npm install -g npm@latest'
 | 
			
		||||
        - restore_cache:
 | 
			
		||||
            key: dependency-cache-{{ checksum "package.json" }}
 | 
			
		||||
        - run:
 | 
			
		||||
            name: Installing dependencies (npm install)
 | 
			
		||||
            command: npm install
 | 
			
		||||
        - save_cache:
 | 
			
		||||
            key: dependency-cache-{{ checksum "package.json" }}
 | 
			
		||||
            paths:
 | 
			
		||||
              - node_modules
 | 
			
		||||
        - run:
 | 
			
		||||
            name: npm run test:coverage
 | 
			
		||||
            command: npm run test:coverage
 | 
			
		||||
        - run:
 | 
			
		||||
            name: npm run lint
 | 
			
		||||
            command: npm run lint
 | 
			
		||||
        - store_artifacts:
 | 
			
		||||
            path: dist
 | 
			
		||||
            prefix: dist
 | 
			
		||||
 | 
			
		||||
workflows:
 | 
			
		||||
  version: 2
 | 
			
		||||
      - image: cimg/base:stable
 | 
			
		||||
orbs:
 | 
			
		||||
  node: circleci/node@4.5.1
 | 
			
		||||
  browser-tools: circleci/browser-tools@1.1.3
 | 
			
		||||
jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    parameters:
 | 
			
		||||
      node-version:
 | 
			
		||||
        type: string
 | 
			
		||||
      browser:
 | 
			
		||||
        type: string
 | 
			
		||||
      always-pass:
 | 
			
		||||
        type: boolean  
 | 
			
		||||
    executor: linux
 | 
			
		||||
    steps:
 | 
			
		||||
      - checkout
 | 
			
		||||
      - restore_cache:
 | 
			
		||||
          key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}
 | 
			
		||||
      - node/install:
 | 
			
		||||
          node-version: << parameters.node-version >>
 | 
			
		||||
      - node/install-packages:
 | 
			
		||||
          override-ci-command: npm install
 | 
			
		||||
      - when: # Just to save time until caching saves the browser bin
 | 
			
		||||
          condition:
 | 
			
		||||
            equal: [ "FirefoxESR", <<parameters.browser>> ]
 | 
			
		||||
          steps:
 | 
			
		||||
            - browser-tools/install-firefox:
 | 
			
		||||
                version: "78.11.0esr" #https://archive.mozilla.org/pub/firefox/releases/          
 | 
			
		||||
      - when: # Just to save time until caching saves the browser bin
 | 
			
		||||
          condition:
 | 
			
		||||
            equal: [ "ChromeHeadless", <<parameters.browser>> ]
 | 
			
		||||
          steps:
 | 
			
		||||
            - browser-tools/install-chrome:
 | 
			
		||||
                replace-existing: false
 | 
			
		||||
      - save_cache:
 | 
			
		||||
          key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}
 | 
			
		||||
          paths:
 | 
			
		||||
            - ~/.npm
 | 
			
		||||
            - ~/.cache
 | 
			
		||||
            - node_modules
 | 
			
		||||
      - run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
 | 
			
		||||
      - store_test_results:
 | 
			
		||||
          path: dist/reports/tests/
 | 
			
		||||
      - store_artifacts:
 | 
			
		||||
          path: dist/reports/
 | 
			
		||||
workflows:
 | 
			
		||||
  matrix-tests:
 | 
			
		||||
    jobs:
 | 
			
		||||
      - build
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node10-chrome
 | 
			
		||||
          node-version: lts/dubnium
 | 
			
		||||
          browser: ChromeHeadless
 | 
			
		||||
          always-pass: false
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node12-firefoxESR-build-only
 | 
			
		||||
          node-version: lts/erbium
 | 
			
		||||
          browser: FirefoxESR
 | 
			
		||||
          always-pass: true
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node14-chrome-build-only
 | 
			
		||||
          node-version: lts/fermium
 | 
			
		||||
          browser: ChromeHeadless
 | 
			
		||||
          always-pass: true
 | 
			
		||||
  nightly:
 | 
			
		||||
    jobs:
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node10-chrome-nightly
 | 
			
		||||
          node-version: lts/dubnium
 | 
			
		||||
          browser: ChromeHeadless
 | 
			
		||||
          always-pass: false
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node12-firefoxESR-nightly
 | 
			
		||||
          node-version: lts/erbium
 | 
			
		||||
          browser: FirefoxESR
 | 
			
		||||
          always-pass: false
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node14-chrome-nightly
 | 
			
		||||
          node-version: lts/fermium
 | 
			
		||||
          browser: ChromeHeadless
 | 
			
		||||
          always-pass: false
 | 
			
		||||
    triggers:
 | 
			
		||||
      - schedule:
 | 
			
		||||
          cron: "0 0 * * *"
 | 
			
		||||
          filters:
 | 
			
		||||
            branches:
 | 
			
		||||
              only:
 | 
			
		||||
                - master      
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,12 @@
 | 
			
		||||
<!--- This is for filing bugs. If you have a general question, please -->
 | 
			
		||||
<!--- visit https://github.com/nasa/openmct/discussions -->
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
name: Bug Report
 | 
			
		||||
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. -->
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +36,7 @@ about: File a Bug !
 | 
			
		||||
 | 
			
		||||
#### Environment
 | 
			
		||||
* Open MCT Version: <!--- date of build, version, or SHA -->
 | 
			
		||||
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yams? -->
 | 
			
		||||
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yamcs? -->
 | 
			
		||||
* OS:
 | 
			
		||||
* Browser:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							@@ -1 +1,5 @@
 | 
			
		||||
blank_issues_enabled: false
 | 
			
		||||
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
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.github/ISSUE_TEMPLATE/enhancement-request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
---
 | 
			
		||||
name: Enhancement request
 | 
			
		||||
about: Suggest an enhancement or new improvement for this project
 | 
			
		||||
title: ''
 | 
			
		||||
labels: type:enhancement
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
**Is your feature request related to a problem? Please describe.**
 | 
			
		||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
 | 
			
		||||
 | 
			
		||||
**Describe the solution you'd like**
 | 
			
		||||
A clear and concise description of what you want to happen.
 | 
			
		||||
 | 
			
		||||
**Describe alternatives you've considered**
 | 
			
		||||
A clear and concise description of any alternative solutions or features you've considered.
 | 
			
		||||
 | 
			
		||||
**Additional context**
 | 
			
		||||
Add any other context or screenshots about the feature request here.
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
 | 
			
		||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
 | 
			
		||||
* [ ] Is this change backwards compatible? Will users need to change how they are calling the API, or how they've extended core plugins such as Tables or Plots?
 | 
			
		||||
 | 
			
		||||
### Author Checklist
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
 | 
			
		||||
name: "CodeQL"
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [ master ]
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '28 21 * * 3'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  analyze:
 | 
			
		||||
    name: Analyze
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    permissions:
 | 
			
		||||
      actions: read
 | 
			
		||||
      contents: read
 | 
			
		||||
      security-events: write
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout repository
 | 
			
		||||
      uses: actions/checkout@v2
 | 
			
		||||
 | 
			
		||||
    # Initializes the CodeQL tools for scanning.
 | 
			
		||||
    - name: Initialize CodeQL
 | 
			
		||||
      uses: github/codeql-action/init@v1
 | 
			
		||||
      with:
 | 
			
		||||
        languages: javascript
 | 
			
		||||
 | 
			
		||||
    - name: Autobuild
 | 
			
		||||
      uses: github/codeql-action/autobuild@v1
 | 
			
		||||
 | 
			
		||||
    - name: Perform CodeQL Analysis
 | 
			
		||||
      uses: github/codeql-action/analyze@v1
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/lighthouse.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/lighthouse.yml
									
									
									
									
										vendored
									
									
								
							@@ -13,6 +13,8 @@ jobs:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
        with:
 | 
			
		||||
          ref: ${{ github.event.inputs.version }}
 | 
			
		||||
      - uses: actions/setup-node@v1
 | 
			
		||||
      - 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
 | 
			
		||||
							
								
								
									
										2
									
								
								API.md
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								API.md
									
									
									
									
									
								
							@@ -996,7 +996,7 @@ reveal additional information when the mouse cursor is hovered over it.
 | 
			
		||||
A common use case for indicators is to convey the state of some external system such as a 
 | 
			
		||||
persistence backend or HTTP server. So long as this system is accessible via HTTP request, 
 | 
			
		||||
Open MCT provides a general purpose indicator to show whether the server is available and 
 | 
			
		||||
returing a 2xx status code. The URL Status Indicator is made available as a default plugin. See
 | 
			
		||||
returning a 2xx status code. The URL Status Indicator is made available as a default plugin. See
 | 
			
		||||
the [documentation](./src/plugins/URLIndicatorPlugin) for details on how to install and configure the 
 | 
			
		||||
URL Status Indicator.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -317,6 +317,7 @@ checklist).
 | 
			
		||||
### Reviewer Checklist
 | 
			
		||||
 | 
			
		||||
* [ ] Changes appear to address issue?
 | 
			
		||||
* [ ] Changes appear not to be breaking changes?
 | 
			
		||||
* [ ] Appropriate unit tests included?
 | 
			
		||||
* [ ] Code style and in-line documentation are appropriate?
 | 
			
		||||
* [ ] Commit messages meet standards?
 | 
			
		||||
 
 | 
			
		||||
@@ -423,7 +423,7 @@ which can help with this, however.
 | 
			
		||||
  instead of separate approaches for static and substitutable
 | 
			
		||||
  dependencies.
 | 
			
		||||
* Removes need to understand Angular's DI mechanism.
 | 
			
		||||
* Improves useability of documentation (`typeService` is an
 | 
			
		||||
* Improves usability of documentation (`typeService` is an
 | 
			
		||||
  instance of `CompositeService` and implements `TypeService`
 | 
			
		||||
  so you can easily traverse links in the JSDoc.)
 | 
			
		||||
* Can be used more easily from Web Workers, allowing services
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
## Legacy Documentation
 | 
			
		||||
 | 
			
		||||
As we transition to a new API, the following documentation for the old API
 | 
			
		||||
(which is supported during the transtion) may be useful as well:
 | 
			
		||||
(which is supported during the transition) may be useful as well:
 | 
			
		||||
 | 
			
		||||
 * The [Architecture Overview](architecture/) describes the concepts used
 | 
			
		||||
 throughout Open MCT, and gives a high level overview of the platform's design.
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,15 @@ define([
 | 
			
		||||
                        domain: 2
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "cos",
 | 
			
		||||
                    name: "Cosine",
 | 
			
		||||
                    unit: "deg",
 | 
			
		||||
                    formatString: '%0.2f',
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        domain: 3
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                // Need to enable "LocalTimeSystem" plugin to make use of this
 | 
			
		||||
                // {
 | 
			
		||||
                //     key: "local",
 | 
			
		||||
@@ -109,6 +118,100 @@ define([
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        'example.spectral-generator': {
 | 
			
		||||
            values: [
 | 
			
		||||
                {
 | 
			
		||||
                    key: "name",
 | 
			
		||||
                    name: "Name",
 | 
			
		||||
                    format: "string"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "utc",
 | 
			
		||||
                    name: "Time",
 | 
			
		||||
                    format: "utc",
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        domain: 1
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "wavelength",
 | 
			
		||||
                    name: "Wavelength",
 | 
			
		||||
                    unit: "Hz",
 | 
			
		||||
                    formatString: '%0.2f',
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        domain: 2,
 | 
			
		||||
                        spectralAttribute: true
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "cos",
 | 
			
		||||
                    name: "Cosine",
 | 
			
		||||
                    unit: "deg",
 | 
			
		||||
                    formatString: '%0.2f',
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        range: 2,
 | 
			
		||||
                        spectralAttribute: true
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        'example.spectral-aggregate-generator': {
 | 
			
		||||
            values: [
 | 
			
		||||
                {
 | 
			
		||||
                    key: "name",
 | 
			
		||||
                    name: "Name",
 | 
			
		||||
                    format: "string"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "utc",
 | 
			
		||||
                    name: "Time",
 | 
			
		||||
                    format: "utc",
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        domain: 1
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "ch1",
 | 
			
		||||
                    name: "Channel 1",
 | 
			
		||||
                    format: "string",
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        range: 1
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "ch2",
 | 
			
		||||
                    name: "Channel 2",
 | 
			
		||||
                    format: "string",
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        range: 2
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "ch3",
 | 
			
		||||
                    name: "Channel 3",
 | 
			
		||||
                    format: "string",
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        range: 3
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "ch4",
 | 
			
		||||
                    name: "Channel 4",
 | 
			
		||||
                    format: "string",
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        range: 4
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "ch5",
 | 
			
		||||
                    name: "Channel 5",
 | 
			
		||||
                    format: "string",
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        range: 5
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										86
									
								
								example/generator/SpectralAggregateGeneratorProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								example/generator/SpectralAggregateGeneratorProvider.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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([
 | 
			
		||||
 | 
			
		||||
], function (
 | 
			
		||||
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    function SpectralAggregateGeneratorProvider() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function pointForTimestamp(timestamp, count, name) {
 | 
			
		||||
        return {
 | 
			
		||||
            name: name,
 | 
			
		||||
            utc: String(Math.floor(timestamp / count) * count),
 | 
			
		||||
            ch1: String(Math.floor(timestamp / count) % 1),
 | 
			
		||||
            ch2: String(Math.floor(timestamp / count) % 2),
 | 
			
		||||
            ch3: String(Math.floor(timestamp / count) % 3),
 | 
			
		||||
            ch4: String(Math.floor(timestamp / count) % 4),
 | 
			
		||||
            ch5: String(Math.floor(timestamp / count) % 5)
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SpectralAggregateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
 | 
			
		||||
        return domainObject.type === 'example.spectral-aggregate-generator';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    SpectralAggregateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
 | 
			
		||||
        var count = 5000;
 | 
			
		||||
 | 
			
		||||
        var interval = setInterval(function () {
 | 
			
		||||
            var now = Date.now();
 | 
			
		||||
            var datum = pointForTimestamp(now, count, domainObject.name);
 | 
			
		||||
            callback(datum);
 | 
			
		||||
        }, count);
 | 
			
		||||
 | 
			
		||||
        return function () {
 | 
			
		||||
            clearInterval(interval);
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    SpectralAggregateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
 | 
			
		||||
        return domainObject.type === 'example.spectral-aggregate-generator';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    SpectralAggregateGeneratorProvider.prototype.request = function (domainObject, options) {
 | 
			
		||||
        var start = options.start;
 | 
			
		||||
        var end = Math.min(Date.now(), options.end); // no future values
 | 
			
		||||
        var count = 5000;
 | 
			
		||||
        if (options.strategy === 'latest' || options.size === 1) {
 | 
			
		||||
            start = end;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var data = [];
 | 
			
		||||
        while (start <= end && data.length < 5000) {
 | 
			
		||||
            data.push(pointForTimestamp(start, count, domainObject.name));
 | 
			
		||||
            start += count;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve(data);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return SpectralAggregateGeneratorProvider;
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										102
									
								
								example/generator/SpectralGeneratorProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								example/generator/SpectralGeneratorProvider.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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([
 | 
			
		||||
    './WorkerInterface'
 | 
			
		||||
], function (
 | 
			
		||||
    WorkerInterface
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    var REQUEST_DEFAULTS = {
 | 
			
		||||
        amplitude: 1,
 | 
			
		||||
        wavelength: 1,
 | 
			
		||||
        period: 10,
 | 
			
		||||
        offset: 0,
 | 
			
		||||
        dataRateInHz: 1,
 | 
			
		||||
        randomness: 0,
 | 
			
		||||
        phase: 0
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function SpectralGeneratorProvider() {
 | 
			
		||||
        this.workerInterface = new WorkerInterface();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SpectralGeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
 | 
			
		||||
        return domainObject.type === 'example.spectral-generator';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    SpectralGeneratorProvider.prototype.supportsRequest =
 | 
			
		||||
        SpectralGeneratorProvider.prototype.supportsSubscribe =
 | 
			
		||||
            SpectralGeneratorProvider.prototype.canProvideTelemetry;
 | 
			
		||||
 | 
			
		||||
    SpectralGeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request = {}) {
 | 
			
		||||
        var props = [
 | 
			
		||||
            'amplitude',
 | 
			
		||||
            'wavelength',
 | 
			
		||||
            'period',
 | 
			
		||||
            'offset',
 | 
			
		||||
            'dataRateInHz',
 | 
			
		||||
            'phase',
 | 
			
		||||
            'randomness'
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        var workerRequest = {};
 | 
			
		||||
 | 
			
		||||
        props.forEach(function (prop) {
 | 
			
		||||
            if (domainObject.telemetry && Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)) {
 | 
			
		||||
                workerRequest[prop] = domainObject.telemetry[prop];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
 | 
			
		||||
                workerRequest[prop] = request[prop];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
 | 
			
		||||
                workerRequest[prop] = REQUEST_DEFAULTS[prop];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            workerRequest[prop] = Number(workerRequest[prop]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        workerRequest.name = domainObject.name;
 | 
			
		||||
 | 
			
		||||
        return workerRequest;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    SpectralGeneratorProvider.prototype.request = function (domainObject, request) {
 | 
			
		||||
        var workerRequest = this.makeWorkerRequest(domainObject, request);
 | 
			
		||||
        workerRequest.start = request.start;
 | 
			
		||||
        workerRequest.end = request.end;
 | 
			
		||||
        workerRequest.spectra = true;
 | 
			
		||||
 | 
			
		||||
        return this.workerInterface.request(workerRequest);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    SpectralGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
 | 
			
		||||
        var workerRequest = this.makeWorkerRequest(domainObject, {});
 | 
			
		||||
        workerRequest.spectra = true;
 | 
			
		||||
 | 
			
		||||
        return this.workerInterface.subscribe(workerRequest, callback);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return SpectralGeneratorProvider;
 | 
			
		||||
});
 | 
			
		||||
@@ -63,7 +63,7 @@ define([
 | 
			
		||||
 | 
			
		||||
    StateGeneratorProvider.prototype.request = function (domainObject, options) {
 | 
			
		||||
        var start = options.start;
 | 
			
		||||
        var end = options.end;
 | 
			
		||||
        var end = Math.min(Date.now(), options.end); // no future values
 | 
			
		||||
        var duration = domainObject.telemetry.duration * 1000;
 | 
			
		||||
        if (options.strategy === 'latest' || options.size === 1) {
 | 
			
		||||
            start = end;
 | 
			
		||||
 
 | 
			
		||||
@@ -54,23 +54,38 @@
 | 
			
		||||
        var start = Date.now();
 | 
			
		||||
        var step = 1000 / data.dataRateInHz;
 | 
			
		||||
        var nextStep = start - (start % step) + step;
 | 
			
		||||
        let work;
 | 
			
		||||
        if (data.spectra) {
 | 
			
		||||
            work = function (now) {
 | 
			
		||||
                while (nextStep < now) {
 | 
			
		||||
                    const messageCopy = Object.create(message);
 | 
			
		||||
                    message.data.start = nextStep - (60 * 1000);
 | 
			
		||||
                    message.data.end = nextStep;
 | 
			
		||||
                    onRequest(messageCopy);
 | 
			
		||||
                    nextStep += step;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        function work(now) {
 | 
			
		||||
            while (nextStep < now) {
 | 
			
		||||
                self.postMessage({
 | 
			
		||||
                    id: message.id,
 | 
			
		||||
                    data: {
 | 
			
		||||
                        name: data.name,
 | 
			
		||||
                        utc: nextStep,
 | 
			
		||||
                        yesterday: nextStep - 60 * 60 * 24 * 1000,
 | 
			
		||||
                        sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
 | 
			
		||||
                        cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                nextStep += step;
 | 
			
		||||
            }
 | 
			
		||||
                return nextStep;
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            work = function (now) {
 | 
			
		||||
                while (nextStep < now) {
 | 
			
		||||
                    self.postMessage({
 | 
			
		||||
                        id: message.id,
 | 
			
		||||
                        data: {
 | 
			
		||||
                            name: data.name,
 | 
			
		||||
                            utc: nextStep,
 | 
			
		||||
                            yesterday: nextStep - 60 * 60 * 24 * 1000,
 | 
			
		||||
                            sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
 | 
			
		||||
                            wavelength: wavelength(start, nextStep),
 | 
			
		||||
                            cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    nextStep += step;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            return nextStep;
 | 
			
		||||
                return nextStep;
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        subscriptions[message.id] = work;
 | 
			
		||||
@@ -111,13 +126,21 @@
 | 
			
		||||
                utc: nextStep,
 | 
			
		||||
                yesterday: nextStep - 60 * 60 * 24 * 1000,
 | 
			
		||||
                sin: sin(nextStep, period, amplitude, offset, phase, randomness),
 | 
			
		||||
                wavelength: wavelength(start, nextStep),
 | 
			
		||||
                cos: cos(nextStep, period, amplitude, offset, phase, randomness)
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.postMessage({
 | 
			
		||||
            id: message.id,
 | 
			
		||||
            data: data
 | 
			
		||||
            data: request.spectra ? {
 | 
			
		||||
                wavelength: data.map((item) => {
 | 
			
		||||
                    return item.wavelength;
 | 
			
		||||
                }),
 | 
			
		||||
                cos: data.map((item) => {
 | 
			
		||||
                    return item.cos;
 | 
			
		||||
                })
 | 
			
		||||
            } : data
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -131,6 +154,10 @@
 | 
			
		||||
            * Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function wavelength(start, nextStep) {
 | 
			
		||||
        return (nextStep - start) / 10;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function sendError(error, message) {
 | 
			
		||||
        self.postMessage({
 | 
			
		||||
            error: error.name + ': ' + error.message,
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,15 @@ define([
 | 
			
		||||
    "./GeneratorProvider",
 | 
			
		||||
    "./SinewaveLimitProvider",
 | 
			
		||||
    "./StateGeneratorProvider",
 | 
			
		||||
    "./SpectralGeneratorProvider",
 | 
			
		||||
    "./SpectralAggregateGeneratorProvider",
 | 
			
		||||
    "./GeneratorMetadataProvider"
 | 
			
		||||
], function (
 | 
			
		||||
    GeneratorProvider,
 | 
			
		||||
    SinewaveLimitProvider,
 | 
			
		||||
    StateGeneratorProvider,
 | 
			
		||||
    SpectralGeneratorProvider,
 | 
			
		||||
    SpectralAggregateGeneratorProvider,
 | 
			
		||||
    GeneratorMetadataProvider
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
@@ -61,6 +65,37 @@ define([
 | 
			
		||||
 | 
			
		||||
        openmct.telemetry.addProvider(new StateGeneratorProvider());
 | 
			
		||||
 | 
			
		||||
        openmct.types.addType("example.spectral-generator", {
 | 
			
		||||
            name: "Spectral Generator",
 | 
			
		||||
            description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
 | 
			
		||||
            cssClass: "icon-generator-telemetry",
 | 
			
		||||
            creatable: true,
 | 
			
		||||
            initialize: function (object) {
 | 
			
		||||
                object.telemetry = {
 | 
			
		||||
                    period: 10,
 | 
			
		||||
                    amplitude: 1,
 | 
			
		||||
                    wavelength: 1,
 | 
			
		||||
                    frequency: 1,
 | 
			
		||||
                    offset: 0,
 | 
			
		||||
                    dataRateInHz: 1,
 | 
			
		||||
                    phase: 0,
 | 
			
		||||
                    randomness: 0
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        openmct.telemetry.addProvider(new SpectralGeneratorProvider());
 | 
			
		||||
 | 
			
		||||
        openmct.types.addType("example.spectral-aggregate-generator", {
 | 
			
		||||
            name: "Spectral Aggregate Generator",
 | 
			
		||||
            description: "For development use. Generates example streaming telemetry data using a simple state algorithm.",
 | 
			
		||||
            cssClass: "icon-generator-telemetry",
 | 
			
		||||
            creatable: true,
 | 
			
		||||
            initialize: function (object) {
 | 
			
		||||
                object.telemetry = {};
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        openmct.telemetry.addProvider(new SpectralAggregateGeneratorProvider());
 | 
			
		||||
 | 
			
		||||
        openmct.types.addType("generator", {
 | 
			
		||||
            name: "Sine Wave Generator",
 | 
			
		||||
            description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import Vue from 'Vue';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import HelloWorld from './HelloWorld.vue';
 | 
			
		||||
 | 
			
		||||
function SimpleVuePlugin() {
 | 
			
		||||
 
 | 
			
		||||
@@ -152,7 +152,7 @@
 | 
			
		||||
        <h2>How to Use Glyphs</h2>
 | 
			
		||||
        <div class="cols cols1-1">
 | 
			
		||||
            <div class="col">
 | 
			
		||||
                <p>The easiest way to use a glyph is to include its CSS class in an element. The CSS adds a psuedo <code>:before</code> HTML element to whatever element it's attached to that makes proper use of the symbols font.</p>
 | 
			
		||||
                <p>The easiest way to use a glyph is to include its CSS class in an element. The CSS adds a pseudo <code>:before</code> HTML element to whatever element it's attached to that makes proper use of the symbols font.</p>
 | 
			
		||||
                <p>Alternately, you can use the <code>.ui-symbol</code> class in an object that contains encoded HTML entities. This method is only recommended if you cannot use the aforementioned CSS class approach.</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <mct-example><a class="s-button icon-gear" title="Settings"></a>
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,7 @@
 | 
			
		||||
        openmct.install(openmct.plugins.ExampleImagery());
 | 
			
		||||
        openmct.install(openmct.plugins.PlanLayout());
 | 
			
		||||
        openmct.install(openmct.plugins.Timeline());
 | 
			
		||||
        openmct.install(openmct.plugins.Hyperlink());
 | 
			
		||||
        openmct.install(openmct.plugins.UTCTimeSystem());
 | 
			
		||||
        openmct.install(openmct.plugins.AutoflowView({
 | 
			
		||||
            type: "telemetry.panel"
 | 
			
		||||
@@ -194,6 +195,7 @@
 | 
			
		||||
            ['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
 | 
			
		||||
            {indicator: true}
 | 
			
		||||
        ));
 | 
			
		||||
        openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
 | 
			
		||||
        openmct.start();
 | 
			
		||||
    </script>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,9 @@
 | 
			
		||||
/*global module,process*/
 | 
			
		||||
 | 
			
		||||
const devMode = process.env.NODE_ENV !== 'production';
 | 
			
		||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'FirefoxHeadless'];
 | 
			
		||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
 | 
			
		||||
const coverageEnabled = process.env.COVERAGE === 'true';
 | 
			
		||||
const reporters = ['progress', 'html'];
 | 
			
		||||
const reporters = ['progress', 'html', 'junit'];
 | 
			
		||||
 | 
			
		||||
if (coverageEnabled) {
 | 
			
		||||
    reporters.push('coverage-istanbul');
 | 
			
		||||
@@ -59,7 +59,8 @@ module.exports = (config) => {
 | 
			
		||||
        browsers: browsers,
 | 
			
		||||
        client: {
 | 
			
		||||
            jasmine: {
 | 
			
		||||
                random: false
 | 
			
		||||
                random: false,
 | 
			
		||||
                timeoutInterval: 30000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        customLaunchers: {
 | 
			
		||||
@@ -67,6 +68,10 @@ module.exports = (config) => {
 | 
			
		||||
                base: 'Chrome',
 | 
			
		||||
                flags: ['--remote-debugging-port=9222'],
 | 
			
		||||
                debug: true
 | 
			
		||||
            },
 | 
			
		||||
            FirefoxESR: {
 | 
			
		||||
                base: 'FirefoxHeadless',
 | 
			
		||||
                name: 'FirefoxESR'
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        colors: true,
 | 
			
		||||
@@ -78,12 +83,21 @@ module.exports = (config) => {
 | 
			
		||||
            preserveDescribeNesting: true,
 | 
			
		||||
            foldAll: false
 | 
			
		||||
        },
 | 
			
		||||
        browserConsoleLogOptions: { level: "error",  format: "%b %T: %m",  terminal: true },
 | 
			
		||||
        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: {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								package.json
									
									
									
									
									
								
							@@ -1,8 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "openmct",
 | 
			
		||||
  "version": "1.7.4",
 | 
			
		||||
  "version": "1.7.8-SNAPSHOT",
 | 
			
		||||
  "description": "The Open MCT core platform",
 | 
			
		||||
  "dependencies": {},
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "angular": ">=1.8.0",
 | 
			
		||||
    "angular-route": "1.4.14",
 | 
			
		||||
@@ -12,16 +11,9 @@
 | 
			
		||||
    "copy-webpack-plugin": "^4.5.2",
 | 
			
		||||
    "cross-env": "^6.0.3",
 | 
			
		||||
    "css-loader": "^1.0.0",
 | 
			
		||||
    "d3-array": "1.2.x",
 | 
			
		||||
    "d3-axis": "1.0.x",
 | 
			
		||||
    "d3-collection": "1.0.x",
 | 
			
		||||
    "d3-color": "1.0.x",
 | 
			
		||||
    "d3-format": "1.2.x",
 | 
			
		||||
    "d3-interpolate": "1.1.x",
 | 
			
		||||
    "d3-scale": "1.0.x",
 | 
			
		||||
    "d3-selection": "1.3.x",
 | 
			
		||||
    "d3-time": "1.0.x",
 | 
			
		||||
    "d3-time-format": "2.1.x",
 | 
			
		||||
    "eslint": "7.0.0",
 | 
			
		||||
    "eslint-plugin-vue": "^7.5.0",
 | 
			
		||||
    "eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
 | 
			
		||||
@@ -34,20 +26,21 @@
 | 
			
		||||
    "git-rev-sync": "^1.4.0",
 | 
			
		||||
    "glob": ">= 3.0.0",
 | 
			
		||||
    "html-loader": "^0.5.5",
 | 
			
		||||
    "html2canvas": "^1.0.0-alpha.12",
 | 
			
		||||
    "html2canvas": "^1.0.0-rc.7",
 | 
			
		||||
    "imports-loader": "^0.8.0",
 | 
			
		||||
    "istanbul-instrumenter-loader": "^3.0.1",
 | 
			
		||||
    "jasmine-core": "^3.1.0",
 | 
			
		||||
    "jasmine-core": "^3.7.1",
 | 
			
		||||
    "jsdoc": "^3.3.2",
 | 
			
		||||
    "karma": "5.1.1",
 | 
			
		||||
    "karma": "6.3.4",
 | 
			
		||||
    "karma-chrome-launcher": "3.1.0",
 | 
			
		||||
    "karma-cli": "2.0.0",
 | 
			
		||||
    "karma-coverage": "2.0.3",
 | 
			
		||||
    "karma-coverage-istanbul-reporter": "3.0.3",
 | 
			
		||||
    "karma-firefox-launcher": "1.3.0",
 | 
			
		||||
    "karma-firefox-launcher": "2.1.1",
 | 
			
		||||
    "karma-html-reporter": "0.2.7",
 | 
			
		||||
    "karma-jasmine": "3.3.1",
 | 
			
		||||
    "karma-sourcemap-loader": "0.3.7",
 | 
			
		||||
    "karma-jasmine": "4.0.1",
 | 
			
		||||
    "karma-junit-reporter": "2.0.1",
 | 
			
		||||
    "karma-sourcemap-loader": "0.3.8",
 | 
			
		||||
    "karma-webpack": "4.0.2",
 | 
			
		||||
    "location-bar": "^3.0.1",
 | 
			
		||||
    "lodash": "^4.17.12",
 | 
			
		||||
@@ -61,6 +54,8 @@
 | 
			
		||||
    "node-bourbon": "^4.2.3",
 | 
			
		||||
    "node-sass": "^4.14.1",
 | 
			
		||||
    "painterro": "^1.2.56",
 | 
			
		||||
    "plotly.js-basic-dist": "^2.5.0",
 | 
			
		||||
    "plotly.js-gl2d-dist": "^2.5.0",
 | 
			
		||||
    "printj": "^1.2.1",
 | 
			
		||||
    "raw-loader": "^0.5.1",
 | 
			
		||||
    "request": "^2.69.0",
 | 
			
		||||
@@ -89,6 +84,7 @@
 | 
			
		||||
    "test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
 | 
			
		||||
    "test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
 | 
			
		||||
    "test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run",
 | 
			
		||||
    "test:coverage:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
 | 
			
		||||
    "test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
 | 
			
		||||
    "verify": "concurrently 'npm:test' 'npm:lint'",
 | 
			
		||||
    "jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
 | 
			
		||||
@@ -100,6 +96,9 @@
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "https://github.com/nasa/openmct.git"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": ">=10.10.2 <16.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "",
 | 
			
		||||
  "license": "Apache-2.0",
 | 
			
		||||
  "private": true
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@ define(
 | 
			
		||||
         *
 | 
			
		||||
         * @param {DomainObject} domainObject the domain object to navigate to
 | 
			
		||||
         * @param {Boolean} force if true, force navigation to occur.
 | 
			
		||||
         * @returns {Boolean} true if navigation occured, otherwise false.
 | 
			
		||||
         * @returns {Boolean} true if navigation occurred, otherwise false.
 | 
			
		||||
         */
 | 
			
		||||
        NavigationService.prototype.setNavigation = function (domainObject, force) {
 | 
			
		||||
            if (force) {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,28 +21,14 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    "./src/MCTDevice",
 | 
			
		||||
    "./src/AgentService",
 | 
			
		||||
    "./src/DeviceClassifier"
 | 
			
		||||
    "./src/AgentService"
 | 
			
		||||
], function (
 | 
			
		||||
    MCTDevice,
 | 
			
		||||
    AgentService,
 | 
			
		||||
    DeviceClassifier
 | 
			
		||||
    AgentService
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        name: "platform/commonUI/mobile",
 | 
			
		||||
        definition: {
 | 
			
		||||
            "extensions": {
 | 
			
		||||
                "directives": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "mctDevice",
 | 
			
		||||
                        "implementation": MCTDevice,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "agentService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "services": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "agentService",
 | 
			
		||||
@@ -51,15 +37,6 @@ define([
 | 
			
		||||
                            "$window"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "runs": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "implementation": DeviceClassifier,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "agentService",
 | 
			
		||||
                            "$document"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,122 +20,12 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides features which support variant behavior on mobile devices.
 | 
			
		||||
 *
 | 
			
		||||
 * @namespace platform/commonUI/mobile
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
define(["../../../../src/utils/agent/Agent.js"], function (Agent) {
 | 
			
		||||
    function AngularAgentServiceWrapper(window) {
 | 
			
		||||
        const AS = Agent.default;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The query service handles calls for browser and userAgent
 | 
			
		||||
         * info using a comparison between the userAgent and key
 | 
			
		||||
         * device names
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param $window Angular-injected instance of the window
 | 
			
		||||
         * @memberof platform/commonUI/mobile
 | 
			
		||||
         */
 | 
			
		||||
        function AgentService($window) {
 | 
			
		||||
            var userAgent = $window.navigator.userAgent,
 | 
			
		||||
                matches = userAgent.match(/iPad|iPhone|Android/i) || [];
 | 
			
		||||
 | 
			
		||||
            this.userAgent = userAgent;
 | 
			
		||||
            this.mobileName = matches[0];
 | 
			
		||||
            this.$window = $window;
 | 
			
		||||
            this.touchEnabled = ($window.ontouchstart !== undefined);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the user is on a mobile device.
 | 
			
		||||
         * @returns {boolean} true on mobile
 | 
			
		||||
         */
 | 
			
		||||
        AgentService.prototype.isMobile = function () {
 | 
			
		||||
            return Boolean(this.mobileName);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the user is on a phone-sized mobile device.
 | 
			
		||||
         * @returns {boolean} true on a phone
 | 
			
		||||
         */
 | 
			
		||||
        AgentService.prototype.isPhone = function () {
 | 
			
		||||
            if (this.isMobile()) {
 | 
			
		||||
                if (this.isAndroidTablet()) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                } else if (this.mobileName === 'iPad') {
 | 
			
		||||
                    return false;
 | 
			
		||||
                } else {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the user is on a tablet sized android device
 | 
			
		||||
         * @returns {boolean} true on an android tablet
 | 
			
		||||
         */
 | 
			
		||||
        AgentService.prototype.isAndroidTablet = function () {
 | 
			
		||||
            if (this.mobileName === 'Android') {
 | 
			
		||||
                if (this.isPortrait() && window.innerWidth >= 768) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                } else if (this.isLandscape() && window.innerHeight >= 768) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the user is on a tablet-sized mobile device.
 | 
			
		||||
         * @returns {boolean} true on a tablet
 | 
			
		||||
         */
 | 
			
		||||
        AgentService.prototype.isTablet = function () {
 | 
			
		||||
            return (this.isMobile() && !this.isPhone() && this.mobileName !== 'Android') || (this.isMobile() && this.isAndroidTablet());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the user's device is in a portrait-style
 | 
			
		||||
         * orientation (display width is narrower than display height.)
 | 
			
		||||
         * @returns {boolean} true in portrait mode
 | 
			
		||||
         */
 | 
			
		||||
        AgentService.prototype.isPortrait = function () {
 | 
			
		||||
            return this.$window.innerWidth < this.$window.innerHeight;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the user's device is in a landscape-style
 | 
			
		||||
         * orientation (display width is greater than display height.)
 | 
			
		||||
         * @returns {boolean} true in landscape mode
 | 
			
		||||
         */
 | 
			
		||||
        AgentService.prototype.isLandscape = function () {
 | 
			
		||||
            return !this.isPortrait();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the user's device supports a touch interface.
 | 
			
		||||
         * @returns {boolean} true if touch is supported
 | 
			
		||||
         */
 | 
			
		||||
        AgentService.prototype.isTouch = function () {
 | 
			
		||||
            return this.touchEnabled;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the user agent matches a certain named device,
 | 
			
		||||
         * as indicated by checking for a case-insensitive substring
 | 
			
		||||
         * match.
 | 
			
		||||
         * @param {string} name the name to check for
 | 
			
		||||
         * @returns {boolean} true if the user agent includes that name
 | 
			
		||||
         */
 | 
			
		||||
        AgentService.prototype.isBrowser = function (name) {
 | 
			
		||||
            name = name.toLowerCase();
 | 
			
		||||
 | 
			
		||||
            return this.userAgent.toLowerCase().indexOf(name) !== -1;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return AgentService;
 | 
			
		||||
        return new AS(window);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
    return AngularAgentServiceWrapper;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										96
									
								
								platform/commonUI/mobile/src/AgentServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								platform/commonUI/mobile/src/AgentServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 AgentService from "./AgentService";
 | 
			
		||||
 | 
			
		||||
const TEST_USER_AGENTS = {
 | 
			
		||||
    DESKTOP:
 | 
			
		||||
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36",
 | 
			
		||||
    IPAD:
 | 
			
		||||
    "Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53",
 | 
			
		||||
    IPHONE:
 | 
			
		||||
    "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe("The AgentService", function () {
 | 
			
		||||
    let testWindow;
 | 
			
		||||
    let agentService;
 | 
			
		||||
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
        testWindow = {
 | 
			
		||||
            innerWidth: 640,
 | 
			
		||||
            innerHeight: 480,
 | 
			
		||||
            navigator: {
 | 
			
		||||
                userAgent: TEST_USER_AGENTS.DESKTOP
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("recognizes desktop devices as non-mobile", function () {
 | 
			
		||||
        testWindow.navigator.userAgent = TEST_USER_AGENTS.DESKTOP;
 | 
			
		||||
        agentService = new AgentService(testWindow);
 | 
			
		||||
        expect(agentService.isMobile()).toBeFalsy();
 | 
			
		||||
        expect(agentService.isPhone()).toBeFalsy();
 | 
			
		||||
        expect(agentService.isTablet()).toBeFalsy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("detects iPhones", function () {
 | 
			
		||||
        testWindow.navigator.userAgent = TEST_USER_AGENTS.IPHONE;
 | 
			
		||||
        agentService = new AgentService(testWindow);
 | 
			
		||||
        expect(agentService.isMobile()).toBeTruthy();
 | 
			
		||||
        expect(agentService.isPhone()).toBeTruthy();
 | 
			
		||||
        expect(agentService.isTablet()).toBeFalsy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("detects iPads", function () {
 | 
			
		||||
        testWindow.navigator.userAgent = TEST_USER_AGENTS.IPAD;
 | 
			
		||||
        agentService = new AgentService(testWindow);
 | 
			
		||||
        expect(agentService.isMobile()).toBeTruthy();
 | 
			
		||||
        expect(agentService.isPhone()).toBeFalsy();
 | 
			
		||||
        expect(agentService.isTablet()).toBeTruthy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("detects display orientation", function () {
 | 
			
		||||
        agentService = new AgentService(testWindow);
 | 
			
		||||
        testWindow.innerWidth = 1024;
 | 
			
		||||
        testWindow.innerHeight = 400;
 | 
			
		||||
        expect(agentService.isPortrait()).toBeFalsy();
 | 
			
		||||
        expect(agentService.isLandscape()).toBeTruthy();
 | 
			
		||||
        testWindow.innerWidth = 400;
 | 
			
		||||
        testWindow.innerHeight = 1024;
 | 
			
		||||
        expect(agentService.isPortrait()).toBeTruthy();
 | 
			
		||||
        expect(agentService.isLandscape()).toBeFalsy();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("detects touch support", function () {
 | 
			
		||||
        testWindow.ontouchstart = null;
 | 
			
		||||
        expect(new AgentService(testWindow).isTouch()).toBe(true);
 | 
			
		||||
        delete testWindow.ontouchstart;
 | 
			
		||||
        expect(new AgentService(testWindow).isTouch()).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("allows for checking browser type", function () {
 | 
			
		||||
        testWindow.navigator.userAgent = "Chromezilla Safarifox";
 | 
			
		||||
        agentService = new AgentService(testWindow);
 | 
			
		||||
        expect(agentService.isBrowser("Chrome")).toBe(true);
 | 
			
		||||
        expect(agentService.isBrowser("Firefox")).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,72 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ['./DeviceMatchers'],
 | 
			
		||||
    function (DeviceMatchers) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Runs at application startup and adds a subset of the following
 | 
			
		||||
         * CSS classes to the body of the document, depending on device
 | 
			
		||||
         * attributes:
 | 
			
		||||
         *
 | 
			
		||||
         * * `mobile`: Phones or tablets.
 | 
			
		||||
         * * `phone`: Phones specifically.
 | 
			
		||||
         * * `tablet`: Tablets specifically.
 | 
			
		||||
         * * `desktop`: Non-mobile devices.
 | 
			
		||||
         * * `portrait`: Devices in a portrait-style orientation.
 | 
			
		||||
         * * `landscape`: Devices in a landscape-style orientation.
 | 
			
		||||
         * * `touch`: Device supports touch events.
 | 
			
		||||
         *
 | 
			
		||||
         * @param {platform/commonUI/mobile.AgentService} agentService
 | 
			
		||||
         *        the service used to examine the user agent
 | 
			
		||||
         * @param $document Angular's jqLite-wrapped document element
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function MobileClassifier(agentService, $document) {
 | 
			
		||||
            var body = $document.find('body');
 | 
			
		||||
 | 
			
		||||
            Object.keys(DeviceMatchers).forEach(function (key, index, array) {
 | 
			
		||||
                if (DeviceMatchers[key](agentService)) {
 | 
			
		||||
                    body.addClass(key);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (agentService.isMobile()) {
 | 
			
		||||
                var mediaQuery = window.matchMedia('(orientation: landscape)');
 | 
			
		||||
 | 
			
		||||
                mediaQuery.addListener(function (event) {
 | 
			
		||||
                    if (event.matches) {
 | 
			
		||||
                        body.removeClass('portrait');
 | 
			
		||||
                        body.addClass('landscape');
 | 
			
		||||
                    } else {
 | 
			
		||||
                        body.removeClass('landscape');
 | 
			
		||||
                        body.addClass('portrait');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MobileClassifier;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,88 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ['./DeviceMatchers'],
 | 
			
		||||
    function (DeviceMatchers) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The `mct-device` directive, when applied as an attribute,
 | 
			
		||||
         * only includes the element when the device being used matches
 | 
			
		||||
         * a set of characteristics required.
 | 
			
		||||
         *
 | 
			
		||||
         * Required characteristics are given as space-separated strings
 | 
			
		||||
         * as the value to this attribute, e.g.:
 | 
			
		||||
         *
 | 
			
		||||
         *    <span mct-device="mobile portrait">Hello world!</span>
 | 
			
		||||
         *
 | 
			
		||||
         * ...will only show Hello world! when viewed on a mobile device
 | 
			
		||||
         * in the portrait orientation.
 | 
			
		||||
         *
 | 
			
		||||
         * Valid device characteristics to detect are:
 | 
			
		||||
         *
 | 
			
		||||
         * * `mobile`: Phones or tablets.
 | 
			
		||||
         * * `phone`: Phones specifically.
 | 
			
		||||
         * * `tablet`: Tablets specifically.
 | 
			
		||||
         * * `desktop`: Non-mobile devices.
 | 
			
		||||
         * * `portrait`: Devices in a portrait-style orientation.
 | 
			
		||||
         * * `landscape`: Devices in a landscape-style orientation.
 | 
			
		||||
         * * `touch`: Device supports touch events.
 | 
			
		||||
         *
 | 
			
		||||
         * @param {AgentService} agentService used to detect device type
 | 
			
		||||
         *        based on information about the user agent
 | 
			
		||||
         */
 | 
			
		||||
        function MCTDevice(agentService) {
 | 
			
		||||
 | 
			
		||||
            function deviceMatches(tokens) {
 | 
			
		||||
                tokens = tokens || "";
 | 
			
		||||
 | 
			
		||||
                return tokens.split(" ").every(function (token) {
 | 
			
		||||
                    var fn = DeviceMatchers[token];
 | 
			
		||||
 | 
			
		||||
                    return fn && fn(agentService);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function link(scope, element, attrs, ctrl, transclude) {
 | 
			
		||||
                if (deviceMatches(attrs.mctDevice)) {
 | 
			
		||||
                    transclude(function (clone) {
 | 
			
		||||
                        element.replaceWith(clone);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                link: link,
 | 
			
		||||
                // We are transcluding the whole element (like ng-if)
 | 
			
		||||
                transclude: 'element',
 | 
			
		||||
                // 1 more than ng-if
 | 
			
		||||
                priority: 601,
 | 
			
		||||
                // Also terminal, since element will be transcluded
 | 
			
		||||
                terminal: true,
 | 
			
		||||
                // Only apply as an attribute
 | 
			
		||||
                restrict: "A"
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MCTDevice;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,99 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/AgentService"],
 | 
			
		||||
    function (AgentService) {
 | 
			
		||||
 | 
			
		||||
        var TEST_USER_AGENTS = {
 | 
			
		||||
            DESKTOP: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36",
 | 
			
		||||
            IPAD: "Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53",
 | 
			
		||||
            IPHONE: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        describe("The AgentService", function () {
 | 
			
		||||
            var testWindow, agentService;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                testWindow = {
 | 
			
		||||
                    innerWidth: 640,
 | 
			
		||||
                    innerHeight: 480,
 | 
			
		||||
                    navigator: {
 | 
			
		||||
                        userAgent: TEST_USER_AGENTS.DESKTOP
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("recognizes desktop devices as non-mobile", function () {
 | 
			
		||||
                testWindow.navigator.userAgent = TEST_USER_AGENTS.DESKTOP;
 | 
			
		||||
                agentService = new AgentService(testWindow);
 | 
			
		||||
                expect(agentService.isMobile()).toBeFalsy();
 | 
			
		||||
                expect(agentService.isPhone()).toBeFalsy();
 | 
			
		||||
                expect(agentService.isTablet()).toBeFalsy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("detects iPhones", function () {
 | 
			
		||||
                testWindow.navigator.userAgent = TEST_USER_AGENTS.IPHONE;
 | 
			
		||||
                agentService = new AgentService(testWindow);
 | 
			
		||||
                expect(agentService.isMobile()).toBeTruthy();
 | 
			
		||||
                expect(agentService.isPhone()).toBeTruthy();
 | 
			
		||||
                expect(agentService.isTablet()).toBeFalsy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("detects iPads", function () {
 | 
			
		||||
                testWindow.navigator.userAgent = TEST_USER_AGENTS.IPAD;
 | 
			
		||||
                agentService = new AgentService(testWindow);
 | 
			
		||||
                expect(agentService.isMobile()).toBeTruthy();
 | 
			
		||||
                expect(agentService.isPhone()).toBeFalsy();
 | 
			
		||||
                expect(agentService.isTablet()).toBeTruthy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("detects display orientation", function () {
 | 
			
		||||
                agentService = new AgentService(testWindow);
 | 
			
		||||
                testWindow.innerWidth = 1024;
 | 
			
		||||
                testWindow.innerHeight = 400;
 | 
			
		||||
                expect(agentService.isPortrait()).toBeFalsy();
 | 
			
		||||
                expect(agentService.isLandscape()).toBeTruthy();
 | 
			
		||||
                testWindow.innerWidth = 400;
 | 
			
		||||
                testWindow.innerHeight = 1024;
 | 
			
		||||
                expect(agentService.isPortrait()).toBeTruthy();
 | 
			
		||||
                expect(agentService.isLandscape()).toBeFalsy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("detects touch support", function () {
 | 
			
		||||
                testWindow.ontouchstart = null;
 | 
			
		||||
                expect(new AgentService(testWindow).isTouch())
 | 
			
		||||
                    .toBe(true);
 | 
			
		||||
                delete testWindow.ontouchstart;
 | 
			
		||||
                expect(new AgentService(testWindow).isTouch())
 | 
			
		||||
                    .toBe(false);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows for checking browser type", function () {
 | 
			
		||||
                testWindow.navigator.userAgent = "Chromezilla Safarifox";
 | 
			
		||||
                agentService = new AgentService(testWindow);
 | 
			
		||||
                expect(agentService.isBrowser("Chrome")).toBe(true);
 | 
			
		||||
                expect(agentService.isBrowser("Firefox")).toBe(false);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,109 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/DeviceClassifier", "../src/DeviceMatchers"],
 | 
			
		||||
    function (DeviceClassifier, DeviceMatchers) {
 | 
			
		||||
 | 
			
		||||
        var AGENT_SERVICE_METHODS = [
 | 
			
		||||
                'isMobile',
 | 
			
		||||
                'isPhone',
 | 
			
		||||
                'isTablet',
 | 
			
		||||
                'isPortrait',
 | 
			
		||||
                'isLandscape',
 | 
			
		||||
                'isTouch'
 | 
			
		||||
            ],
 | 
			
		||||
            TEST_PERMUTATIONS = [
 | 
			
		||||
                ['isMobile', 'isPhone', 'isTouch', 'isPortrait'],
 | 
			
		||||
                ['isMobile', 'isPhone', 'isTouch', 'isLandscape'],
 | 
			
		||||
                ['isMobile', 'isTablet', 'isTouch', 'isPortrait'],
 | 
			
		||||
                ['isMobile', 'isTablet', 'isTouch', 'isLandscape'],
 | 
			
		||||
                ['isTouch'],
 | 
			
		||||
                []
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
        describe("DeviceClassifier", function () {
 | 
			
		||||
            var mockAgentService,
 | 
			
		||||
                mockDocument,
 | 
			
		||||
                mockBody;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockAgentService = jasmine.createSpyObj(
 | 
			
		||||
                    'agentService',
 | 
			
		||||
                    AGENT_SERVICE_METHODS
 | 
			
		||||
                );
 | 
			
		||||
                mockDocument = jasmine.createSpyObj(
 | 
			
		||||
                    '$document',
 | 
			
		||||
                    ['find']
 | 
			
		||||
                );
 | 
			
		||||
                mockBody = jasmine.createSpyObj(
 | 
			
		||||
                    'body',
 | 
			
		||||
                    ['addClass']
 | 
			
		||||
                );
 | 
			
		||||
                mockDocument.find.and.callFake(function (sel) {
 | 
			
		||||
                    return sel === 'body' && mockBody;
 | 
			
		||||
                });
 | 
			
		||||
                AGENT_SERVICE_METHODS.forEach(function (m) {
 | 
			
		||||
                    mockAgentService[m].and.returnValue(false);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            TEST_PERMUTATIONS.forEach(function (trueMethods) {
 | 
			
		||||
                var summary = trueMethods.length === 0
 | 
			
		||||
                    ? "device has no detected characteristics"
 | 
			
		||||
                    : "device " + (trueMethods.join(", "));
 | 
			
		||||
 | 
			
		||||
                describe("when " + summary, function () {
 | 
			
		||||
                    var classifier; // eslint-disable-line
 | 
			
		||||
 | 
			
		||||
                    beforeEach(function () {
 | 
			
		||||
                        trueMethods.forEach(function (m) {
 | 
			
		||||
                            mockAgentService[m].and.returnValue(true);
 | 
			
		||||
                        });
 | 
			
		||||
                        classifier = new DeviceClassifier(
 | 
			
		||||
                            mockAgentService,
 | 
			
		||||
                            mockDocument
 | 
			
		||||
                        );
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("adds classes for matching, detected characteristics", function () {
 | 
			
		||||
                        Object.keys(DeviceMatchers).filter(function (m) {
 | 
			
		||||
                            return DeviceMatchers[m](mockAgentService);
 | 
			
		||||
                        }).forEach(function (key) {
 | 
			
		||||
                            expect(mockBody.addClass)
 | 
			
		||||
                                .toHaveBeenCalledWith(key);
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("does not add classes for non-matching characteristics", function () {
 | 
			
		||||
                        Object.keys(DeviceMatchers).filter(function (m) {
 | 
			
		||||
                            return !DeviceMatchers[m](mockAgentService);
 | 
			
		||||
                        }).forEach(function (key) {
 | 
			
		||||
                            expect(mockBody.addClass)
 | 
			
		||||
                                .not.toHaveBeenCalledWith(key);
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,78 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/DeviceMatchers"],
 | 
			
		||||
    function (DeviceMatchers) {
 | 
			
		||||
 | 
			
		||||
        describe("DeviceMatchers", function () {
 | 
			
		||||
            var mockAgentService;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockAgentService = jasmine.createSpyObj(
 | 
			
		||||
                    'agentService',
 | 
			
		||||
                    [
 | 
			
		||||
                        'isMobile',
 | 
			
		||||
                        'isPhone',
 | 
			
		||||
                        'isTablet',
 | 
			
		||||
                        'isPortrait',
 | 
			
		||||
                        'isLandscape',
 | 
			
		||||
                        'isTouch'
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("detects when a device is a desktop device", function () {
 | 
			
		||||
                mockAgentService.isMobile.and.returnValue(false);
 | 
			
		||||
                expect(DeviceMatchers.desktop(mockAgentService))
 | 
			
		||||
                    .toBe(true);
 | 
			
		||||
                mockAgentService.isMobile.and.returnValue(true);
 | 
			
		||||
                expect(DeviceMatchers.desktop(mockAgentService))
 | 
			
		||||
                    .toBe(false);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function method(deviceType) {
 | 
			
		||||
                return "is" + deviceType[0].toUpperCase() + deviceType.slice(1);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [
 | 
			
		||||
                "mobile",
 | 
			
		||||
                "phone",
 | 
			
		||||
                "tablet",
 | 
			
		||||
                "landscape",
 | 
			
		||||
                "portrait",
 | 
			
		||||
                "landscape",
 | 
			
		||||
                "touch"
 | 
			
		||||
            ].forEach(function (deviceType) {
 | 
			
		||||
                it("detects when a device is a " + deviceType + " device", function () {
 | 
			
		||||
                    mockAgentService[method(deviceType)].and.returnValue(true);
 | 
			
		||||
                    expect(DeviceMatchers[deviceType](mockAgentService))
 | 
			
		||||
                        .toBe(true);
 | 
			
		||||
                    mockAgentService[method(deviceType)].and.returnValue(false);
 | 
			
		||||
                    expect(DeviceMatchers[deviceType](mockAgentService))
 | 
			
		||||
                        .toBe(false);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,168 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ['../src/MCTDevice'],
 | 
			
		||||
    function (MCTDevice) {
 | 
			
		||||
 | 
			
		||||
        var JQLITE_METHODS = ['replaceWith'];
 | 
			
		||||
 | 
			
		||||
        describe("The mct-device directive", function () {
 | 
			
		||||
            var mockAgentService,
 | 
			
		||||
                mockTransclude,
 | 
			
		||||
                mockElement,
 | 
			
		||||
                mockClone,
 | 
			
		||||
                testAttrs,
 | 
			
		||||
                directive;
 | 
			
		||||
 | 
			
		||||
            function link() {
 | 
			
		||||
                directive.link(null, mockElement, testAttrs, null, mockTransclude);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockAgentService = jasmine.createSpyObj(
 | 
			
		||||
                    "agentService",
 | 
			
		||||
                    ["isMobile", "isPhone", "isTablet", "isPortrait", "isLandscape"]
 | 
			
		||||
                );
 | 
			
		||||
                mockTransclude = jasmine.createSpy("$transclude");
 | 
			
		||||
                mockElement = jasmine.createSpyObj(name, JQLITE_METHODS);
 | 
			
		||||
                mockClone = jasmine.createSpyObj(name, JQLITE_METHODS);
 | 
			
		||||
 | 
			
		||||
                mockTransclude.and.callFake(function (fn) {
 | 
			
		||||
                    fn(mockClone);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Look desktop-like by default
 | 
			
		||||
                mockAgentService.isLandscape.and.returnValue(true);
 | 
			
		||||
 | 
			
		||||
                testAttrs = {};
 | 
			
		||||
 | 
			
		||||
                directive = new MCTDevice(mockAgentService);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function expectInclusion() {
 | 
			
		||||
                expect(mockElement.replaceWith)
 | 
			
		||||
                    .toHaveBeenCalledWith(mockClone);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function expectExclusion() {
 | 
			
		||||
                expect(mockElement.replaceWith).not.toHaveBeenCalled();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            it("is applicable at the attribute level", function () {
 | 
			
		||||
                expect(directive.restrict).toEqual("A");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("transcludes at the element level", function () {
 | 
			
		||||
                expect(directive.transclude).toEqual('element');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("has a greater priority number than ng-if", function () {
 | 
			
		||||
                expect(directive.priority > 600).toBeTruthy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("restricts element inclusion for mobile devices", function () {
 | 
			
		||||
                testAttrs.mctDevice = "mobile";
 | 
			
		||||
                link();
 | 
			
		||||
                expectExclusion();
 | 
			
		||||
 | 
			
		||||
                mockAgentService.isMobile.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectInclusion();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("restricts element inclusion for tablet devices", function () {
 | 
			
		||||
                testAttrs.mctDevice = "tablet";
 | 
			
		||||
                mockAgentService.isMobile.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectExclusion();
 | 
			
		||||
 | 
			
		||||
                mockAgentService.isTablet.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectInclusion();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("restricts element inclusion for phone devices", function () {
 | 
			
		||||
                testAttrs.mctDevice = "phone";
 | 
			
		||||
                mockAgentService.isMobile.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectExclusion();
 | 
			
		||||
 | 
			
		||||
                mockAgentService.isPhone.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectInclusion();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("restricts element inclusion for desktop devices", function () {
 | 
			
		||||
                testAttrs.mctDevice = "desktop";
 | 
			
		||||
                mockAgentService.isMobile.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectExclusion();
 | 
			
		||||
 | 
			
		||||
                mockAgentService.isMobile.and.returnValue(false);
 | 
			
		||||
                link();
 | 
			
		||||
                expectInclusion();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("restricts element inclusion for portrait orientation", function () {
 | 
			
		||||
                testAttrs.mctDevice = "portrait";
 | 
			
		||||
                link();
 | 
			
		||||
                expectExclusion();
 | 
			
		||||
 | 
			
		||||
                mockAgentService.isPortrait.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectInclusion();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("restricts element inclusion for landscape orientation", function () {
 | 
			
		||||
                testAttrs.mctDevice = "landscape";
 | 
			
		||||
                mockAgentService.isLandscape.and.returnValue(false);
 | 
			
		||||
                mockAgentService.isPortrait.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectExclusion();
 | 
			
		||||
 | 
			
		||||
                mockAgentService.isLandscape.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectInclusion();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows multiple device characteristics to be requested", function () {
 | 
			
		||||
                // Won't try to test every permutation here, just
 | 
			
		||||
                // make sure the multi-characteristic feature has support.
 | 
			
		||||
                testAttrs.mctDevice = "portrait mobile";
 | 
			
		||||
                link();
 | 
			
		||||
                // Neither portrait nor mobile, not called
 | 
			
		||||
                expectExclusion();
 | 
			
		||||
 | 
			
		||||
                mockAgentService.isPortrait.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
 | 
			
		||||
                // Was portrait, but not mobile, so no
 | 
			
		||||
                expectExclusion();
 | 
			
		||||
 | 
			
		||||
                mockAgentService.isMobile.and.returnValue(true);
 | 
			
		||||
                link();
 | 
			
		||||
                expectInclusion();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -379,7 +379,7 @@ define([
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "Math.uuid.js",
 | 
			
		||||
                        "version": "1.4.7",
 | 
			
		||||
                        "description": "Unique identifer generation (code adapted.)",
 | 
			
		||||
                        "description": "Unique identifier generation (code adapted.)",
 | 
			
		||||
                        "author": "Robert Kieffer",
 | 
			
		||||
                        "website": "https://github.com/broofa/node-uuid",
 | 
			
		||||
                        "copyright": "Copyright (c) 2010-2012 Robert Kieffer",
 | 
			
		||||
 
 | 
			
		||||
@@ -21,32 +21,24 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    "moment-timezone",
 | 
			
		||||
    "./src/indicators/ClockIndicator",
 | 
			
		||||
    "./src/services/TickerService",
 | 
			
		||||
    "./src/services/TimerService",
 | 
			
		||||
    "./src/controllers/ClockController",
 | 
			
		||||
    "./src/controllers/TimerController",
 | 
			
		||||
    "./src/controllers/RefreshingController",
 | 
			
		||||
    "./src/actions/StartTimerAction",
 | 
			
		||||
    "./src/actions/RestartTimerAction",
 | 
			
		||||
    "./src/actions/StopTimerAction",
 | 
			
		||||
    "./src/actions/PauseTimerAction",
 | 
			
		||||
    "./res/templates/clock.html",
 | 
			
		||||
    "./res/templates/timer.html"
 | 
			
		||||
], function (
 | 
			
		||||
    MomentTimezone,
 | 
			
		||||
    ClockIndicator,
 | 
			
		||||
    TickerService,
 | 
			
		||||
    TimerService,
 | 
			
		||||
    ClockController,
 | 
			
		||||
    TimerController,
 | 
			
		||||
    RefreshingController,
 | 
			
		||||
    StartTimerAction,
 | 
			
		||||
    RestartTimerAction,
 | 
			
		||||
    StopTimerAction,
 | 
			
		||||
    PauseTimerAction,
 | 
			
		||||
    clockTemplate,
 | 
			
		||||
    timerTemplate
 | 
			
		||||
) {
 | 
			
		||||
    return {
 | 
			
		||||
@@ -73,16 +65,6 @@ define([
 | 
			
		||||
                        "value": "YYYY/MM/DD HH:mm:ss"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "indicators": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "implementation": ClockIndicator,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "tickerService",
 | 
			
		||||
                            "CLOCK_INDICATOR_FORMAT"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "priority": "preferred"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "services": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "tickerService",
 | 
			
		||||
@@ -99,14 +81,6 @@ define([
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "controllers": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "ClockController",
 | 
			
		||||
                        "implementation": ClockController,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "$scope",
 | 
			
		||||
                            "tickerService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "TimerController",
 | 
			
		||||
                        "implementation": TimerController,
 | 
			
		||||
@@ -126,12 +100,6 @@ define([
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "views": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "clock",
 | 
			
		||||
                        "type": "clock",
 | 
			
		||||
                        "editable": false,
 | 
			
		||||
                        "template": clockTemplate
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer",
 | 
			
		||||
                        "type": "timer",
 | 
			
		||||
@@ -181,75 +149,11 @@ define([
 | 
			
		||||
                        ],
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "name": "Stop",
 | 
			
		||||
                        "cssClass": "icon-box",
 | 
			
		||||
                        "cssClass": "icon-box-round-corners",
 | 
			
		||||
                        "priority": "preferred"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "types": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "clock",
 | 
			
		||||
                        "name": "Clock",
 | 
			
		||||
                        "cssClass": "icon-clock",
 | 
			
		||||
                        "description": "A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.",
 | 
			
		||||
                        "priority": 101,
 | 
			
		||||
                        "features": [
 | 
			
		||||
                            "creation"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "properties": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "clockFormat",
 | 
			
		||||
                                "name": "Display Format",
 | 
			
		||||
                                "control": "composite",
 | 
			
		||||
                                "items": [
 | 
			
		||||
                                    {
 | 
			
		||||
                                        "control": "select",
 | 
			
		||||
                                        "options": [
 | 
			
		||||
                                            {
 | 
			
		||||
                                                "value": "YYYY/MM/DD hh:mm:ss",
 | 
			
		||||
                                                "name": "YYYY/MM/DD hh:mm:ss"
 | 
			
		||||
                                            },
 | 
			
		||||
                                            {
 | 
			
		||||
                                                "value": "YYYY/DDD hh:mm:ss",
 | 
			
		||||
                                                "name": "YYYY/DDD hh:mm:ss"
 | 
			
		||||
                                            },
 | 
			
		||||
                                            {
 | 
			
		||||
                                                "value": "hh:mm:ss",
 | 
			
		||||
                                                "name": "hh:mm:ss"
 | 
			
		||||
                                            }
 | 
			
		||||
                                        ],
 | 
			
		||||
                                        "cssClass": "l-inline"
 | 
			
		||||
                                    },
 | 
			
		||||
                                    {
 | 
			
		||||
                                        "control": "select",
 | 
			
		||||
                                        "options": [
 | 
			
		||||
                                            {
 | 
			
		||||
                                                "value": "clock12",
 | 
			
		||||
                                                "name": "12hr"
 | 
			
		||||
                                            },
 | 
			
		||||
                                            {
 | 
			
		||||
                                                "value": "clock24",
 | 
			
		||||
                                                "name": "24hr"
 | 
			
		||||
                                            }
 | 
			
		||||
                                        ],
 | 
			
		||||
                                        "cssClass": "l-inline"
 | 
			
		||||
                                    }
 | 
			
		||||
                                ]
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "timezone",
 | 
			
		||||
                                "name": "Timezone",
 | 
			
		||||
                                "control": "autocomplete",
 | 
			
		||||
                                "options": MomentTimezone.tz.names()
 | 
			
		||||
                            }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "model": {
 | 
			
		||||
                            "clockFormat": [
 | 
			
		||||
                                "YYYY/MM/DD hh:mm:ss",
 | 
			
		||||
                                "clock12"
 | 
			
		||||
                            ],
 | 
			
		||||
                            "timezone": "UTC"
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer",
 | 
			
		||||
                        "name": "Timer",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2009-2016, United States Government
 | 
			
		||||
 as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 Administration. All rights reserved.
 | 
			
		||||
 | 
			
		||||
 Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 You may obtain a copy of the License at
 | 
			
		||||
 http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 | 
			
		||||
 Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 License for the specific language governing permissions and limitations
 | 
			
		||||
 under the License.
 | 
			
		||||
 | 
			
		||||
 Open MCT includes source code licensed under additional open source
 | 
			
		||||
 licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div class="c-clock l-time-display u-style-receiver js-style-receiver" ng-controller="ClockController as clock">
 | 
			
		||||
	<div class="c-clock__timezone">
 | 
			
		||||
		{{clock.zone()}}
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="c-clock__value">
 | 
			
		||||
		{{clock.text()}}
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="c-clock__ampm">
 | 
			
		||||
		{{clock.ampm()}}
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -1,110 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    'moment',
 | 
			
		||||
    'moment-timezone'
 | 
			
		||||
],
 | 
			
		||||
function (
 | 
			
		||||
    moment,
 | 
			
		||||
    momentTimezone
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
         * Controller for views of a Clock domain object.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         * @param {angular.Scope} $scope the Angular scope
 | 
			
		||||
         * @param {platform/features/clock.TickerService} tickerService
 | 
			
		||||
         *        a service used to align behavior with clock ticks
 | 
			
		||||
         */
 | 
			
		||||
    function ClockController($scope, tickerService) {
 | 
			
		||||
        var lastTimestamp,
 | 
			
		||||
            unlisten,
 | 
			
		||||
            timeFormat,
 | 
			
		||||
            zoneName,
 | 
			
		||||
            self = this;
 | 
			
		||||
 | 
			
		||||
        function update() {
 | 
			
		||||
            var m = zoneName
 | 
			
		||||
                ? moment.utc(lastTimestamp).tz(zoneName) : moment.utc(lastTimestamp);
 | 
			
		||||
            self.zoneAbbr = m.zoneAbbr();
 | 
			
		||||
            self.textValue = timeFormat && m.format(timeFormat);
 | 
			
		||||
            self.ampmValue = m.format("A"); // Just the AM or PM part
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function tick(timestamp) {
 | 
			
		||||
            lastTimestamp = timestamp;
 | 
			
		||||
            update();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function updateModel(model) {
 | 
			
		||||
            var baseFormat;
 | 
			
		||||
            if (model !== undefined) {
 | 
			
		||||
                baseFormat = model.clockFormat[0];
 | 
			
		||||
 | 
			
		||||
                self.use24 = model.clockFormat[1] === 'clock24';
 | 
			
		||||
                timeFormat = self.use24
 | 
			
		||||
                    ? baseFormat.replace('hh', "HH") : baseFormat;
 | 
			
		||||
                // If wrong timezone is provided, the UTC will be used
 | 
			
		||||
                zoneName = momentTimezone.tz.names().includes(model.timezone)
 | 
			
		||||
                    ? model.timezone : "UTC";
 | 
			
		||||
                update();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Pull in the model (clockFormat and timezone) from the domain object model
 | 
			
		||||
        $scope.$watch('model', updateModel);
 | 
			
		||||
 | 
			
		||||
        // Listen for clock ticks ... and stop listening on destroy
 | 
			
		||||
        unlisten = tickerService.listen(tick);
 | 
			
		||||
        $scope.$on('$destroy', unlisten);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
         * Get the clock's time zone, as displayable text.
 | 
			
		||||
         * @returns {string}
 | 
			
		||||
         */
 | 
			
		||||
    ClockController.prototype.zone = function () {
 | 
			
		||||
        return this.zoneAbbr;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
         * Get the current time, as displayable text.
 | 
			
		||||
         * @returns {string}
 | 
			
		||||
         */
 | 
			
		||||
    ClockController.prototype.text = function () {
 | 
			
		||||
        return this.textValue;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
         * Get the text to display to qualify a time as AM or PM.
 | 
			
		||||
         * @returns {string}
 | 
			
		||||
         */
 | 
			
		||||
    ClockController.prototype.ampm = function () {
 | 
			
		||||
        return this.use24 ? '' : this.ampmValue;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return ClockController;
 | 
			
		||||
}
 | 
			
		||||
);
 | 
			
		||||
@@ -1,65 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ['moment'],
 | 
			
		||||
    function (moment) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Indicator that displays the current UTC time in the status area.
 | 
			
		||||
         * @implements {Indicator}
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         * @param {platform/features/clock.TickerService} tickerService
 | 
			
		||||
         *        a service used to align behavior with clock ticks
 | 
			
		||||
         * @param {string} indicatorFormat format string for timestamps
 | 
			
		||||
         *        shown in this indicator
 | 
			
		||||
         */
 | 
			
		||||
        function ClockIndicator(tickerService, indicatorFormat) {
 | 
			
		||||
            var self = this;
 | 
			
		||||
 | 
			
		||||
            this.text = "";
 | 
			
		||||
 | 
			
		||||
            tickerService.listen(function (timestamp) {
 | 
			
		||||
                self.text = moment.utc(timestamp)
 | 
			
		||||
                    .format(indicatorFormat) + " UTC";
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ClockIndicator.prototype.getGlyphClass = function () {
 | 
			
		||||
            return "";
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ClockIndicator.prototype.getCssClass = function () {
 | 
			
		||||
            return "t-indicator-clock icon-clock no-minify c-indicator--not-clickable";
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ClockIndicator.prototype.getText = function () {
 | 
			
		||||
            return this.text;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ClockIndicator.prototype.getDescription = function () {
 | 
			
		||||
            return "";
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return ClockIndicator;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,107 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-2017, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/controllers/ClockController"],
 | 
			
		||||
    function (ClockController) {
 | 
			
		||||
 | 
			
		||||
        // Wed, 03 Jun 2015 17:56:14 GMT
 | 
			
		||||
        var TEST_TIMESTAMP = 1433354174000;
 | 
			
		||||
 | 
			
		||||
        describe("A clock view's controller", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
                mockTicker,
 | 
			
		||||
                mockUnticker,
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']);
 | 
			
		||||
                mockTicker = jasmine.createSpyObj('ticker', ['listen']);
 | 
			
		||||
                mockUnticker = jasmine.createSpy('unticker');
 | 
			
		||||
 | 
			
		||||
                mockTicker.listen.and.returnValue(mockUnticker);
 | 
			
		||||
 | 
			
		||||
                controller = new ClockController(mockScope, mockTicker);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches for model (clockFormat and timezone) from the domain object model", function () {
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "model",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("subscribes to clock ticks", function () {
 | 
			
		||||
                expect(mockTicker.listen)
 | 
			
		||||
                    .toHaveBeenCalledWith(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("unsubscribes to ticks when destroyed", function () {
 | 
			
		||||
                // Make sure $destroy is being listened for...
 | 
			
		||||
                expect(mockScope.$on.calls.mostRecent().args[0]).toEqual('$destroy');
 | 
			
		||||
                expect(mockUnticker).not.toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                // ...and makes sure that its listener unsubscribes from ticker
 | 
			
		||||
                mockScope.$on.calls.mostRecent().args[1]();
 | 
			
		||||
                expect(mockUnticker).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("formats using the format string from the model", function () {
 | 
			
		||||
                mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
 | 
			
		||||
                mockScope.$watch.calls.mostRecent().args[1]({
 | 
			
		||||
                    "clockFormat": [
 | 
			
		||||
                        "YYYY-DDD hh:mm:ss",
 | 
			
		||||
                        "clock24"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "timezone": "Canada/Eastern"
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                expect(controller.zone()).toEqual("EDT");
 | 
			
		||||
                expect(controller.text()).toEqual("2015-154 13:56:14");
 | 
			
		||||
                expect(controller.ampm()).toEqual("");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("formats 12-hour time", function () {
 | 
			
		||||
                mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
 | 
			
		||||
                mockScope.$watch.calls.mostRecent().args[1]({
 | 
			
		||||
                    "clockFormat": [
 | 
			
		||||
                        "YYYY-DDD hh:mm:ss",
 | 
			
		||||
                        "clock12"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "timezone": ""
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                expect(controller.zone()).toEqual("UTC");
 | 
			
		||||
                expect(controller.text()).toEqual("2015-154 05:56:14");
 | 
			
		||||
                expect(controller.ampm()).toEqual("PM");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not throw exceptions when model is undefined", function () {
 | 
			
		||||
                mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
 | 
			
		||||
                expect(function () {
 | 
			
		||||
                    mockScope.$watch.calls.mostRecent().args[1](undefined);
 | 
			
		||||
                }).not.toThrow();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -101,7 +101,7 @@ define(
 | 
			
		||||
                    name: "Pause"
 | 
			
		||||
                });
 | 
			
		||||
                mockStop.getMetadata.and.returnValue({
 | 
			
		||||
                    cssClass: "icon-box",
 | 
			
		||||
                    cssClass: "icon-box-round-corners",
 | 
			
		||||
                    name: "Stop"
 | 
			
		||||
                });
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,58 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/indicators/ClockIndicator"],
 | 
			
		||||
    function (ClockIndicator) {
 | 
			
		||||
 | 
			
		||||
        // Wed, 03 Jun 2015 17:56:14 GMT
 | 
			
		||||
        var TEST_TIMESTAMP = 1433354174000,
 | 
			
		||||
            TEST_FORMAT = "YYYY-DDD HH:mm:ss";
 | 
			
		||||
 | 
			
		||||
        describe("The clock indicator", function () {
 | 
			
		||||
            var mockTicker,
 | 
			
		||||
                mockUnticker,
 | 
			
		||||
                indicator;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockTicker = jasmine.createSpyObj('ticker', ['listen']);
 | 
			
		||||
                mockUnticker = jasmine.createSpy('unticker');
 | 
			
		||||
 | 
			
		||||
                mockTicker.listen.and.returnValue(mockUnticker);
 | 
			
		||||
 | 
			
		||||
                indicator = new ClockIndicator(mockTicker, TEST_FORMAT);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("displays the current time", function () {
 | 
			
		||||
                mockTicker.listen.calls.mostRecent().args[0](TEST_TIMESTAMP);
 | 
			
		||||
                expect(indicator.getText()).toEqual("2015-154 17:56:14 UTC");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("implements the Indicator interface", function () {
 | 
			
		||||
                expect(indicator.getCssClass()).toEqual(jasmine.any(String));
 | 
			
		||||
                expect(indicator.getText()).toEqual(jasmine.any(String));
 | 
			
		||||
                expect(indicator.getDescription()).toEqual(jasmine.any(String));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,120 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    './src/HyperlinkController',
 | 
			
		||||
    './res/templates/hyperlink.html'
 | 
			
		||||
], function (
 | 
			
		||||
    HyperlinkController,
 | 
			
		||||
    hyperlinkTemplate
 | 
			
		||||
) {
 | 
			
		||||
    return {
 | 
			
		||||
        name: "platform/features/hyperlink",
 | 
			
		||||
        definition: {
 | 
			
		||||
            "name": "Hyperlink",
 | 
			
		||||
            "description": "Insert a hyperlink to reference a link",
 | 
			
		||||
            "extensions": {
 | 
			
		||||
                "types": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "hyperlink",
 | 
			
		||||
                        "name": "Hyperlink",
 | 
			
		||||
                        "cssClass": "icon-chain-links",
 | 
			
		||||
                        "description": "A hyperlink to redirect to a different link",
 | 
			
		||||
                        "features": ["creation"],
 | 
			
		||||
                        "properties": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "url",
 | 
			
		||||
                                "name": "URL",
 | 
			
		||||
                                "control": "textfield",
 | 
			
		||||
                                "required": true,
 | 
			
		||||
                                "cssClass": "l-input-lg"
 | 
			
		||||
                            },
 | 
			
		||||
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "displayText",
 | 
			
		||||
                                "name": "Text to Display",
 | 
			
		||||
                                "control": "textfield",
 | 
			
		||||
                                "required": true,
 | 
			
		||||
                                "cssClass": "l-input-lg"
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "displayFormat",
 | 
			
		||||
                                "name": "Display Format",
 | 
			
		||||
                                "control": "select",
 | 
			
		||||
                                "options": [
 | 
			
		||||
                                    {
 | 
			
		||||
                                        "name": "Link",
 | 
			
		||||
                                        "value": "link"
 | 
			
		||||
                                    },
 | 
			
		||||
                                    {
 | 
			
		||||
                                        "value": "button",
 | 
			
		||||
                                        "name": "Button"
 | 
			
		||||
                                    }
 | 
			
		||||
                                ],
 | 
			
		||||
                                "cssClass": "l-inline"
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "openNewTab",
 | 
			
		||||
                                "name": "Tab to Open Hyperlink",
 | 
			
		||||
                                "control": "select",
 | 
			
		||||
                                "options": [
 | 
			
		||||
                                    {
 | 
			
		||||
                                        "name": "Open in this tab",
 | 
			
		||||
                                        "value": "thisTab"
 | 
			
		||||
                                    },
 | 
			
		||||
                                    {
 | 
			
		||||
                                        "value": "newTab",
 | 
			
		||||
                                        "name": "Open in a new tab"
 | 
			
		||||
                                    }
 | 
			
		||||
                                ],
 | 
			
		||||
                                "cssClass": "l-inline"
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "model": {
 | 
			
		||||
                            "displayFormat": "link",
 | 
			
		||||
                            "openNewTab": "thisTab",
 | 
			
		||||
                            "removeTitle": true
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "views": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "hyperlink",
 | 
			
		||||
                        "type": "hyperlink",
 | 
			
		||||
                        "name": "Hyperlink Display",
 | 
			
		||||
                        "template": hyperlinkTemplate,
 | 
			
		||||
                        "editable": false
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "controllers": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "HyperlinkController",
 | 
			
		||||
                        "implementation": HyperlinkController,
 | 
			
		||||
                        "depends": ["$scope"]
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
@@ -1,61 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This bundle adds the Hyperlink object type, which can be used to add hyperlinks as a domain Object type
 | 
			
		||||
 and into display Layouts as either a button or link that can be chosen to open in either the same tab or
 | 
			
		||||
 create a new tab to open the link in
 | 
			
		||||
 * @namespace platform/features/hyperlink
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
        function HyperlinkController($scope) {
 | 
			
		||||
            this.$scope = $scope;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**Function to analyze the location in which to open the hyperlink
 | 
			
		||||
        @returns true if the hyperlink is chosen to open in a different tab, false if the same tab
 | 
			
		||||
        **/
 | 
			
		||||
        HyperlinkController.prototype.openNewTab = function () {
 | 
			
		||||
            if (this.$scope.domainObject.getModel().openNewTab === "thisTab") {
 | 
			
		||||
                return false;
 | 
			
		||||
            } else {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**Function to specify the format in which the hyperlink should be created
 | 
			
		||||
        @returns true if the hyperlink is chosen to be created as a button, false if a link
 | 
			
		||||
        **/
 | 
			
		||||
        HyperlinkController.prototype.isButton = function () {
 | 
			
		||||
            if (this.$scope.domainObject.getModel().displayFormat === "link") {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return HyperlinkController;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
);
 | 
			
		||||
@@ -1,89 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/HyperlinkController"],
 | 
			
		||||
    function (HyperlinkController) {
 | 
			
		||||
 | 
			
		||||
        describe("The controller for hyperlinks", function () {
 | 
			
		||||
            var domainObject,
 | 
			
		||||
                controller,
 | 
			
		||||
                scope;
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                scope = jasmine.createSpyObj(
 | 
			
		||||
                    "$scope",
 | 
			
		||||
                    ["domainObject"]
 | 
			
		||||
                );
 | 
			
		||||
                domainObject = jasmine.createSpyObj(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    ["getModel"]
 | 
			
		||||
                );
 | 
			
		||||
                scope.domainObject = domainObject;
 | 
			
		||||
                controller = new HyperlinkController(scope);
 | 
			
		||||
            });
 | 
			
		||||
            it("knows when it should open a new tab", function () {
 | 
			
		||||
                scope.domainObject.getModel.and.returnValue({
 | 
			
		||||
                    "displayFormat": "link",
 | 
			
		||||
                    "openNewTab": "newTab",
 | 
			
		||||
                    "showTitle": false
 | 
			
		||||
                }
 | 
			
		||||
                );
 | 
			
		||||
                controller = new HyperlinkController(scope);
 | 
			
		||||
                expect(controller.openNewTab())
 | 
			
		||||
                    .toBe(true);
 | 
			
		||||
            });
 | 
			
		||||
            it("knows when it is a button", function () {
 | 
			
		||||
                scope.domainObject.getModel.and.returnValue({
 | 
			
		||||
                    "displayFormat": "button",
 | 
			
		||||
                    "openNewTab": "thisTab",
 | 
			
		||||
                    "showTitle": false
 | 
			
		||||
                }
 | 
			
		||||
                );
 | 
			
		||||
                controller = new HyperlinkController(scope);
 | 
			
		||||
                expect(controller.isButton())
 | 
			
		||||
                    .toEqual(true);
 | 
			
		||||
            });
 | 
			
		||||
            it("knows when it should open in the same tab", function () {
 | 
			
		||||
                scope.domainObject.getModel.and.returnValue({
 | 
			
		||||
                    "displayFormat": "link",
 | 
			
		||||
                    "openNewTab": "thisTab",
 | 
			
		||||
                    "showTitle": false
 | 
			
		||||
                }
 | 
			
		||||
                );
 | 
			
		||||
                controller = new HyperlinkController(scope);
 | 
			
		||||
                expect(controller.openNewTab())
 | 
			
		||||
                    .toBe(false);
 | 
			
		||||
            });
 | 
			
		||||
            it("knows when it is a link", function () {
 | 
			
		||||
                scope.domainObject.getModel.and.returnValue({
 | 
			
		||||
                    "displayFormat": "link",
 | 
			
		||||
                    "openNewTab": "thisTab",
 | 
			
		||||
                    "showTitle": false
 | 
			
		||||
                }
 | 
			
		||||
                );
 | 
			
		||||
                controller = new HyperlinkController(scope);
 | 
			
		||||
                expect(controller.openNewTab())
 | 
			
		||||
                    .toBe(false);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,70 +0,0 @@
 | 
			
		||||
This bundle provides the Timeline domain object type, as well
 | 
			
		||||
as other associated domain object types and relevant views.
 | 
			
		||||
 | 
			
		||||
# Implementation notes
 | 
			
		||||
 | 
			
		||||
## Model Properties
 | 
			
		||||
 | 
			
		||||
The properties below record properties relevant to using and
 | 
			
		||||
understanding timelines based on their JSON representation.
 | 
			
		||||
Additional common properties, such as `modified`
 | 
			
		||||
or `persisted` timestamps, may also be present.
 | 
			
		||||
 | 
			
		||||
### Timeline Model
 | 
			
		||||
 | 
			
		||||
A timeline's model looks like:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
    "type": "timeline",
 | 
			
		||||
    "start": {
 | 
			
		||||
        "timestamp": <number> (milliseconds since epoch),
 | 
			
		||||
        "epoch": <string> (currently, always "SET")
 | 
			
		||||
    },
 | 
			
		||||
    "capacity": <number> (optional; battery capacity in watt-hours)
 | 
			
		||||
    "composition": <string[]> (array of identifiers for contained objects)
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The identifiers in a timeline's `composition` field should refer to
 | 
			
		||||
other Timeline objects, or to Activity objects.
 | 
			
		||||
 | 
			
		||||
### Activity Model
 | 
			
		||||
 | 
			
		||||
An activity's model looks like:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
    "type": "activity",
 | 
			
		||||
    "start": {
 | 
			
		||||
        "timestamp": <number> (milliseconds since epoch),
 | 
			
		||||
        "epoch": <string> (currently, always "SET")
 | 
			
		||||
    },
 | 
			
		||||
    "duration": {
 | 
			
		||||
        "timestamp": <number> (duration of this activity, in milliseconds)
 | 
			
		||||
        "epoch": "SET" (this is ignored)
 | 
			
		||||
    },
 | 
			
		||||
    "relationships": {
 | 
			
		||||
        "modes": <string[]> (array of applicable Activity Mode ids)
 | 
			
		||||
    },
 | 
			
		||||
    "link": <string> (optional; URL linking to associated external resource)
 | 
			
		||||
    "composition": <string[]> (array of identifiers for contained objects)
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The identifiers in a timeline's `composition` field should only refer to
 | 
			
		||||
other Activity objects.
 | 
			
		||||
 | 
			
		||||
### Activity Mode Model
 | 
			
		||||
 | 
			
		||||
An activity mode's model looks like:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
    "type": "mode",
 | 
			
		||||
    "resources": {
 | 
			
		||||
        "comms": <number> (communications utilization, in Kbps)
 | 
			
		||||
        "power": <number> (power utilization, in watts)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
@@ -1,10 +0,0 @@
 | 
			
		||||
<div>
 | 
			
		||||
    Timeline, Activity and Activity Mode objects have been deprecated and will no longer be supported.
 | 
			
		||||
</div>
 | 
			
		||||
<div>
 | 
			
		||||
    Please open an issue in the
 | 
			
		||||
    <a href="https://github.com/nasa/openmct/issues" target="_blank">
 | 
			
		||||
        Open MCT Issue tracker
 | 
			
		||||
    </a>
 | 
			
		||||
    if you have any questions about the timeline plugin.
 | 
			
		||||
</div>
 | 
			
		||||
@@ -30,8 +30,8 @@ define([
 | 
			
		||||
 | 
			
		||||
    return function ImportExportPlugin() {
 | 
			
		||||
        return function (openmct) {
 | 
			
		||||
            ExportAsJSONAction.appliesTo = function (context) {
 | 
			
		||||
                return openmct.$injector.get('policyService')
 | 
			
		||||
            ExportAsJSONAction.prototype.appliesTo = function (context) {
 | 
			
		||||
                return this.openmct.$injector.get('policyService')
 | 
			
		||||
                    .allow("creation", context.domainObject.getCapability("type")
 | 
			
		||||
                    );
 | 
			
		||||
            };
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ define(
 | 
			
		||||
    ],
 | 
			
		||||
    function (ExportAsJSONAction, domainObjectFactory, MCT, AdapterCapability) {
 | 
			
		||||
 | 
			
		||||
        xdescribe("The export JSON action", function () {
 | 
			
		||||
        describe("The export JSON action", function () {
 | 
			
		||||
 | 
			
		||||
            var context,
 | 
			
		||||
                action,
 | 
			
		||||
@@ -102,7 +102,7 @@ define(
 | 
			
		||||
                expect(action).toBeDefined();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("doesn't export non-creatable objects in tree", function () {
 | 
			
		||||
            xit("doesn't export non-creatable objects in tree", function () {
 | 
			
		||||
                var nonCreatableType = {
 | 
			
		||||
                    hasFeature:
 | 
			
		||||
                        function (feature) {
 | 
			
		||||
@@ -149,7 +149,7 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("can export self-containing objects", function () {
 | 
			
		||||
            xit("can export self-containing objects", function () {
 | 
			
		||||
                var parent = domainObjectFactory({
 | 
			
		||||
                    name: 'parent',
 | 
			
		||||
                    model: {
 | 
			
		||||
@@ -191,7 +191,7 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("exports links to external objects as new objects", function () {
 | 
			
		||||
            xit("exports links to external objects as new objects", function () {
 | 
			
		||||
                var parent = domainObjectFactory({
 | 
			
		||||
                    name: 'parent',
 | 
			
		||||
                    model: {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ define(
 | 
			
		||||
    ],
 | 
			
		||||
    function (ImportAsJSONAction, domainObjectFactory) {
 | 
			
		||||
 | 
			
		||||
        xdescribe("The import JSON action", function () {
 | 
			
		||||
        describe("The import JSON action", function () {
 | 
			
		||||
 | 
			
		||||
            var context = {};
 | 
			
		||||
            var action,
 | 
			
		||||
@@ -146,7 +146,7 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("can import self-containing objects", function () {
 | 
			
		||||
            xit("can import self-containing objects", function () {
 | 
			
		||||
                var compDomainObject = domainObjectFactory({
 | 
			
		||||
                    name: 'compObject',
 | 
			
		||||
                    model: { name: 'compObject'},
 | 
			
		||||
@@ -198,7 +198,7 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("assigns new ids to each imported object", function () {
 | 
			
		||||
            xit("assigns new ids to each imported object", function () {
 | 
			
		||||
                dialogService.getUserInput.and.returnValue(Promise.resolve(
 | 
			
		||||
                    {
 | 
			
		||||
                        selectFile: {
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ define(
 | 
			
		||||
         * @param $interval Angular's $interval service
 | 
			
		||||
         * @param {string} space the name of the persistence space being served
 | 
			
		||||
         * @param {string} root the root of the path to ElasticSearch
 | 
			
		||||
         * @param {stirng} path the path to domain objects within ElasticSearch
 | 
			
		||||
         * @param {string} path the path to domain objects within ElasticSearch
 | 
			
		||||
         */
 | 
			
		||||
        function ElasticPersistenceProvider($http, $q, space, root, path) {
 | 
			
		||||
            this.spaces = [space];
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/MCT.js
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/MCT.js
									
									
									
									
									
								
							@@ -122,6 +122,7 @@ define([
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.destroy = this.destroy.bind(this);
 | 
			
		||||
        /**
 | 
			
		||||
         * Tracks current selection state of the application.
 | 
			
		||||
         * @private
 | 
			
		||||
@@ -135,7 +136,7 @@ define([
 | 
			
		||||
         * @memberof module:openmct.MCT#
 | 
			
		||||
         * @name conductor
 | 
			
		||||
         */
 | 
			
		||||
        this.time = new api.TimeAPI();
 | 
			
		||||
        this.time = new api.TimeAPI(this);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * An interface for interacting with the composition of domain objects.
 | 
			
		||||
@@ -262,7 +263,7 @@ define([
 | 
			
		||||
        // Plugins that are installed by default
 | 
			
		||||
 | 
			
		||||
        this.install(this.plugins.Plot());
 | 
			
		||||
        this.install(this.plugins.TelemetryTable());
 | 
			
		||||
        this.install(this.plugins.TelemetryTable.default());
 | 
			
		||||
        this.install(PreviewPlugin.default());
 | 
			
		||||
        this.install(LegacyIndicatorsPlugin());
 | 
			
		||||
        this.install(LicensesPlugin.default());
 | 
			
		||||
@@ -283,8 +284,10 @@ 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());
 | 
			
		||||
        this.install(this.plugins.DeviceClassifier());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MCT.prototype = Object.create(EventEmitter.prototype);
 | 
			
		||||
@@ -434,6 +437,8 @@ define([
 | 
			
		||||
                    Browse(this);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                window.addEventListener('beforeunload', this.destroy);
 | 
			
		||||
 | 
			
		||||
                this.router.start();
 | 
			
		||||
                this.emit('start');
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
@@ -457,6 +462,7 @@ define([
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    MCT.prototype.destroy = function () {
 | 
			
		||||
        window.removeEventListener('beforeunload', this.destroy);
 | 
			
		||||
        this.emit('destroy');
 | 
			
		||||
        this.router.destroy();
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
@@ -36,8 +36,7 @@ define([
 | 
			
		||||
    './views/installLegacyViews',
 | 
			
		||||
    './policies/LegacyCompositionPolicyAdapter',
 | 
			
		||||
    './actions/LegacyActionAdapter',
 | 
			
		||||
    './services/LegacyPersistenceAdapter',
 | 
			
		||||
    './services/ExportImageService'
 | 
			
		||||
    './services/LegacyPersistenceAdapter'
 | 
			
		||||
], function (
 | 
			
		||||
    ActionDialogDecorator,
 | 
			
		||||
    AdapterCapability,
 | 
			
		||||
@@ -54,8 +53,7 @@ define([
 | 
			
		||||
    installLegacyViews,
 | 
			
		||||
    legacyCompositionPolicyAdapter,
 | 
			
		||||
    LegacyActionAdapter,
 | 
			
		||||
    LegacyPersistenceAdapter,
 | 
			
		||||
    ExportImageService
 | 
			
		||||
    LegacyPersistenceAdapter
 | 
			
		||||
) {
 | 
			
		||||
    return {
 | 
			
		||||
        name: 'src/adapter',
 | 
			
		||||
@@ -84,13 +82,6 @@ define([
 | 
			
		||||
                            "identifierService",
 | 
			
		||||
                            "cacheService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "exportImageService",
 | 
			
		||||
                        "implementation": ExportImageService,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "dialogService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                components: [
 | 
			
		||||
 
 | 
			
		||||
@@ -173,10 +173,11 @@ define([
 | 
			
		||||
        const limitEvaluator = oldObject.getCapability("limit");
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            limits: function () {
 | 
			
		||||
                return limitEvaluator.limits();
 | 
			
		||||
            limits: () => {
 | 
			
		||||
                return limitEvaluator.limits.then !== undefined
 | 
			
		||||
                    ? limitEvaluator.limits()
 | 
			
		||||
                    : Promise.resolve(limitEvaluator.limits());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,218 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Module defining ExportImageService. Created by hudsonfoo on 09/02/16
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    [
 | 
			
		||||
        "html2canvas",
 | 
			
		||||
        "saveAs"
 | 
			
		||||
    ],
 | 
			
		||||
    function (
 | 
			
		||||
        html2canvas,
 | 
			
		||||
        { saveAs }
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The export image service will export any HTML node to
 | 
			
		||||
         * JPG, or PNG.
 | 
			
		||||
         * @param {object} dialogService
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function ExportImageService(dialogService) {
 | 
			
		||||
            this.dialogService = dialogService;
 | 
			
		||||
            this.exportCount = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Converts an HTML element into a PNG or JPG Blob.
 | 
			
		||||
         * @private
 | 
			
		||||
         * @param {node} element that will be converted to an image
 | 
			
		||||
         * @param {object} options Image options.
 | 
			
		||||
         * @returns {promise}
 | 
			
		||||
         */
 | 
			
		||||
        ExportImageService.prototype.renderElement = function (element, {imageType, className, thumbnailSize}) {
 | 
			
		||||
            const self = this;
 | 
			
		||||
            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) {
 | 
			
		||||
                    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 = dialogService.showBlockingMessage({
 | 
			
		||||
                    title: "Error capturing image",
 | 
			
		||||
                    severity: "error",
 | 
			
		||||
                    hint: "Image was not captured successfully!",
 | 
			
		||||
                    options: [{
 | 
			
		||||
                        label: "OK",
 | 
			
		||||
                        callback: function () {
 | 
			
		||||
                            errorDialog.dismiss();
 | 
			
		||||
                        }
 | 
			
		||||
                    }]
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        ExportImageService.prototype.getThumbnail = function (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}
 | 
			
		||||
         */
 | 
			
		||||
        ExportImageService.prototype.exportJPG = function (element, filename, className) {
 | 
			
		||||
            const processedFilename = replaceDotsWithUnderscores(filename);
 | 
			
		||||
 | 
			
		||||
            return this.renderElement(element, {
 | 
			
		||||
                imageType: 'jpg',
 | 
			
		||||
                className
 | 
			
		||||
            })
 | 
			
		||||
                .then(function (img) {
 | 
			
		||||
                    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}
 | 
			
		||||
         */
 | 
			
		||||
        ExportImageService.prototype.exportPNG = function (element, filename, className) {
 | 
			
		||||
            const processedFilename = replaceDotsWithUnderscores(filename);
 | 
			
		||||
 | 
			
		||||
            return this.renderElement(element, {
 | 
			
		||||
                imageType: 'png',
 | 
			
		||||
                className
 | 
			
		||||
            })
 | 
			
		||||
                .then(function (img) {
 | 
			
		||||
                    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}
 | 
			
		||||
         */
 | 
			
		||||
 | 
			
		||||
        ExportImageService.prototype.exportPNGtoSRC = function (element, options) {
 | 
			
		||||
 | 
			
		||||
            return this.renderElement(element, {
 | 
			
		||||
                imageType: 'png',
 | 
			
		||||
                ...options
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        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,8 +46,6 @@ class ActionCollection extends EventEmitter {
 | 
			
		||||
            this._observeObjectPath();
 | 
			
		||||
            this.openmct.editor.on('isEditing', this._updateActions);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._initializeActions();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    disable(actionKeys) {
 | 
			
		||||
@@ -156,19 +154,10 @@ class ActionCollection extends EventEmitter {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _initializeActions() {
 | 
			
		||||
        Object.keys(this.applicableActions).forEach(key => {
 | 
			
		||||
            this.applicableActions[key].callBack = () => {
 | 
			
		||||
                return this.applicableActions[key].invoke(this.objectPath, this.view);
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _updateActions() {
 | 
			
		||||
        let newApplicableActions = this.openmct.actions._applicableActions(this.objectPath, this.view);
 | 
			
		||||
 | 
			
		||||
        this.applicableActions = this._mergeOldAndNewActions(this.applicableActions, newApplicableActions);
 | 
			
		||||
        this._initializeActions();
 | 
			
		||||
        this._update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ class ActionsAPI extends EventEmitter {
 | 
			
		||||
        this._groupOrder = ['windowing', 'undefined', 'view', 'action', 'json'];
 | 
			
		||||
 | 
			
		||||
        this.register = this.register.bind(this);
 | 
			
		||||
        this.get = this.get.bind(this);
 | 
			
		||||
        this.getActionsCollection = this.getActionsCollection.bind(this);
 | 
			
		||||
        this._applicableActions = this._applicableActions.bind(this);
 | 
			
		||||
        this._updateCachedActionCollections = this._updateCachedActionCollections.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
@@ -43,12 +43,14 @@ class ActionsAPI extends EventEmitter {
 | 
			
		||||
        this._allActions[actionDefinition.key] = actionDefinition;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get(objectPath, view) {
 | 
			
		||||
        if (view) {
 | 
			
		||||
    getAction(key) {
 | 
			
		||||
        return this._allActions[key];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getActionsCollection(objectPath, view) {
 | 
			
		||||
        if (view) {
 | 
			
		||||
            return this._getCachedActionCollection(objectPath, view) || this._newActionCollection(objectPath, view, true);
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
            return this._newActionCollection(objectPath, view, true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -57,25 +59,24 @@ 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);
 | 
			
		||||
 | 
			
		||||
        return cachedActionCollection;
 | 
			
		||||
        return this._actionCollections.get(view);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _newActionCollection(objectPath, view, skipEnvironmentObservers) {
 | 
			
		||||
        let applicableActions = this._applicableActions(objectPath, view);
 | 
			
		||||
 | 
			
		||||
        return new ActionCollection(applicableActions, objectPath, view, this._openmct, skipEnvironmentObservers);
 | 
			
		||||
        const actionCollection = new ActionCollection(applicableActions, objectPath, view, this._openmct, skipEnvironmentObservers);
 | 
			
		||||
        if (view) {
 | 
			
		||||
            this._cacheActionCollection(view, actionCollection);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return actionCollection;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _cacheActionCollection(view, actionCollection) {
 | 
			
		||||
        this._actionCollections.set(view, actionCollection);
 | 
			
		||||
        actionCollection.on('destroy', this._updateCachedActionCollections);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _updateCachedActionCollections(key) {
 | 
			
		||||
 
 | 
			
		||||
@@ -106,7 +106,7 @@ describe('The Actions API', () => {
 | 
			
		||||
        it("adds action to ActionsAPI", () => {
 | 
			
		||||
            actionsAPI.register(mockAction);
 | 
			
		||||
 | 
			
		||||
            let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let action = actionCollection.getActionsObject()[mockAction.key];
 | 
			
		||||
 | 
			
		||||
            expect(action.key).toEqual(mockAction.key);
 | 
			
		||||
@@ -121,21 +121,21 @@ describe('The Actions API', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("returns an ActionCollection when invoked with an objectPath only", () => {
 | 
			
		||||
            let actionCollection = actionsAPI.get(mockObjectPath);
 | 
			
		||||
            let actionCollection = actionsAPI.getActionsCollection(mockObjectPath);
 | 
			
		||||
            let instanceOfActionCollection = actionCollection instanceof ActionCollection;
 | 
			
		||||
 | 
			
		||||
            expect(instanceOfActionCollection).toBeTrue();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("returns an ActionCollection when invoked with an objectPath and view", () => {
 | 
			
		||||
            let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let instanceOfActionCollection = actionCollection instanceof ActionCollection;
 | 
			
		||||
 | 
			
		||||
            expect(instanceOfActionCollection).toBeTrue();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("returns relevant actions when invoked with objectPath only", () => {
 | 
			
		||||
            let actionCollection = actionsAPI.get(mockObjectPath);
 | 
			
		||||
            let actionCollection = actionsAPI.getActionsCollection(mockObjectPath);
 | 
			
		||||
            let action = actionCollection.getActionsObject()[mockObjectPathAction.key];
 | 
			
		||||
 | 
			
		||||
            expect(action.key).toEqual(mockObjectPathAction.key);
 | 
			
		||||
@@ -143,7 +143,7 @@ describe('The Actions API', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("returns relevant actions when invoked with objectPath and view", () => {
 | 
			
		||||
            let actionCollection = actionsAPI.get(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let actionCollection = actionsAPI.getActionsCollection(mockObjectPath, mockViewContext1);
 | 
			
		||||
            let action = actionCollection.getActionsObject()[mockAction.key];
 | 
			
		||||
 | 
			
		||||
            expect(action.key).toEqual(mockAction.key);
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ define([
 | 
			
		||||
    StatusAPI
 | 
			
		||||
) {
 | 
			
		||||
    return {
 | 
			
		||||
        TimeAPI: TimeAPI,
 | 
			
		||||
        TimeAPI: TimeAPI.default,
 | 
			
		||||
        ObjectAPI: ObjectAPI,
 | 
			
		||||
        CompositionAPI: CompositionAPI,
 | 
			
		||||
        TypeRegistry: TypeRegistry,
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ import Menu, { MENU_PLACEMENT } from './menu.js';
 | 
			
		||||
 * @property {Boolean} isDisabled adds disable class if true
 | 
			
		||||
 * @property {String} name Menu item text
 | 
			
		||||
 * @property {String} description Menu item description
 | 
			
		||||
 * @property {Function} callBack callback function: invoked when item is clicked
 | 
			
		||||
 * @property {Function} onItemClicked callback function: invoked when item is clicked
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -66,12 +66,27 @@ class MenuAPI {
 | 
			
		||||
     * @param {Array.<Action>|Array.<Array.<Action>>} actions collection of actions{@link Action} or collection of groups of actions {@link Action}
 | 
			
		||||
     * @param {MenuOptions} [menuOptions] [Optional] The {@link MenuOptions} options for Menu
 | 
			
		||||
     */
 | 
			
		||||
    showMenu(x, y, actions, menuOptions) {
 | 
			
		||||
        this._createMenuComponent(x, y, actions, menuOptions);
 | 
			
		||||
    showMenu(x, y, items, menuOptions) {
 | 
			
		||||
        this._createMenuComponent(x, y, items, menuOptions);
 | 
			
		||||
 | 
			
		||||
        this.menuComponent.showMenu();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    actionsToMenuItems(actions, objectPath, view) {
 | 
			
		||||
        return actions.map(action => {
 | 
			
		||||
            const isActionGroup = Array.isArray(action);
 | 
			
		||||
            if (isActionGroup) {
 | 
			
		||||
                action = this.actionsToMenuItems(action, objectPath, view);
 | 
			
		||||
            } else {
 | 
			
		||||
                action.onItemClicked = () => {
 | 
			
		||||
                    action.invoke(objectPath, view);
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return action;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show popup menu with description of item on hover
 | 
			
		||||
     * @param {number} x x-coordinates for popup
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,7 @@ describe ('The Menu API', () => {
 | 
			
		||||
                name: 'Test Action 1',
 | 
			
		||||
                cssClass: 'icon-clock',
 | 
			
		||||
                description: 'This is a test action',
 | 
			
		||||
                callBack: () => {
 | 
			
		||||
                onItemClicked: () => {
 | 
			
		||||
                    result = 'Test Action 1 Invoked';
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
@@ -66,7 +66,7 @@ describe ('The Menu API', () => {
 | 
			
		||||
                name: 'Test Action 2',
 | 
			
		||||
                cssClass: 'icon-clock',
 | 
			
		||||
                description: 'This is a test action',
 | 
			
		||||
                callBack: () => {
 | 
			
		||||
                onItemClicked: () => {
 | 
			
		||||
                    result = 'Test Action 2 Invoked';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
                :key="action.name"
 | 
			
		||||
                :class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
 | 
			
		||||
                :title="action.description"
 | 
			
		||||
                @click="action.callBack"
 | 
			
		||||
                @click="action.onItemClicked"
 | 
			
		||||
            >
 | 
			
		||||
                {{ action.name }}
 | 
			
		||||
            </li>
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
            :key="action.name"
 | 
			
		||||
            :class="action.cssClass"
 | 
			
		||||
            :title="action.description"
 | 
			
		||||
            @click="action.callBack"
 | 
			
		||||
            @click="action.onItemClicked"
 | 
			
		||||
        >
 | 
			
		||||
            {{ action.name }}
 | 
			
		||||
        </li>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
                :key="action.name"
 | 
			
		||||
                :class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
 | 
			
		||||
                :title="action.description"
 | 
			
		||||
                @click="action.callBack"
 | 
			
		||||
                @click="action.onItemClicked"
 | 
			
		||||
                @mouseover="toggleItemDescription(action)"
 | 
			
		||||
                @mouseleave="toggleItemDescription()"
 | 
			
		||||
            >
 | 
			
		||||
@@ -42,7 +42,7 @@
 | 
			
		||||
            :key="action.name"
 | 
			
		||||
            :class="action.cssClass"
 | 
			
		||||
            :title="action.description"
 | 
			
		||||
            @click="action.callBack"
 | 
			
		||||
            @click="action.onItemClicked"
 | 
			
		||||
            @mouseover="toggleItemDescription(action)"
 | 
			
		||||
            @mouseleave="toggleItemDescription()"
 | 
			
		||||
        >
 | 
			
		||||
 
 | 
			
		||||
@@ -71,12 +71,12 @@ class Menu extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    showMenu() {
 | 
			
		||||
        this.component = new Vue({
 | 
			
		||||
            provide: {
 | 
			
		||||
                options: this.options
 | 
			
		||||
            },
 | 
			
		||||
            components: {
 | 
			
		||||
                MenuComponent
 | 
			
		||||
            },
 | 
			
		||||
            provide: {
 | 
			
		||||
                options: this.options
 | 
			
		||||
            },
 | 
			
		||||
            template: '<menu-component />'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -85,12 +85,12 @@ class Menu extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
    showSuperMenu() {
 | 
			
		||||
        this.component = new Vue({
 | 
			
		||||
            provide: {
 | 
			
		||||
                options: this.options
 | 
			
		||||
            },
 | 
			
		||||
            components: {
 | 
			
		||||
                SuperMenuComponent
 | 
			
		||||
            },
 | 
			
		||||
            provide: {
 | 
			
		||||
                options: this.options
 | 
			
		||||
            },
 | 
			
		||||
            template: '<super-menu-component />'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ import EventEmitter from 'EventEmitter';
 | 
			
		||||
 *
 | 
			
		||||
 * @typedef {object} NotificationModel
 | 
			
		||||
 * @property {string} message The message to be displayed by the notification
 | 
			
		||||
 * @property {number | 'unknown'} [progress] The progres of some ongoing task. Should be a number between 0 and 100, or
 | 
			
		||||
 * @property {number | 'unknown'} [progress] The progress of some ongoing task. Should be a number between 0 and 100, or
 | 
			
		||||
 * with the string literal 'unknown'.
 | 
			
		||||
 * @property {string} [progressText] A message conveying progress of some ongoing task.
 | 
			
		||||
 | 
			
		||||
@@ -98,7 +98,7 @@ export default class NotificationAPI extends EventEmitter {
 | 
			
		||||
     * Present an alert to the user.
 | 
			
		||||
     * @param {string} message The message to display to the user.
 | 
			
		||||
     * @param {Object} [options] object with following properties
 | 
			
		||||
     *      autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
 | 
			
		||||
     *      autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
 | 
			
		||||
     *      link: {Object} Add a link to notifications for navigation
 | 
			
		||||
     *              onClick: callback function
 | 
			
		||||
     *              cssClass: css class name to add style on link
 | 
			
		||||
@@ -119,7 +119,7 @@ export default class NotificationAPI extends EventEmitter {
 | 
			
		||||
     * Present an error message to the user
 | 
			
		||||
     * @param {string} message
 | 
			
		||||
     * @param {Object} [options] object with following properties
 | 
			
		||||
     *      autoDismissTimeout: {number} in miliseconds to automatically dismisses notification
 | 
			
		||||
     *      autoDismissTimeout: {number} in milliseconds to automatically dismisses notification
 | 
			
		||||
     *      link: {Object} Add a link to notifications for navigation
 | 
			
		||||
     *              onClick: callback function
 | 
			
		||||
     *              cssClass: css class name to add style on link
 | 
			
		||||
 
 | 
			
		||||
@@ -358,6 +358,20 @@ ObjectAPI.prototype.applyGetInterceptors = function (identifier, domainObject) {
 | 
			
		||||
    return domainObject;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Return relative url path from a given object path
 | 
			
		||||
 * eg: #/browse/mine/cb56f6bf-c900-43b7-b923-2e3b64b412db/6e89e858-77ce-46e4-a1ad-749240286497/....
 | 
			
		||||
 * @param {Array} objectPath
 | 
			
		||||
 * @returns {string} relative url for object
 | 
			
		||||
 */
 | 
			
		||||
ObjectAPI.prototype.getRelativePath = function (objectPath) {
 | 
			
		||||
    return objectPath
 | 
			
		||||
        .map(p => this.makeKeyString(p.identifier))
 | 
			
		||||
        .reverse()
 | 
			
		||||
        .join('/')
 | 
			
		||||
    ;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Modify a domain object.
 | 
			
		||||
 * @param {module:openmct.DomainObject} object the object to mutate
 | 
			
		||||
 
 | 
			
		||||
@@ -10,28 +10,37 @@ const cssClasses = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Overlay extends EventEmitter {
 | 
			
		||||
    constructor(options) {
 | 
			
		||||
    constructor({
 | 
			
		||||
        buttons,
 | 
			
		||||
        autoHide = true,
 | 
			
		||||
        dismissable = true,
 | 
			
		||||
        element,
 | 
			
		||||
        onDestroy,
 | 
			
		||||
        size
 | 
			
		||||
    } = {}) {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this.dismissable = options.dismissable !== false;
 | 
			
		||||
        this.container = document.createElement('div');
 | 
			
		||||
        this.container.classList.add('l-overlay-wrapper', cssClasses[options.size]);
 | 
			
		||||
        this.container.classList.add('l-overlay-wrapper', cssClasses[size]);
 | 
			
		||||
 | 
			
		||||
        this.autoHide = autoHide;
 | 
			
		||||
        this.dismissable = dismissable !== false;
 | 
			
		||||
 | 
			
		||||
        this.component = new Vue({
 | 
			
		||||
            provide: {
 | 
			
		||||
                dismiss: this.dismiss.bind(this),
 | 
			
		||||
                element: options.element,
 | 
			
		||||
                buttons: options.buttons,
 | 
			
		||||
                dismissable: this.dismissable
 | 
			
		||||
            },
 | 
			
		||||
            components: {
 | 
			
		||||
                OverlayComponent: OverlayComponent
 | 
			
		||||
            },
 | 
			
		||||
            provide: {
 | 
			
		||||
                dismiss: this.dismiss.bind(this),
 | 
			
		||||
                element,
 | 
			
		||||
                buttons,
 | 
			
		||||
                dismissable: this.dismissable
 | 
			
		||||
            },
 | 
			
		||||
            template: '<overlay-component></overlay-component>'
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (options.onDestroy) {
 | 
			
		||||
            this.once('destroy', options.onDestroy);
 | 
			
		||||
        if (onDestroy) {
 | 
			
		||||
            this.once('destroy', onDestroy);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,10 @@ class OverlayAPI {
 | 
			
		||||
     */
 | 
			
		||||
    showOverlay(overlay) {
 | 
			
		||||
        if (this.activeOverlays.length) {
 | 
			
		||||
            this.activeOverlays[this.activeOverlays.length - 1].container.classList.add('invisible');
 | 
			
		||||
            const previousOverlay = this.activeOverlays[this.activeOverlays.length - 1];
 | 
			
		||||
            if (previousOverlay.autoHide) {
 | 
			
		||||
                previousOverlay.container.classList.add('invisible');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.activeOverlays.push(overlay);
 | 
			
		||||
@@ -60,7 +63,7 @@ class OverlayAPI {
 | 
			
		||||
     * A description of option properties that can be passed into the overlay
 | 
			
		||||
     * @typedef options
 | 
			
		||||
        * @property {object} element DOMElement that is to be inserted/shown on the overlay
 | 
			
		||||
        * @property {string} size prefered size of the overlay (large, small, fit)
 | 
			
		||||
        * @property {string} size preferred size of the overlay (large, small, fit)
 | 
			
		||||
        * @property {array} buttons optional button objects with label and callback properties
 | 
			
		||||
        * @property {function} onDestroy callback to be called when overlay is destroyed
 | 
			
		||||
        * @property {boolean} dismissable allow user to dismiss overlay by using esc, and clicking away
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
        ></button>
 | 
			
		||||
        <div
 | 
			
		||||
            ref="element"
 | 
			
		||||
            class="c-overlay__contents"
 | 
			
		||||
            class="c-overlay__contents js-notebook-snapshot-item-wrapper"
 | 
			
		||||
            tabindex="0"
 | 
			
		||||
        ></div>
 | 
			
		||||
        <div
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
const { TelemetryCollection } = require("./TelemetryCollection");
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    '../../plugins/displayLayout/CustomStringFormatter',
 | 
			
		||||
    './TelemetryMetadataManager',
 | 
			
		||||
@@ -273,6 +275,28 @@ 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 requestCollection
 | 
			
		||||
     * @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.requestCollection = 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
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										396
									
								
								src/api/telemetry/TelemetryCollection.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										396
									
								
								src/api/telemetry/TelemetryCollection.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,396 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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';
 | 
			
		||||
 | 
			
		||||
const ERRORS = {
 | 
			
		||||
    TIMESYSTEM_KEY: 'All telemetry metadata must have a telemetry value with a key that matches the key of the active time system.',
 | 
			
		||||
    LOADED: 'Telemetry Collection has already been loaded.'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 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) {
 | 
			
		||||
            this._error(ERRORS.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;
 | 
			
		||||
 | 
			
		||||
        this.options.onPartialResponse = this._processNewTelemetry.bind(this);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.requestAbort = new AbortController();
 | 
			
		||||
            this.options.signal = this.requestAbort.signal;
 | 
			
		||||
            historicalData = await this.historicalProvider.request(this.domainObject, this.options);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (error.name !== 'AbortError') {
 | 
			
		||||
                console.error('Error requesting telemetry data...');
 | 
			
		||||
                this._error(error);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.requestAbort = undefined;
 | 
			
		||||
 | 
			
		||||
        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.bind(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) {
 | 
			
		||||
        let domains = this.metadata.valuesForHints(['domain']);
 | 
			
		||||
        let domain = domains.find((d) => d.key === timeSystem.key);
 | 
			
		||||
 | 
			
		||||
        if (domain === undefined) {
 | 
			
		||||
            this._error(ERRORS.TIMESYSTEM_KEY);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // timeKey is used to create a dummy datum used for sorting
 | 
			
		||||
        this.timeKey = domain.source; // this defaults to key if no source is set
 | 
			
		||||
        let metadataValue = this.metadata.value(timeSystem.key) || { format: timeSystem.key };
 | 
			
		||||
        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.emit('clear');
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * will throw a new Error, for passed in message
 | 
			
		||||
     * @param  {string} message Message describing the error
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _error(message) {
 | 
			
		||||
        throw new Error(message);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										106
									
								
								src/api/time/GlobalTimeContext.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/api/time/GlobalTimeContext.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 TimeContext from "./TimeContext";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The GlobalContext handles getting and setting time of the openmct application in general.
 | 
			
		||||
 * Views will use this context unless they specify an alternate/independent time context
 | 
			
		||||
 */
 | 
			
		||||
class GlobalTimeContext extends TimeContext {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        //The Time Of Interest
 | 
			
		||||
        this.toi = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the start and end time of the time conductor. Basic validation
 | 
			
		||||
     * of bounds is performed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
 | 
			
		||||
     * @throws {Error} Validation error
 | 
			
		||||
     * @fires module:openmct.TimeAPI~bounds
 | 
			
		||||
     * @returns {module:openmct.TimeAPI~TimeConductorBounds}
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method bounds
 | 
			
		||||
     */
 | 
			
		||||
    bounds(newBounds) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            super.bounds.call(this, ...arguments);
 | 
			
		||||
            // If a bounds change results in a TOI outside of the current
 | 
			
		||||
            // bounds, unset it
 | 
			
		||||
            if (this.toi < newBounds.start || this.toi > newBounds.end) {
 | 
			
		||||
                this.timeOfInterest(undefined);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Return a copy to prevent direct mutation of time conductor bounds.
 | 
			
		||||
        return JSON.parse(JSON.stringify(this.boundsVal));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update bounds based on provided time and current offsets
 | 
			
		||||
     * @private
 | 
			
		||||
     * @param {number} timestamp A time from which bounds will be calculated
 | 
			
		||||
     * using current offsets.
 | 
			
		||||
     */
 | 
			
		||||
    tick(timestamp) {
 | 
			
		||||
        super.tick.call(this, ...arguments);
 | 
			
		||||
 | 
			
		||||
        // If a bounds change results in a TOI outside of the current
 | 
			
		||||
        // bounds, unset it
 | 
			
		||||
        if (this.toi < this.boundsVal.start || this.toi > this.boundsVal.end) {
 | 
			
		||||
            this.timeOfInterest(undefined);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the Time of Interest. The Time of Interest is a single point
 | 
			
		||||
     * in time, and constitutes the temporal focus of application views. It can
 | 
			
		||||
     * be manipulated by the user from the time conductor or from other views.
 | 
			
		||||
     * The time of interest can effectively be unset by assigning a value of
 | 
			
		||||
     * 'undefined'.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~timeOfInterest
 | 
			
		||||
     * @param newTOI
 | 
			
		||||
     * @returns {number} the current time of interest
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method timeOfInterest
 | 
			
		||||
     */
 | 
			
		||||
    timeOfInterest(newTOI) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            this.toi = newTOI;
 | 
			
		||||
            /**
 | 
			
		||||
             * The Time of Interest has moved.
 | 
			
		||||
             * @event timeOfInterest
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {number} Current time of interest
 | 
			
		||||
             */
 | 
			
		||||
            this.emit('timeOfInterest', this.toi);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.toi;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default GlobalTimeContext;
 | 
			
		||||
							
								
								
									
										94
									
								
								src/api/time/IndependentTimeContext.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/api/time/IndependentTimeContext.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 TimeContext from "./TimeContext";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The IndependentTimeContext handles getting and setting time of the openmct application in general.
 | 
			
		||||
 * Views will use the GlobalTimeContext unless they specify an alternate/independent time context here.
 | 
			
		||||
 */
 | 
			
		||||
class IndependentTimeContext extends TimeContext {
 | 
			
		||||
    constructor(globalTimeContext, key) {
 | 
			
		||||
        super();
 | 
			
		||||
        this.key = key;
 | 
			
		||||
 | 
			
		||||
        this.globalTimeContext = globalTimeContext;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the active clock. Tick source will be immediately subscribed to
 | 
			
		||||
     * and ticking will begin. Offsets from 'now' must also be provided. A clock
 | 
			
		||||
     * can be unset by calling {@link stopClock}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Clock || string} keyOrClock The clock to activate, or its key
 | 
			
		||||
     * @param {ClockOffsets} offsets on each tick these will be used to calculate
 | 
			
		||||
     * the start and end bounds. This maintains a sliding time window of a fixed
 | 
			
		||||
     * width that automatically updates.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~clock
 | 
			
		||||
     * @return {Clock} the currently active clock;
 | 
			
		||||
     */
 | 
			
		||||
    clock(keyOrClock, offsets) {
 | 
			
		||||
        if (arguments.length === 2) {
 | 
			
		||||
            let clock;
 | 
			
		||||
 | 
			
		||||
            if (typeof keyOrClock === 'string') {
 | 
			
		||||
                clock = this.globalTimeContext.clocks.get(keyOrClock);
 | 
			
		||||
                if (clock === undefined) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof keyOrClock === 'object') {
 | 
			
		||||
                clock = keyOrClock;
 | 
			
		||||
                if (!this.globalTimeContext.clocks.has(clock.key)) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const previousClock = this.activeClock;
 | 
			
		||||
            if (previousClock !== undefined) {
 | 
			
		||||
                previousClock.off("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.activeClock = clock;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The active clock has changed. Clock can be unset by calling {@link stopClock}
 | 
			
		||||
             * @event clock
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {Clock} clock The newly activated clock, or undefined
 | 
			
		||||
             * if the system is no longer following a clock source
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clock", this.activeClock);
 | 
			
		||||
 | 
			
		||||
            if (this.activeClock !== undefined) {
 | 
			
		||||
                this.clockOffsets(offsets);
 | 
			
		||||
                this.activeClock.on("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else if (arguments.length === 1) {
 | 
			
		||||
            throw "When setting the clock, clock offsets must also be provided";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.activeClock;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default IndependentTimeContext;
 | 
			
		||||
@@ -20,51 +20,35 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(['EventEmitter'], function (EventEmitter) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The public API for setting and querying the temporal state of the
 | 
			
		||||
     * application. The concept of time is integral to Open MCT, and at least
 | 
			
		||||
     * one {@link TimeSystem}, as well as some default time bounds must be
 | 
			
		||||
     * registered and enabled via {@link TimeAPI.addTimeSystem} and
 | 
			
		||||
     * {@link TimeAPI.timeSystem} respectively for Open MCT to work.
 | 
			
		||||
     *
 | 
			
		||||
     * Time-sensitive views will typically respond to changes to bounds or other
 | 
			
		||||
     * properties of the time conductor and update the data displayed based on
 | 
			
		||||
     * the temporal state of the application. The current time bounds are also
 | 
			
		||||
     * used in queries for historical data.
 | 
			
		||||
     *
 | 
			
		||||
     * The TimeAPI extends the EventEmitter class. A number of events are
 | 
			
		||||
     * fired when properties of the time conductor change, which are documented
 | 
			
		||||
     * below.
 | 
			
		||||
     *
 | 
			
		||||
     * @interface
 | 
			
		||||
     * @memberof module:openmct
 | 
			
		||||
     */
 | 
			
		||||
    function TimeAPI() {
 | 
			
		||||
        EventEmitter.call(this);
 | 
			
		||||
 | 
			
		||||
        //The Time System
 | 
			
		||||
        this.system = undefined;
 | 
			
		||||
        //The Time Of Interest
 | 
			
		||||
        this.toi = undefined;
 | 
			
		||||
 | 
			
		||||
        this.boundsVal = {
 | 
			
		||||
            start: undefined,
 | 
			
		||||
            end: undefined
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.timeSystems = new Map();
 | 
			
		||||
        this.clocks = new Map();
 | 
			
		||||
        this.activeClock = undefined;
 | 
			
		||||
        this.offsets = undefined;
 | 
			
		||||
 | 
			
		||||
        this.tick = this.tick.bind(this);
 | 
			
		||||
import GlobalTimeContext from "./GlobalTimeContext";
 | 
			
		||||
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* The public API for setting and querying the temporal state of the
 | 
			
		||||
* application. The concept of time is integral to Open MCT, and at least
 | 
			
		||||
* one {@link TimeSystem}, as well as some default time bounds must be
 | 
			
		||||
* registered and enabled via {@link TimeAPI.addTimeSystem} and
 | 
			
		||||
* {@link TimeAPI.timeSystem} respectively for Open MCT to work.
 | 
			
		||||
*
 | 
			
		||||
* Time-sensitive views will typically respond to changes to bounds or other
 | 
			
		||||
* properties of the time conductor and update the data displayed based on
 | 
			
		||||
* the temporal state of the application. The current time bounds are also
 | 
			
		||||
* used in queries for historical data.
 | 
			
		||||
*
 | 
			
		||||
* The TimeAPI extends the GlobalTimeContext which in turn extends the TimeContext/EventEmitter class. A number of events are
 | 
			
		||||
* fired when properties of the time conductor change, which are documented
 | 
			
		||||
* below.
 | 
			
		||||
*
 | 
			
		||||
* @interface
 | 
			
		||||
* @memberof module:openmct
 | 
			
		||||
*/
 | 
			
		||||
class TimeAPI extends GlobalTimeContext {
 | 
			
		||||
    constructor(openmct) {
 | 
			
		||||
        super();
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.independentContexts = new Map();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TimeAPI.prototype = Object.create(EventEmitter.prototype);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A TimeSystem provides meaning to the values returned by the TimeAPI. Open
 | 
			
		||||
     * MCT supports multiple different types of time values, although all are
 | 
			
		||||
@@ -94,16 +78,16 @@ define(['EventEmitter'], function (EventEmitter) {
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @param {TimeSystem} timeSystem A time system object.
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.addTimeSystem = function (timeSystem) {
 | 
			
		||||
    addTimeSystem(timeSystem) {
 | 
			
		||||
        this.timeSystems.set(timeSystem.key, timeSystem);
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @returns {TimeSystem[]}
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.getAllTimeSystems = function () {
 | 
			
		||||
    getAllTimeSystems() {
 | 
			
		||||
        return Array.from(this.timeSystems.values());
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clocks provide a timing source that is used to
 | 
			
		||||
@@ -126,340 +110,81 @@ define(['EventEmitter'], function (EventEmitter) {
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @param {Clock} clock
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.addClock = function (clock) {
 | 
			
		||||
    addClock(clock) {
 | 
			
		||||
        this.clocks.set(clock.key, clock);
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @returns {Clock[]}
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.getAllClocks = function () {
 | 
			
		||||
    getAllClocks() {
 | 
			
		||||
        return Array.from(this.clocks.values());
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate the given bounds. This can be used for pre-validation of bounds,
 | 
			
		||||
     * for example by views validating user inputs.
 | 
			
		||||
     * @param {TimeBounds} bounds The start and end time of the conductor.
 | 
			
		||||
     * @returns {string | true} A validation error, or true if valid
 | 
			
		||||
     * Get or set an independent time context which follows the TimeAPI timeSystem,
 | 
			
		||||
     * but with different offsets for a given domain object
 | 
			
		||||
     * @param {key | string} key The identifier key of the domain object these offsets are set for
 | 
			
		||||
     * @param {ClockOffsets | TimeBounds} value This maintains a sliding time window of a fixed width that automatically updates
 | 
			
		||||
     * @param {key | string} clockKey the real time clock key currently in use
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method validateBounds
 | 
			
		||||
     * @method addIndependentTimeContext
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.validateBounds = function (bounds) {
 | 
			
		||||
        if ((bounds.start === undefined)
 | 
			
		||||
            || (bounds.end === undefined)
 | 
			
		||||
            || isNaN(bounds.start)
 | 
			
		||||
            || isNaN(bounds.end)
 | 
			
		||||
        ) {
 | 
			
		||||
            return "Start and end must be specified as integer values";
 | 
			
		||||
        } else if (bounds.start > bounds.end) {
 | 
			
		||||
            return "Specified start date exceeds end bound";
 | 
			
		||||
    addIndependentContext(key, value, clockKey) {
 | 
			
		||||
        let timeContext = this.independentContexts.get(key);
 | 
			
		||||
        if (!timeContext) {
 | 
			
		||||
            timeContext = new IndependentTimeContext(this, key);
 | 
			
		||||
            this.independentContexts.set(key, timeContext);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate the given offsets. This can be used for pre-validation of
 | 
			
		||||
     * offsets, for example by views validating user inputs.
 | 
			
		||||
     * @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
 | 
			
		||||
     * @returns {string | true} A validation error, or true if valid
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method validateBounds
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.validateOffsets = function (offsets) {
 | 
			
		||||
        if ((offsets.start === undefined)
 | 
			
		||||
            || (offsets.end === undefined)
 | 
			
		||||
            || isNaN(offsets.start)
 | 
			
		||||
            || isNaN(offsets.end)
 | 
			
		||||
        ) {
 | 
			
		||||
            return "Start and end offsets must be specified as integer values";
 | 
			
		||||
        } else if (offsets.start >= offsets.end) {
 | 
			
		||||
            return "Specified start offset must be < end offset";
 | 
			
		||||
        if (clockKey) {
 | 
			
		||||
            timeContext.clock(clockKey, value);
 | 
			
		||||
        } else {
 | 
			
		||||
            timeContext.stopClock();
 | 
			
		||||
            timeContext.bounds(value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    };
 | 
			
		||||
        this.emit('timeContext', key);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @typedef {Object} TimeBounds
 | 
			
		||||
     * @property {number} start The start time displayed by the time conductor
 | 
			
		||||
     * in ms since epoch. Epoch determined by currently active time system
 | 
			
		||||
     * @property {number} end The end time displayed by the time conductor in ms
 | 
			
		||||
     * since epoch.
 | 
			
		||||
     * @memberof module:openmct.TimeAPI~
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the start and end time of the time conductor. Basic validation
 | 
			
		||||
     * of bounds is performed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
 | 
			
		||||
     * @throws {Error} Validation error
 | 
			
		||||
     * @fires module:openmct.TimeAPI~bounds
 | 
			
		||||
     * @returns {module:openmct.TimeAPI~TimeConductorBounds}
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method bounds
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.bounds = function (newBounds) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            const validationResult = this.validateBounds(newBounds);
 | 
			
		||||
            if (validationResult !== true) {
 | 
			
		||||
                throw new Error(validationResult);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //Create a copy to avoid direct mutation of conductor bounds
 | 
			
		||||
            this.boundsVal = JSON.parse(JSON.stringify(newBounds));
 | 
			
		||||
            /**
 | 
			
		||||
             * The start time, end time, or both have been updated.
 | 
			
		||||
             * @event bounds
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {TimeConductorBounds} bounds The newly updated bounds
 | 
			
		||||
             * @property {boolean} [tick] `true` if the bounds update was due to
 | 
			
		||||
             * a "tick" event (ie. was an automatic update), false otherwise.
 | 
			
		||||
             */
 | 
			
		||||
            this.emit('bounds', this.boundsVal, false);
 | 
			
		||||
 | 
			
		||||
            // If a bounds change results in a TOI outside of the current
 | 
			
		||||
            // bounds, unset it
 | 
			
		||||
            if (this.toi < newBounds.start || this.toi > newBounds.end) {
 | 
			
		||||
                this.timeOfInterest(undefined);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Return a copy to prevent direct mutation of time conductor bounds.
 | 
			
		||||
        return JSON.parse(JSON.stringify(this.boundsVal));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the time system of the TimeAPI.
 | 
			
		||||
     * @param {TimeSystem | string} timeSystem
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
 | 
			
		||||
     * @fires module:openmct.TimeAPI~timeSystem
 | 
			
		||||
     * @returns {TimeSystem} The currently applied time system
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method timeSystem
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.timeSystem = function (timeSystemOrKey, bounds) {
 | 
			
		||||
        if (arguments.length >= 1) {
 | 
			
		||||
            if (arguments.length === 1 && !this.activeClock) {
 | 
			
		||||
                throw new Error(
 | 
			
		||||
                    "Must specify bounds when changing time system without "
 | 
			
		||||
                    + "an active clock."
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let timeSystem;
 | 
			
		||||
 | 
			
		||||
            if (timeSystemOrKey === undefined) {
 | 
			
		||||
                throw "Please provide a time system";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (typeof timeSystemOrKey === 'string') {
 | 
			
		||||
                timeSystem = this.timeSystems.get(timeSystemOrKey);
 | 
			
		||||
 | 
			
		||||
                if (timeSystem === undefined) {
 | 
			
		||||
                    throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof timeSystemOrKey === 'object') {
 | 
			
		||||
                timeSystem = timeSystemOrKey;
 | 
			
		||||
 | 
			
		||||
                if (!this.timeSystems.has(timeSystem.key)) {
 | 
			
		||||
                    throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.system = timeSystem;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The time system used by the time
 | 
			
		||||
             * conductor has changed. A change in Time System will always be
 | 
			
		||||
             * followed by a bounds event specifying new query bounds.
 | 
			
		||||
             *
 | 
			
		||||
             * @event module:openmct.TimeAPI~timeSystem
 | 
			
		||||
             * @property {TimeSystem} The value of the currently applied
 | 
			
		||||
             * Time System
 | 
			
		||||
             * */
 | 
			
		||||
            this.emit('timeSystem', this.system);
 | 
			
		||||
            if (bounds) {
 | 
			
		||||
                this.bounds(bounds);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.system;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the Time of Interest. The Time of Interest is a single point
 | 
			
		||||
     * in time, and constitutes the temporal focus of application views. It can
 | 
			
		||||
     * be manipulated by the user from the time conductor or from other views.
 | 
			
		||||
     * The time of interest can effectively be unset by assigning a value of
 | 
			
		||||
     * 'undefined'.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~timeOfInterest
 | 
			
		||||
     * @param newTOI
 | 
			
		||||
     * @returns {number} the current time of interest
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method timeOfInterest
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.timeOfInterest = function (newTOI) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            this.toi = newTOI;
 | 
			
		||||
            /**
 | 
			
		||||
             * The Time of Interest has moved.
 | 
			
		||||
             * @event timeOfInterest
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {number} Current time of interest
 | 
			
		||||
             */
 | 
			
		||||
            this.emit('timeOfInterest', this.toi);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.toi;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update bounds based on provided time and current offsets
 | 
			
		||||
     * @private
 | 
			
		||||
     * @param {number} timestamp A time from which boudns will be calculated
 | 
			
		||||
     * using current offsets.
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.tick = function (timestamp) {
 | 
			
		||||
        const newBounds = {
 | 
			
		||||
            start: timestamp + this.offsets.start,
 | 
			
		||||
            end: timestamp + this.offsets.end
 | 
			
		||||
        return () => {
 | 
			
		||||
            this.independentContexts.delete(key);
 | 
			
		||||
            timeContext.emit('timeContext', key);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.boundsVal = newBounds;
 | 
			
		||||
        this.emit('bounds', this.boundsVal, true);
 | 
			
		||||
 | 
			
		||||
        // If a bounds change results in a TOI outside of the current
 | 
			
		||||
        // bounds, unset it
 | 
			
		||||
        if (this.toi < newBounds.start || this.toi > newBounds.end) {
 | 
			
		||||
            this.timeOfInterest(undefined);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the active clock. Tick source will be immediately subscribed to
 | 
			
		||||
     * and ticking will begin. Offsets from 'now' must also be provided. A clock
 | 
			
		||||
     * can be unset by calling {@link stopClock}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Clock || string} The clock to activate, or its key
 | 
			
		||||
     * @param {ClockOffsets} offsets on each tick these will be used to calculate
 | 
			
		||||
     * the start and end bounds. This maintains a sliding time window of a fixed
 | 
			
		||||
     * width that automatically updates.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~clock
 | 
			
		||||
     * @return {Clock} the currently active clock;
 | 
			
		||||
     * Get the independent time context which follows the TimeAPI timeSystem,
 | 
			
		||||
     * but with different offsets.
 | 
			
		||||
     * @param {key | string} key The identifier key of the domain object these offsets
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method getIndependentTimeContext
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.clock = function (keyOrClock, offsets) {
 | 
			
		||||
        if (arguments.length === 2) {
 | 
			
		||||
            let clock;
 | 
			
		||||
 | 
			
		||||
            if (typeof keyOrClock === 'string') {
 | 
			
		||||
                clock = this.clocks.get(keyOrClock);
 | 
			
		||||
                if (clock === undefined) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof keyOrClock === 'object') {
 | 
			
		||||
                clock = keyOrClock;
 | 
			
		||||
                if (!this.clocks.has(clock.key)) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const previousClock = this.activeClock;
 | 
			
		||||
            if (previousClock !== undefined) {
 | 
			
		||||
                previousClock.off("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.activeClock = clock;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The active clock has changed. Clock can be unset by calling {@link stopClock}
 | 
			
		||||
             * @event clock
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {Clock} clock The newly activated clock, or undefined
 | 
			
		||||
             * if the system is no longer following a clock source
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clock", this.activeClock);
 | 
			
		||||
 | 
			
		||||
            if (this.activeClock !== undefined) {
 | 
			
		||||
                this.clockOffsets(offsets);
 | 
			
		||||
                this.activeClock.on("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else if (arguments.length === 1) {
 | 
			
		||||
            throw "When setting the clock, clock offsets must also be provided";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.activeClock;
 | 
			
		||||
    };
 | 
			
		||||
    getIndependentContext(key) {
 | 
			
		||||
        return this.independentContexts.get(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clock offsets are used to calculate temporal bounds when the system is
 | 
			
		||||
     * ticking on a clock source.
 | 
			
		||||
     *
 | 
			
		||||
     * @typedef {object} ClockOffsets
 | 
			
		||||
     * @property {number} start A time span relative to the current value of the
 | 
			
		||||
     * ticking clock, from which start bounds will be calculated. This value must
 | 
			
		||||
     * be < 0. When a clock is active, bounds will be calculated automatically
 | 
			
		||||
     * based on the value provided by the clock, and the defined clock offsets.
 | 
			
		||||
     * @property {number} end A time span relative to the current value of the
 | 
			
		||||
     * ticking clock, from which end bounds will be calculated. This value must
 | 
			
		||||
     * be >= 0.
 | 
			
		||||
     * Get the a timeContext for a view based on it's objectPath. If there is any object in the objectPath with an independent time context, it will be returned.
 | 
			
		||||
     * Otherwise, the global time context will be returned.
 | 
			
		||||
     * @param { Array } objectPath The view's objectPath
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method getContextForView
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the currently applied clock offsets. If no parameter is provided,
 | 
			
		||||
     * the current value will be returned. If provided, the new value will be
 | 
			
		||||
     * used as the new clock offsets.
 | 
			
		||||
     * @param {ClockOffsets} offsets
 | 
			
		||||
     * @returns {ClockOffsets}
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.clockOffsets = function (offsets) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
    getContextForView(objectPath) {
 | 
			
		||||
        let timeContext = this;
 | 
			
		||||
 | 
			
		||||
            const validationResult = this.validateOffsets(offsets);
 | 
			
		||||
            if (validationResult !== true) {
 | 
			
		||||
                throw new Error(validationResult);
 | 
			
		||||
        objectPath.forEach(item => {
 | 
			
		||||
            const key = this.openmct.objects.makeKeyString(item.identifier);
 | 
			
		||||
            if (this.independentContexts.get(key)) {
 | 
			
		||||
                timeContext = this.independentContexts.get(key);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
            this.offsets = offsets;
 | 
			
		||||
        return timeContext;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
            const currentValue = this.activeClock.currentValue();
 | 
			
		||||
            const newBounds = {
 | 
			
		||||
                start: currentValue + offsets.start,
 | 
			
		||||
                end: currentValue + offsets.end
 | 
			
		||||
            };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
            this.bounds(newBounds);
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Event that is triggered when clock offsets change.
 | 
			
		||||
             * @event clockOffsets
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {ClockOffsets} clockOffsets The newly activated clock
 | 
			
		||||
             * offsets.
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clockOffsets", offsets);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.offsets;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stop the currently active clock from ticking, and unset it. This will
 | 
			
		||||
     * revert all views to showing a static time frame defined by the current
 | 
			
		||||
     * bounds.
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.stopClock = function () {
 | 
			
		||||
        if (this.activeClock) {
 | 
			
		||||
            this.clock(undefined, undefined);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return TimeAPI;
 | 
			
		||||
});
 | 
			
		||||
export default TimeAPI;
 | 
			
		||||
 
 | 
			
		||||
@@ -19,241 +19,243 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import TimeAPI from "./TimeAPI";
 | 
			
		||||
import {createOpenMct} from "utils/testing";
 | 
			
		||||
 | 
			
		||||
define(['./TimeAPI'], function (TimeAPI) {
 | 
			
		||||
    describe("The Time API", function () {
 | 
			
		||||
        let api;
 | 
			
		||||
        let timeSystemKey;
 | 
			
		||||
        let timeSystem;
 | 
			
		||||
        let clockKey;
 | 
			
		||||
        let clock;
 | 
			
		||||
        let bounds;
 | 
			
		||||
        let eventListener;
 | 
			
		||||
        let toi;
 | 
			
		||||
describe("The Time API", function () {
 | 
			
		||||
    let api;
 | 
			
		||||
    let timeSystemKey;
 | 
			
		||||
    let timeSystem;
 | 
			
		||||
    let clockKey;
 | 
			
		||||
    let clock;
 | 
			
		||||
    let bounds;
 | 
			
		||||
    let eventListener;
 | 
			
		||||
    let toi;
 | 
			
		||||
    let openmct;
 | 
			
		||||
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
        api = new TimeAPI(openmct);
 | 
			
		||||
        timeSystemKey = "timeSystemKey";
 | 
			
		||||
        timeSystem = {key: timeSystemKey};
 | 
			
		||||
        clockKey = "someClockKey";
 | 
			
		||||
        clock = jasmine.createSpyObj("clock", [
 | 
			
		||||
            "on",
 | 
			
		||||
            "off",
 | 
			
		||||
            "currentValue"
 | 
			
		||||
        ]);
 | 
			
		||||
        clock.currentValue.and.returnValue(100);
 | 
			
		||||
        clock.key = clockKey;
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
        eventListener = jasmine.createSpy("eventListener");
 | 
			
		||||
        toi = 111;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Supports setting and querying of time of interest", function () {
 | 
			
		||||
        expect(api.timeOfInterest()).not.toBe(toi);
 | 
			
		||||
        api.timeOfInterest(toi);
 | 
			
		||||
        expect(api.timeOfInterest()).toBe(toi);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows setting of valid bounds", function () {
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
        expect(api.bounds()).not.toBe(bounds);
 | 
			
		||||
        expect(api.bounds.bind(api, bounds)).not.toThrow();
 | 
			
		||||
        expect(api.bounds()).toEqual(bounds);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Disallows setting of invalid bounds", function () {
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 1,
 | 
			
		||||
            end: 0
 | 
			
		||||
        };
 | 
			
		||||
        expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
        expect(api.bounds.bind(api, bounds)).toThrow();
 | 
			
		||||
        expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
 | 
			
		||||
        bounds = {start: 1};
 | 
			
		||||
        expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
        expect(api.bounds.bind(api, bounds)).toThrow();
 | 
			
		||||
        expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows setting of previously registered time system with bounds", function () {
 | 
			
		||||
        api.addTimeSystem(timeSystem);
 | 
			
		||||
        expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
        expect(function () {
 | 
			
		||||
            api.timeSystem(timeSystem, bounds);
 | 
			
		||||
        }).not.toThrow();
 | 
			
		||||
        expect(api.timeSystem()).toBe(timeSystem);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Disallows setting of time system without bounds", function () {
 | 
			
		||||
        api.addTimeSystem(timeSystem);
 | 
			
		||||
        expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
        expect(function () {
 | 
			
		||||
            api.timeSystem(timeSystemKey);
 | 
			
		||||
        }).toThrow();
 | 
			
		||||
        expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("allows setting of timesystem without bounds with clock", function () {
 | 
			
		||||
        api.addTimeSystem(timeSystem);
 | 
			
		||||
        api.addClock(clock);
 | 
			
		||||
        api.clock(clockKey, {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        });
 | 
			
		||||
        expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
        expect(function () {
 | 
			
		||||
            api.timeSystem(timeSystemKey);
 | 
			
		||||
        }).not.toThrow();
 | 
			
		||||
        expect(api.timeSystem()).toBe(timeSystem);
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Emits an event when time system changes", function () {
 | 
			
		||||
        api.addTimeSystem(timeSystem);
 | 
			
		||||
        expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
        api.on("timeSystem", eventListener);
 | 
			
		||||
        api.timeSystem(timeSystemKey, bounds);
 | 
			
		||||
        expect(eventListener).toHaveBeenCalledWith(timeSystem);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Emits an event when time of interest changes", function () {
 | 
			
		||||
        expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
        api.on("timeOfInterest", eventListener);
 | 
			
		||||
        api.timeOfInterest(toi);
 | 
			
		||||
        expect(eventListener).toHaveBeenCalledWith(toi);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Emits an event when bounds change", function () {
 | 
			
		||||
        expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
        api.on("bounds", eventListener);
 | 
			
		||||
        api.bounds(bounds);
 | 
			
		||||
        expect(eventListener).toHaveBeenCalledWith(bounds, false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("If bounds are set and TOI lies inside them, do not change TOI", function () {
 | 
			
		||||
        api.timeOfInterest(6);
 | 
			
		||||
        api.bounds({
 | 
			
		||||
            start: 1,
 | 
			
		||||
            end: 10
 | 
			
		||||
        });
 | 
			
		||||
        expect(api.timeOfInterest()).toEqual(6);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("If bounds are set and TOI lies outside them, reset TOI", function () {
 | 
			
		||||
        api.timeOfInterest(11);
 | 
			
		||||
        api.bounds({
 | 
			
		||||
            start: 1,
 | 
			
		||||
            end: 10
 | 
			
		||||
        });
 | 
			
		||||
        expect(api.timeOfInterest()).toBeUndefined();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Maintains delta during tick", function () {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows registered time system to be activated", function () {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows a registered tick source to be activated", function () {
 | 
			
		||||
        const mockTickSource = jasmine.createSpyObj("mockTickSource", [
 | 
			
		||||
            "on",
 | 
			
		||||
            "off",
 | 
			
		||||
            "currentValue"
 | 
			
		||||
        ]);
 | 
			
		||||
        mockTickSource.key = 'mockTickSource';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe(" when enabling a tick source", function () {
 | 
			
		||||
        let mockTickSource;
 | 
			
		||||
        let anotherMockTickSource;
 | 
			
		||||
        const mockOffsets = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            api = new TimeAPI();
 | 
			
		||||
            timeSystemKey = "timeSystemKey";
 | 
			
		||||
            timeSystem = {key: timeSystemKey};
 | 
			
		||||
            clockKey = "someClockKey";
 | 
			
		||||
            clock = jasmine.createSpyObj("clock", [
 | 
			
		||||
            mockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
                "on",
 | 
			
		||||
                "off",
 | 
			
		||||
                "currentValue"
 | 
			
		||||
            ]);
 | 
			
		||||
            clock.currentValue.and.returnValue(100);
 | 
			
		||||
            clock.key = clockKey;
 | 
			
		||||
            bounds = {
 | 
			
		||||
                start: 0,
 | 
			
		||||
                end: 1
 | 
			
		||||
            };
 | 
			
		||||
            eventListener = jasmine.createSpy("eventListener");
 | 
			
		||||
            toi = 111;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Supports setting and querying of time of interest", function () {
 | 
			
		||||
            expect(api.timeOfInterest()).not.toBe(toi);
 | 
			
		||||
            api.timeOfInterest(toi);
 | 
			
		||||
            expect(api.timeOfInterest()).toBe(toi);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows setting of valid bounds", function () {
 | 
			
		||||
            bounds = {
 | 
			
		||||
                start: 0,
 | 
			
		||||
                end: 1
 | 
			
		||||
            };
 | 
			
		||||
            expect(api.bounds()).not.toBe(bounds);
 | 
			
		||||
            expect(api.bounds.bind(api, bounds)).not.toThrow();
 | 
			
		||||
            expect(api.bounds()).toEqual(bounds);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Disallows setting of invalid bounds", function () {
 | 
			
		||||
            bounds = {
 | 
			
		||||
                start: 1,
 | 
			
		||||
                end: 0
 | 
			
		||||
            };
 | 
			
		||||
            expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
            expect(api.bounds.bind(api, bounds)).toThrow();
 | 
			
		||||
            expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
 | 
			
		||||
            bounds = {start: 1};
 | 
			
		||||
            expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
            expect(api.bounds.bind(api, bounds)).toThrow();
 | 
			
		||||
            expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows setting of previously registered time system with bounds", function () {
 | 
			
		||||
            api.addTimeSystem(timeSystem);
 | 
			
		||||
            expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                api.timeSystem(timeSystem, bounds);
 | 
			
		||||
            }).not.toThrow();
 | 
			
		||||
            expect(api.timeSystem()).toBe(timeSystem);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Disallows setting of time system without bounds", function () {
 | 
			
		||||
            api.addTimeSystem(timeSystem);
 | 
			
		||||
            expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                api.timeSystem(timeSystemKey);
 | 
			
		||||
            }).toThrow();
 | 
			
		||||
            expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("allows setting of timesystem without bounds with clock", function () {
 | 
			
		||||
            api.addTimeSystem(timeSystem);
 | 
			
		||||
            api.addClock(clock);
 | 
			
		||||
            api.clock(clockKey, {
 | 
			
		||||
                start: 0,
 | 
			
		||||
                end: 1
 | 
			
		||||
            });
 | 
			
		||||
            expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                api.timeSystem(timeSystemKey);
 | 
			
		||||
            }).not.toThrow();
 | 
			
		||||
            expect(api.timeSystem()).toBe(timeSystem);
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Emits an event when time system changes", function () {
 | 
			
		||||
            api.addTimeSystem(timeSystem);
 | 
			
		||||
            expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
            api.on("timeSystem", eventListener);
 | 
			
		||||
            api.timeSystem(timeSystemKey, bounds);
 | 
			
		||||
            expect(eventListener).toHaveBeenCalledWith(timeSystem);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Emits an event when time of interest changes", function () {
 | 
			
		||||
            expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
            api.on("timeOfInterest", eventListener);
 | 
			
		||||
            api.timeOfInterest(toi);
 | 
			
		||||
            expect(eventListener).toHaveBeenCalledWith(toi);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Emits an event when bounds change", function () {
 | 
			
		||||
            expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
            api.on("bounds", eventListener);
 | 
			
		||||
            api.bounds(bounds);
 | 
			
		||||
            expect(eventListener).toHaveBeenCalledWith(bounds, false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("If bounds are set and TOI lies inside them, do not change TOI", function () {
 | 
			
		||||
            api.timeOfInterest(6);
 | 
			
		||||
            api.bounds({
 | 
			
		||||
                start: 1,
 | 
			
		||||
                end: 10
 | 
			
		||||
            });
 | 
			
		||||
            expect(api.timeOfInterest()).toEqual(6);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("If bounds are set and TOI lies outside them, reset TOI", function () {
 | 
			
		||||
            api.timeOfInterest(11);
 | 
			
		||||
            api.bounds({
 | 
			
		||||
                start: 1,
 | 
			
		||||
                end: 10
 | 
			
		||||
            });
 | 
			
		||||
            expect(api.timeOfInterest()).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Maintains delta during tick", function () {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows registered time system to be activated", function () {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows a registered tick source to be activated", function () {
 | 
			
		||||
            const mockTickSource = jasmine.createSpyObj("mockTickSource", [
 | 
			
		||||
                "on",
 | 
			
		||||
                "off",
 | 
			
		||||
                "currentValue"
 | 
			
		||||
            ]);
 | 
			
		||||
            mockTickSource.key = 'mockTickSource';
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe(" when enabling a tick source", function () {
 | 
			
		||||
            let mockTickSource;
 | 
			
		||||
            let anotherMockTickSource;
 | 
			
		||||
            const mockOffsets = {
 | 
			
		||||
                start: 0,
 | 
			
		||||
                end: 1
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
                    "on",
 | 
			
		||||
                    "off",
 | 
			
		||||
                    "currentValue"
 | 
			
		||||
                ]);
 | 
			
		||||
                mockTickSource.currentValue.and.returnValue(10);
 | 
			
		||||
                mockTickSource.key = "mts";
 | 
			
		||||
 | 
			
		||||
                anotherMockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
                    "on",
 | 
			
		||||
                    "off",
 | 
			
		||||
                    "currentValue"
 | 
			
		||||
                ]);
 | 
			
		||||
                anotherMockTickSource.key = "amts";
 | 
			
		||||
                anotherMockTickSource.currentValue.and.returnValue(10);
 | 
			
		||||
 | 
			
		||||
                api.addClock(mockTickSource);
 | 
			
		||||
                api.addClock(anotherMockTickSource);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("sets bounds based on current value", function () {
 | 
			
		||||
                api.clock("mts", mockOffsets);
 | 
			
		||||
                expect(api.bounds()).toEqual({
 | 
			
		||||
                    start: 10,
 | 
			
		||||
                    end: 11
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("a new tick listener is registered", function () {
 | 
			
		||||
                api.clock("mts", mockOffsets);
 | 
			
		||||
                expect(mockTickSource.on).toHaveBeenCalledWith("tick", jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("listener of existing tick source is reregistered", function () {
 | 
			
		||||
                api.clock("mts", mockOffsets);
 | 
			
		||||
                api.clock("amts", mockOffsets);
 | 
			
		||||
                expect(mockTickSource.off).toHaveBeenCalledWith("tick", jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("Allows the active clock to be set and unset", function () {
 | 
			
		||||
                expect(api.clock()).toBeUndefined();
 | 
			
		||||
                api.clock("mts", mockOffsets);
 | 
			
		||||
                expect(api.clock()).toBeDefined();
 | 
			
		||||
                api.stopClock();
 | 
			
		||||
                expect(api.clock()).toBeUndefined();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("on tick, observes offsets, and indicates tick in bounds callback", function () {
 | 
			
		||||
            const mockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
                "on",
 | 
			
		||||
                "off",
 | 
			
		||||
                "currentValue"
 | 
			
		||||
            ]);
 | 
			
		||||
            mockTickSource.currentValue.and.returnValue(100);
 | 
			
		||||
            let tickCallback;
 | 
			
		||||
            const boundsCallback = jasmine.createSpy("boundsCallback");
 | 
			
		||||
            const clockOffsets = {
 | 
			
		||||
                start: -100,
 | 
			
		||||
                end: 100
 | 
			
		||||
            };
 | 
			
		||||
            mockTickSource.currentValue.and.returnValue(10);
 | 
			
		||||
            mockTickSource.key = "mts";
 | 
			
		||||
 | 
			
		||||
            anotherMockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
                "on",
 | 
			
		||||
                "off",
 | 
			
		||||
                "currentValue"
 | 
			
		||||
            ]);
 | 
			
		||||
            anotherMockTickSource.key = "amts";
 | 
			
		||||
            anotherMockTickSource.currentValue.and.returnValue(10);
 | 
			
		||||
 | 
			
		||||
            api.addClock(mockTickSource);
 | 
			
		||||
            api.clock("mts", clockOffsets);
 | 
			
		||||
 | 
			
		||||
            api.on("bounds", boundsCallback);
 | 
			
		||||
 | 
			
		||||
            tickCallback = mockTickSource.on.calls.mostRecent().args[1];
 | 
			
		||||
            tickCallback(1000);
 | 
			
		||||
            expect(boundsCallback).toHaveBeenCalledWith({
 | 
			
		||||
                start: 900,
 | 
			
		||||
                end: 1100
 | 
			
		||||
            }, true);
 | 
			
		||||
            api.addClock(anotherMockTickSource);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("sets bounds based on current value", function () {
 | 
			
		||||
            api.clock("mts", mockOffsets);
 | 
			
		||||
            expect(api.bounds()).toEqual({
 | 
			
		||||
                start: 10,
 | 
			
		||||
                end: 11
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("a new tick listener is registered", function () {
 | 
			
		||||
            api.clock("mts", mockOffsets);
 | 
			
		||||
            expect(mockTickSource.on).toHaveBeenCalledWith("tick", jasmine.any(Function));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("listener of existing tick source is reregistered", function () {
 | 
			
		||||
            api.clock("mts", mockOffsets);
 | 
			
		||||
            api.clock("amts", mockOffsets);
 | 
			
		||||
            expect(mockTickSource.off).toHaveBeenCalledWith("tick", jasmine.any(Function));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows the active clock to be set and unset", function () {
 | 
			
		||||
            expect(api.clock()).toBeUndefined();
 | 
			
		||||
            api.clock("mts", mockOffsets);
 | 
			
		||||
            expect(api.clock()).toBeDefined();
 | 
			
		||||
            api.stopClock();
 | 
			
		||||
            expect(api.clock()).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("on tick, observes offsets, and indicates tick in bounds callback", function () {
 | 
			
		||||
        const mockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
            "on",
 | 
			
		||||
            "off",
 | 
			
		||||
            "currentValue"
 | 
			
		||||
        ]);
 | 
			
		||||
        mockTickSource.currentValue.and.returnValue(100);
 | 
			
		||||
        let tickCallback;
 | 
			
		||||
        const boundsCallback = jasmine.createSpy("boundsCallback");
 | 
			
		||||
        const clockOffsets = {
 | 
			
		||||
            start: -100,
 | 
			
		||||
            end: 100
 | 
			
		||||
        };
 | 
			
		||||
        mockTickSource.key = "mts";
 | 
			
		||||
 | 
			
		||||
        api.addClock(mockTickSource);
 | 
			
		||||
        api.clock("mts", clockOffsets);
 | 
			
		||||
 | 
			
		||||
        api.on("bounds", boundsCallback);
 | 
			
		||||
 | 
			
		||||
        tickCallback = mockTickSource.on.calls.mostRecent().args[1];
 | 
			
		||||
        tickCallback(1000);
 | 
			
		||||
        expect(boundsCallback).toHaveBeenCalledWith({
 | 
			
		||||
            start: 900,
 | 
			
		||||
            end: 1100
 | 
			
		||||
        }, true);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										360
									
								
								src/api/time/TimeContext.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										360
									
								
								src/api/time/TimeContext.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,360 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 EventEmitter from 'EventEmitter';
 | 
			
		||||
 | 
			
		||||
class TimeContext extends EventEmitter {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        //The Time System
 | 
			
		||||
        this.timeSystems = new Map();
 | 
			
		||||
 | 
			
		||||
        this.system = undefined;
 | 
			
		||||
 | 
			
		||||
        this.clocks = new Map();
 | 
			
		||||
 | 
			
		||||
        this.boundsVal = {
 | 
			
		||||
            start: undefined,
 | 
			
		||||
            end: undefined
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.activeClock = undefined;
 | 
			
		||||
        this.offsets = undefined;
 | 
			
		||||
 | 
			
		||||
        this.tick = this.tick.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the time system of the TimeAPI.
 | 
			
		||||
     * @param {TimeSystem | string} timeSystem
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
 | 
			
		||||
     * @fires module:openmct.TimeAPI~timeSystem
 | 
			
		||||
     * @returns {TimeSystem} The currently applied time system
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method timeSystem
 | 
			
		||||
     */
 | 
			
		||||
    timeSystem(timeSystemOrKey, bounds) {
 | 
			
		||||
        if (arguments.length >= 1) {
 | 
			
		||||
            if (arguments.length === 1 && !this.activeClock) {
 | 
			
		||||
                throw new Error(
 | 
			
		||||
                    "Must specify bounds when changing time system without "
 | 
			
		||||
                    + "an active clock."
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let timeSystem;
 | 
			
		||||
 | 
			
		||||
            if (timeSystemOrKey === undefined) {
 | 
			
		||||
                throw "Please provide a time system";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (typeof timeSystemOrKey === 'string') {
 | 
			
		||||
                timeSystem = this.timeSystems.get(timeSystemOrKey);
 | 
			
		||||
 | 
			
		||||
                if (timeSystem === undefined) {
 | 
			
		||||
                    throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof timeSystemOrKey === 'object') {
 | 
			
		||||
                timeSystem = timeSystemOrKey;
 | 
			
		||||
 | 
			
		||||
                if (!this.timeSystems.has(timeSystem.key)) {
 | 
			
		||||
                    throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.system = timeSystem;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The time system used by the time
 | 
			
		||||
             * conductor has changed. A change in Time System will always be
 | 
			
		||||
             * followed by a bounds event specifying new query bounds.
 | 
			
		||||
             *
 | 
			
		||||
             * @event module:openmct.TimeAPI~timeSystem
 | 
			
		||||
             * @property {TimeSystem} The value of the currently applied
 | 
			
		||||
             * Time System
 | 
			
		||||
             * */
 | 
			
		||||
            this.emit('timeSystem', this.system);
 | 
			
		||||
            if (bounds) {
 | 
			
		||||
                this.bounds(bounds);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.system;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clock offsets are used to calculate temporal bounds when the system is
 | 
			
		||||
     * ticking on a clock source.
 | 
			
		||||
     *
 | 
			
		||||
     * @typedef {object} ValidationResult
 | 
			
		||||
     * @property {boolean} valid Result of the validation - true or false.
 | 
			
		||||
     * @property {string} message An error message if valid is false.
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate the given bounds. This can be used for pre-validation of bounds,
 | 
			
		||||
     * for example by views validating user inputs.
 | 
			
		||||
     * @param {TimeBounds} bounds The start and end time of the conductor.
 | 
			
		||||
     * @returns {ValidationResult} A validation error, or true if valid
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method validateBounds
 | 
			
		||||
     */
 | 
			
		||||
    validateBounds(bounds) {
 | 
			
		||||
        if ((bounds.start === undefined)
 | 
			
		||||
            || (bounds.end === undefined)
 | 
			
		||||
            || isNaN(bounds.start)
 | 
			
		||||
            || isNaN(bounds.end)
 | 
			
		||||
        ) {
 | 
			
		||||
            return {
 | 
			
		||||
                valid: false,
 | 
			
		||||
                message: "Start and end must be specified as integer values"
 | 
			
		||||
            };
 | 
			
		||||
        } else if (bounds.start > bounds.end) {
 | 
			
		||||
            return {
 | 
			
		||||
                valid: false,
 | 
			
		||||
                message: "Specified start date exceeds end bound"
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            valid: true,
 | 
			
		||||
            message: ''
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the start and end time of the time conductor. Basic validation
 | 
			
		||||
     * of bounds is performed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
 | 
			
		||||
     * @throws {Error} Validation error
 | 
			
		||||
     * @fires module:openmct.TimeAPI~bounds
 | 
			
		||||
     * @returns {module:openmct.TimeAPI~TimeConductorBounds}
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method bounds
 | 
			
		||||
     */
 | 
			
		||||
    bounds(newBounds) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            const validationResult = this.validateBounds(newBounds);
 | 
			
		||||
            if (validationResult.valid !== true) {
 | 
			
		||||
                throw new Error(validationResult.message);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //Create a copy to avoid direct mutation of conductor bounds
 | 
			
		||||
            this.boundsVal = JSON.parse(JSON.stringify(newBounds));
 | 
			
		||||
            /**
 | 
			
		||||
             * The start time, end time, or both have been updated.
 | 
			
		||||
             * @event bounds
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {TimeConductorBounds} bounds The newly updated bounds
 | 
			
		||||
             * @property {boolean} [tick] `true` if the bounds update was due to
 | 
			
		||||
             * a "tick" event (ie. was an automatic update), false otherwise.
 | 
			
		||||
             */
 | 
			
		||||
            this.emit('bounds', this.boundsVal, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Return a copy to prevent direct mutation of time conductor bounds.
 | 
			
		||||
        return JSON.parse(JSON.stringify(this.boundsVal));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate the given offsets. This can be used for pre-validation of
 | 
			
		||||
     * offsets, for example by views validating user inputs.
 | 
			
		||||
     * @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
 | 
			
		||||
     * @returns { ValidationResult } A validation error, and true/false if valid or not
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method validateOffsets
 | 
			
		||||
     */
 | 
			
		||||
    validateOffsets(offsets) {
 | 
			
		||||
        if ((offsets.start === undefined)
 | 
			
		||||
            || (offsets.end === undefined)
 | 
			
		||||
            || isNaN(offsets.start)
 | 
			
		||||
            || isNaN(offsets.end)
 | 
			
		||||
        ) {
 | 
			
		||||
            return {
 | 
			
		||||
                valid: false,
 | 
			
		||||
                message: "Start and end offsets must be specified as integer values"
 | 
			
		||||
            };
 | 
			
		||||
        } else if (offsets.start >= offsets.end) {
 | 
			
		||||
            return {
 | 
			
		||||
                valid: false,
 | 
			
		||||
                message: "Specified start offset must be < end offset"
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            valid: true,
 | 
			
		||||
            message: ''
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @typedef {Object} TimeBounds
 | 
			
		||||
     * @property {number} start The start time displayed by the time conductor
 | 
			
		||||
     * in ms since epoch. Epoch determined by currently active time system
 | 
			
		||||
     * @property {number} end The end time displayed by the time conductor in ms
 | 
			
		||||
     * since epoch.
 | 
			
		||||
     * @memberof module:openmct.TimeAPI~
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clock offsets are used to calculate temporal bounds when the system is
 | 
			
		||||
     * ticking on a clock source.
 | 
			
		||||
     *
 | 
			
		||||
     * @typedef {object} ClockOffsets
 | 
			
		||||
     * @property {number} start A time span relative to the current value of the
 | 
			
		||||
     * ticking clock, from which start bounds will be calculated. This value must
 | 
			
		||||
     * be < 0. When a clock is active, bounds will be calculated automatically
 | 
			
		||||
     * based on the value provided by the clock, and the defined clock offsets.
 | 
			
		||||
     * @property {number} end A time span relative to the current value of the
 | 
			
		||||
     * ticking clock, from which end bounds will be calculated. This value must
 | 
			
		||||
     * be >= 0.
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the currently applied clock offsets. If no parameter is provided,
 | 
			
		||||
     * the current value will be returned. If provided, the new value will be
 | 
			
		||||
     * used as the new clock offsets.
 | 
			
		||||
     * @param {ClockOffsets} offsets
 | 
			
		||||
     * @returns {ClockOffsets}
 | 
			
		||||
     */
 | 
			
		||||
    clockOffsets(offsets) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
 | 
			
		||||
            const validationResult = this.validateOffsets(offsets);
 | 
			
		||||
            if (validationResult.valid !== true) {
 | 
			
		||||
                throw new Error(validationResult.message);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.offsets = offsets;
 | 
			
		||||
 | 
			
		||||
            const currentValue = this.activeClock.currentValue();
 | 
			
		||||
            const newBounds = {
 | 
			
		||||
                start: currentValue + offsets.start,
 | 
			
		||||
                end: currentValue + offsets.end
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.bounds(newBounds);
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Event that is triggered when clock offsets change.
 | 
			
		||||
             * @event clockOffsets
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {ClockOffsets} clockOffsets The newly activated clock
 | 
			
		||||
             * offsets.
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clockOffsets", offsets);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.offsets;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stop the currently active clock from ticking, and unset it. This will
 | 
			
		||||
     * revert all views to showing a static time frame defined by the current
 | 
			
		||||
     * bounds.
 | 
			
		||||
     */
 | 
			
		||||
    stopClock() {
 | 
			
		||||
        if (this.activeClock) {
 | 
			
		||||
            this.clock(undefined, undefined);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the active clock. Tick source will be immediately subscribed to
 | 
			
		||||
     * and ticking will begin. Offsets from 'now' must also be provided. A clock
 | 
			
		||||
     * can be unset by calling {@link stopClock}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Clock || string} keyOrClock The clock to activate, or its key
 | 
			
		||||
     * @param {ClockOffsets} offsets on each tick these will be used to calculate
 | 
			
		||||
     * the start and end bounds. This maintains a sliding time window of a fixed
 | 
			
		||||
     * width that automatically updates.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~clock
 | 
			
		||||
     * @return {Clock} the currently active clock;
 | 
			
		||||
     */
 | 
			
		||||
    clock(keyOrClock, offsets) {
 | 
			
		||||
        if (arguments.length === 2) {
 | 
			
		||||
            let clock;
 | 
			
		||||
 | 
			
		||||
            if (typeof keyOrClock === 'string') {
 | 
			
		||||
                clock = this.clocks.get(keyOrClock);
 | 
			
		||||
                if (clock === undefined) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof keyOrClock === 'object') {
 | 
			
		||||
                clock = keyOrClock;
 | 
			
		||||
                if (!this.clocks.has(clock.key)) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const previousClock = this.activeClock;
 | 
			
		||||
            if (previousClock !== undefined) {
 | 
			
		||||
                previousClock.off("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.activeClock = clock;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The active clock has changed. Clock can be unset by calling {@link stopClock}
 | 
			
		||||
             * @event clock
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {Clock} clock The newly activated clock, or undefined
 | 
			
		||||
             * if the system is no longer following a clock source
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clock", this.activeClock);
 | 
			
		||||
 | 
			
		||||
            if (this.activeClock !== undefined) {
 | 
			
		||||
                this.clockOffsets(offsets);
 | 
			
		||||
                this.activeClock.on("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else if (arguments.length === 1) {
 | 
			
		||||
            throw "When setting the clock, clock offsets must also be provided";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.activeClock;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update bounds based on provided time and current offsets
 | 
			
		||||
     * @param {number} timestamp A time from which bounds will be calculated
 | 
			
		||||
     * using current offsets.
 | 
			
		||||
     */
 | 
			
		||||
    tick(timestamp) {
 | 
			
		||||
        if (!this.activeClock) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const newBounds = {
 | 
			
		||||
            start: timestamp + this.offsets.start,
 | 
			
		||||
            end: timestamp + this.offsets.end
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.boundsVal = newBounds;
 | 
			
		||||
        this.emit('bounds', this.boundsVal, true);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default TimeContext;
 | 
			
		||||
							
								
								
									
										155
									
								
								src/api/time/independentTimeAPISpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/api/time/independentTimeAPISpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 TimeAPI from "./TimeAPI";
 | 
			
		||||
import {createOpenMct} from "utils/testing";
 | 
			
		||||
describe("The Independent Time API", function () {
 | 
			
		||||
    let api;
 | 
			
		||||
    let domainObjectKey;
 | 
			
		||||
    let clockKey;
 | 
			
		||||
    let clock;
 | 
			
		||||
    let bounds;
 | 
			
		||||
    let independentBounds;
 | 
			
		||||
    let eventListener;
 | 
			
		||||
    let openmct;
 | 
			
		||||
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
        api = new TimeAPI(openmct);
 | 
			
		||||
        clockKey = "someClockKey";
 | 
			
		||||
        clock = jasmine.createSpyObj("clock", [
 | 
			
		||||
            "on",
 | 
			
		||||
            "off",
 | 
			
		||||
            "currentValue"
 | 
			
		||||
        ]);
 | 
			
		||||
        clock.currentValue.and.returnValue(100);
 | 
			
		||||
        clock.key = clockKey;
 | 
			
		||||
        api.addClock(clock);
 | 
			
		||||
        domainObjectKey = 'test-key';
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
        api.bounds(bounds);
 | 
			
		||||
        independentBounds = {
 | 
			
		||||
            start: 10,
 | 
			
		||||
            end: 11
 | 
			
		||||
        };
 | 
			
		||||
        eventListener = jasmine.createSpy("eventListener");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Creates an independent time context", () => {
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getIndependentContext(domainObjectKey);
 | 
			
		||||
        expect(timeContext.bounds()).toEqual(independentBounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Gets an independent time context given the objectPath", () => {
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{
 | 
			
		||||
            identifier: {
 | 
			
		||||
                namespace: '',
 | 
			
		||||
                key: 'blah'
 | 
			
		||||
            }
 | 
			
		||||
        }, { identifier: domainObjectKey }]);
 | 
			
		||||
        expect(timeContext.bounds()).toEqual(independentBounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("defaults to the global time context given the objectPath", () => {
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{
 | 
			
		||||
            identifier: {
 | 
			
		||||
                namespace: '',
 | 
			
		||||
                key: 'blah'
 | 
			
		||||
            }
 | 
			
		||||
        }]);
 | 
			
		||||
        expect(timeContext.bounds()).toEqual(bounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows setting of valid bounds", function () {
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
 | 
			
		||||
        expect(timeContext.bounds()).not.toEqual(bounds);
 | 
			
		||||
        timeContext.bounds(bounds);
 | 
			
		||||
        expect(timeContext.bounds()).toEqual(bounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Disallows setting of invalid bounds", function () {
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 1,
 | 
			
		||||
            end: 0
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
 | 
			
		||||
        expect(timeContext.bounds()).not.toBe(bounds);
 | 
			
		||||
 | 
			
		||||
        expect(timeContext.bounds.bind(timeContext, bounds)).toThrow();
 | 
			
		||||
        expect(timeContext.bounds()).not.toEqual(bounds);
 | 
			
		||||
 | 
			
		||||
        bounds = {start: 1};
 | 
			
		||||
        expect(timeContext.bounds()).not.toEqual(bounds);
 | 
			
		||||
        expect(timeContext.bounds.bind(timeContext, bounds)).toThrow();
 | 
			
		||||
        expect(timeContext.bounds()).not.toEqual(bounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Emits an event when bounds change", function () {
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
 | 
			
		||||
        expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
        timeContext.on('bounds', eventListener);
 | 
			
		||||
        timeContext.bounds(bounds);
 | 
			
		||||
        expect(eventListener).toHaveBeenCalledWith(bounds, false);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe(" when using real time clock", function () {
 | 
			
		||||
        const mockOffsets = {
 | 
			
		||||
            start: 10,
 | 
			
		||||
            end: 11
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        it("Emits an event when bounds change based on current value", function () {
 | 
			
		||||
            let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
            let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
 | 
			
		||||
            expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
            timeContext.clock('someClockKey', mockOffsets);
 | 
			
		||||
            timeContext.on('bounds', eventListener);
 | 
			
		||||
            timeContext.tick(10);
 | 
			
		||||
            expect(eventListener).toHaveBeenCalledWith({
 | 
			
		||||
                start: 20,
 | 
			
		||||
                end: 21
 | 
			
		||||
            }, true);
 | 
			
		||||
            destroyTimeContext();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										189
									
								
								src/exporters/ImageExporter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								src/exporters/ImageExporter.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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(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);
 | 
			
		||||
            });
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
            dialog.dismiss();
 | 
			
		||||
 | 
			
		||||
            console.error('error capturing image', error);
 | 
			
		||||
            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;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										58
									
								
								src/exporters/ImageExporterSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/exporters/ImageExporterSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import ImageExporter from './ImageExporter';
 | 
			
		||||
import { createOpenMct, resetApplicationState } from '../utils/testing';
 | 
			
		||||
 | 
			
		||||
describe('The Image Exporter', () => {
 | 
			
		||||
    let openmct;
 | 
			
		||||
    let imageExporter;
 | 
			
		||||
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        return resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe("basic instatation", () => {
 | 
			
		||||
        it("can be instatiated", () => {
 | 
			
		||||
            imageExporter = new ImageExporter(openmct);
 | 
			
		||||
 | 
			
		||||
            expect(imageExporter).not.toEqual(null);
 | 
			
		||||
        });
 | 
			
		||||
        it("can render an element to a blob", async () => {
 | 
			
		||||
            const mockHeadElement = document.createElement("h1");
 | 
			
		||||
            const mockTextNode = document.createTextNode('foo bar');
 | 
			
		||||
            mockHeadElement.appendChild(mockTextNode);
 | 
			
		||||
            document.body.appendChild(mockHeadElement);
 | 
			
		||||
            imageExporter = new ImageExporter(openmct);
 | 
			
		||||
            const returnedBlob = await imageExporter.renderElement(document.body, {
 | 
			
		||||
                imageType: 'png'
 | 
			
		||||
            });
 | 
			
		||||
            expect(returnedBlob).not.toEqual(null);
 | 
			
		||||
            expect(returnedBlob.blob).not.toEqual(null);
 | 
			
		||||
            expect(returnedBlob.blob).toBeInstanceOf(Blob);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -38,8 +38,6 @@ const DEFAULTS = [
 | 
			
		||||
    'platform/exporters',
 | 
			
		||||
    'platform/telemetry',
 | 
			
		||||
    'platform/features/clock',
 | 
			
		||||
    'platform/features/hyperlink',
 | 
			
		||||
    'platform/features/timeline',
 | 
			
		||||
    'platform/forms',
 | 
			
		||||
    'platform/identity',
 | 
			
		||||
    'platform/persistence/aggregator',
 | 
			
		||||
@@ -82,9 +80,7 @@ define([
 | 
			
		||||
    '../platform/exporters/bundle',
 | 
			
		||||
    '../platform/features/clock/bundle',
 | 
			
		||||
    '../platform/features/my-items/bundle',
 | 
			
		||||
    '../platform/features/hyperlink/bundle',
 | 
			
		||||
    '../platform/features/static-markup/bundle',
 | 
			
		||||
    '../platform/features/timeline/bundle',
 | 
			
		||||
    '../platform/forms/bundle',
 | 
			
		||||
    '../platform/framework/bundle',
 | 
			
		||||
    '../platform/framework/src/load/Bundle',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								src/plugins/DeviceClassifier/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/plugins/DeviceClassifier/plugin.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 Agent from "../../utils/agent/Agent";
 | 
			
		||||
import DeviceClassifier from "./src/DeviceClassifier";
 | 
			
		||||
 | 
			
		||||
export default () => {
 | 
			
		||||
    return (openmct) => {
 | 
			
		||||
        openmct.on("start", () => {
 | 
			
		||||
            const agent = new Agent(window);
 | 
			
		||||
            DeviceClassifier(agent, window.document);
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										72
									
								
								src/plugins/DeviceClassifier/src/DeviceClassifier.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/plugins/DeviceClassifier/src/DeviceClassifier.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Runs at application startup and adds a subset of the following
 | 
			
		||||
 * CSS classes to the body of the document, depending on device
 | 
			
		||||
 * attributes:
 | 
			
		||||
 *
 | 
			
		||||
 * * `mobile`: Phones or tablets.
 | 
			
		||||
 * * `phone`: Phones specifically.
 | 
			
		||||
 * * `tablet`: Tablets specifically.
 | 
			
		||||
 * * `desktop`: Non-mobile devices.
 | 
			
		||||
 * * `portrait`: Devices in a portrait-style orientation.
 | 
			
		||||
 * * `landscape`: Devices in a landscape-style orientation.
 | 
			
		||||
 * * `touch`: Device supports touch events.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {utils/agent/Agent} agent
 | 
			
		||||
 *        the service used to examine the user agent
 | 
			
		||||
 * @param document the HTML DOM document object
 | 
			
		||||
 * @constructor
 | 
			
		||||
 */
 | 
			
		||||
import DeviceMatchers from "./DeviceMatchers";
 | 
			
		||||
 | 
			
		||||
export default (agent, document) => {
 | 
			
		||||
    const body = document.body;
 | 
			
		||||
 | 
			
		||||
    Object.keys(DeviceMatchers).forEach((key, index, array) => {
 | 
			
		||||
        if (DeviceMatchers[key](agent)) {
 | 
			
		||||
            body.classList.add(key);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (agent.isMobile()) {
 | 
			
		||||
        const mediaQuery = window.matchMedia("(orientation: landscape)");
 | 
			
		||||
        function eventHandler(event) {
 | 
			
		||||
            console.log("changed");
 | 
			
		||||
            if (event.matches) {
 | 
			
		||||
                body.classList.remove("portrait");
 | 
			
		||||
                body.classList.add("landscape");
 | 
			
		||||
            } else {
 | 
			
		||||
                body.classList.remove("landscape");
 | 
			
		||||
                body.classList.add("portrait");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mediaQuery.addEventListener) {
 | 
			
		||||
            mediaQuery.addEventListener(`change`, eventHandler);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Deprecated 'MediaQueryList' API, <Safari 14, IE, <Edge 16
 | 
			
		||||
            mediaQuery.addListener(eventHandler);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										105
									
								
								src/plugins/DeviceClassifier/src/DeviceClassifierSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/plugins/DeviceClassifier/src/DeviceClassifierSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 DeviceClassifier from "./DeviceClassifier";
 | 
			
		||||
import DeviceMatchers from "./DeviceMatchers";
 | 
			
		||||
 | 
			
		||||
const AGENT_METHODS = [
 | 
			
		||||
    "isMobile",
 | 
			
		||||
    "isPhone",
 | 
			
		||||
    "isTablet",
 | 
			
		||||
    "isPortrait",
 | 
			
		||||
    "isLandscape",
 | 
			
		||||
    "isTouch"
 | 
			
		||||
];
 | 
			
		||||
const TEST_PERMUTATIONS = [
 | 
			
		||||
    ["isMobile", "isPhone", "isTouch", "isPortrait"],
 | 
			
		||||
    ["isMobile", "isPhone", "isTouch", "isLandscape"],
 | 
			
		||||
    ["isMobile", "isTablet", "isTouch", "isPortrait"],
 | 
			
		||||
    ["isMobile", "isTablet", "isTouch", "isLandscape"],
 | 
			
		||||
    ["isTouch"],
 | 
			
		||||
    []
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
describe("DeviceClassifier", function () {
 | 
			
		||||
    let mockAgent;
 | 
			
		||||
    let mockDocument;
 | 
			
		||||
    let mockClassList;
 | 
			
		||||
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
        mockAgent = jasmine.createSpyObj(
 | 
			
		||||
            "agent",
 | 
			
		||||
            AGENT_METHODS
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        mockClassList = jasmine.createSpyObj("classList", ["add"]);
 | 
			
		||||
 | 
			
		||||
        mockDocument = jasmine.createSpyObj(
 | 
			
		||||
            "document",
 | 
			
		||||
            {},
 | 
			
		||||
            { body: { classList: mockClassList } }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        AGENT_METHODS.forEach(function (m) {
 | 
			
		||||
            mockAgent[m].and.returnValue(false);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    TEST_PERMUTATIONS.forEach(function (trueMethods) {
 | 
			
		||||
        const summary =
 | 
			
		||||
      trueMethods.length === 0
 | 
			
		||||
          ? "device has no detected characteristics"
 | 
			
		||||
          : "device " + trueMethods.join(", ");
 | 
			
		||||
 | 
			
		||||
        describe("when " + summary, function () {
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                trueMethods.forEach(function (m) {
 | 
			
		||||
                    mockAgent[m].and.returnValue(true);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // eslint-disable-next-line no-new
 | 
			
		||||
                DeviceClassifier(mockAgent, mockDocument);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("adds classes for matching, detected characteristics", function () {
 | 
			
		||||
                Object.keys(DeviceMatchers)
 | 
			
		||||
                    .filter(function (m) {
 | 
			
		||||
                        return DeviceMatchers[m](mockAgent);
 | 
			
		||||
                    })
 | 
			
		||||
                    .forEach(function (key) {
 | 
			
		||||
                        expect(mockDocument.body.classList.add).toHaveBeenCalledWith(key);
 | 
			
		||||
                    });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not add classes for non-matching characteristics", function () {
 | 
			
		||||
                Object.keys(DeviceMatchers)
 | 
			
		||||
                    .filter(function (m) {
 | 
			
		||||
                        return !DeviceMatchers[m](mockAgent);
 | 
			
		||||
                    })
 | 
			
		||||
                    .forEach(function (key) {
 | 
			
		||||
                        expect(mockDocument.body.classList.add).not.toHaveBeenCalledWith(
 | 
			
		||||
                            key
 | 
			
		||||
                        );
 | 
			
		||||
                    });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										57
									
								
								src/plugins/DeviceClassifier/src/DeviceMatchers.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/plugins/DeviceClassifier/src/DeviceMatchers.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An object containing key-value pairs, where keys are symbolic of
 | 
			
		||||
 * device attributes, and values are functions that take the
 | 
			
		||||
 * `agent` as inputs and return boolean values indicating
 | 
			
		||||
 * whether or not the current device has these attributes.
 | 
			
		||||
 *
 | 
			
		||||
 * For internal use by the mobile support bundle.
 | 
			
		||||
 *
 | 
			
		||||
 * @memberof src/plugins/DeviceClassifier
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    mobile: function (agent) {
 | 
			
		||||
        return agent.isMobile();
 | 
			
		||||
    },
 | 
			
		||||
    phone: function (agent) {
 | 
			
		||||
        return agent.isPhone();
 | 
			
		||||
    },
 | 
			
		||||
    tablet: function (agent) {
 | 
			
		||||
        return agent.isTablet();
 | 
			
		||||
    },
 | 
			
		||||
    desktop: function (agent) {
 | 
			
		||||
        return !agent.isMobile();
 | 
			
		||||
    },
 | 
			
		||||
    portrait: function (agent) {
 | 
			
		||||
        return agent.isPortrait();
 | 
			
		||||
    },
 | 
			
		||||
    landscape: function (agent) {
 | 
			
		||||
        return agent.isLandscape();
 | 
			
		||||
    },
 | 
			
		||||
    touch: function (agent) {
 | 
			
		||||
        return agent.isTouch();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										65
									
								
								src/plugins/DeviceClassifier/src/DeviceMatchersSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/plugins/DeviceClassifier/src/DeviceMatchersSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 DeviceMatchers from "./DeviceMatchers";
 | 
			
		||||
 | 
			
		||||
describe("DeviceMatchers", function () {
 | 
			
		||||
    let mockAgent;
 | 
			
		||||
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
        mockAgent = jasmine.createSpyObj("agent", [
 | 
			
		||||
            "isMobile",
 | 
			
		||||
            "isPhone",
 | 
			
		||||
            "isTablet",
 | 
			
		||||
            "isPortrait",
 | 
			
		||||
            "isLandscape",
 | 
			
		||||
            "isTouch"
 | 
			
		||||
        ]);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("detects when a device is a desktop device", function () {
 | 
			
		||||
        mockAgent.isMobile.and.returnValue(false);
 | 
			
		||||
        expect(DeviceMatchers.desktop(mockAgent)).toBe(true);
 | 
			
		||||
        mockAgent.isMobile.and.returnValue(true);
 | 
			
		||||
        expect(DeviceMatchers.desktop(mockAgent)).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function method(deviceType) {
 | 
			
		||||
        return "is" + deviceType[0].toUpperCase() + deviceType.slice(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [
 | 
			
		||||
        "mobile",
 | 
			
		||||
        "phone",
 | 
			
		||||
        "tablet",
 | 
			
		||||
        "landscape",
 | 
			
		||||
        "portrait",
 | 
			
		||||
        "landscape",
 | 
			
		||||
        "touch"
 | 
			
		||||
    ].forEach(function (deviceType) {
 | 
			
		||||
        it("detects when a device is a " + deviceType + " device", function () {
 | 
			
		||||
            mockAgent[method(deviceType)].and.returnValue(true);
 | 
			
		||||
            expect(DeviceMatchers[deviceType](mockAgent)).toBe(true);
 | 
			
		||||
            mockAgent[method(deviceType)].and.returnValue(false);
 | 
			
		||||
            expect(DeviceMatchers[deviceType](mockAgent)).toBe(false);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -19,8 +19,8 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import LadTableSet from './components/LadTableSet.vue';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
import LadTableSetView from './LadTableSetView';
 | 
			
		||||
 | 
			
		||||
export default function LADTableSetViewProvider(openmct) {
 | 
			
		||||
    return {
 | 
			
		||||
@@ -34,32 +34,7 @@ export default function LADTableSetViewProvider(openmct) {
 | 
			
		||||
            return domainObject.type === 'LadTableSet';
 | 
			
		||||
        },
 | 
			
		||||
        view: function (domainObject, objectPath) {
 | 
			
		||||
            let component;
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                show: function (element) {
 | 
			
		||||
                    component = new Vue({
 | 
			
		||||
                        el: element,
 | 
			
		||||
                        components: {
 | 
			
		||||
                            LadTableSet: LadTableSet
 | 
			
		||||
                        },
 | 
			
		||||
                        provide: {
 | 
			
		||||
                            openmct,
 | 
			
		||||
                            objectPath
 | 
			
		||||
                        },
 | 
			
		||||
                        data() {
 | 
			
		||||
                            return {
 | 
			
		||||
                                domainObject
 | 
			
		||||
                            };
 | 
			
		||||
                        },
 | 
			
		||||
                        template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
 | 
			
		||||
                    });
 | 
			
		||||
                },
 | 
			
		||||
                destroy: function (element) {
 | 
			
		||||
                    component.$destroy();
 | 
			
		||||
                    component = undefined;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            return new LadTableSetView(openmct, domainObject, objectPath);
 | 
			
		||||
        },
 | 
			
		||||
        priority: function () {
 | 
			
		||||
            return 1;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								src/plugins/LADTable/LADTableView.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/plugins/LADTable/LADTableView.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import LadTable from './components/LADTable.vue';
 | 
			
		||||
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default class LADTableView {
 | 
			
		||||
    constructor(openmct, domainObject, objectPath) {
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.domainObject = domainObject;
 | 
			
		||||
        this.objectPath = objectPath;
 | 
			
		||||
        this.component = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show(element) {
 | 
			
		||||
        this.component = new Vue({
 | 
			
		||||
            el: element,
 | 
			
		||||
            components: {
 | 
			
		||||
                LadTable
 | 
			
		||||
            },
 | 
			
		||||
            provide: {
 | 
			
		||||
                openmct: this.openmct,
 | 
			
		||||
                currentView: this
 | 
			
		||||
            },
 | 
			
		||||
            data: () => {
 | 
			
		||||
                return {
 | 
			
		||||
                    domainObject: this.domainObject,
 | 
			
		||||
                    objectPath: this.objectPath
 | 
			
		||||
                };
 | 
			
		||||
            },
 | 
			
		||||
            template: '<lad-table ref="ladTable" :domain-object="domainObject" :object-path="objectPath"></lad-table>'
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getViewContext() {
 | 
			
		||||
        if (!this.component) {
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.component.$refs.ladTable.getViewContext();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy(element) {
 | 
			
		||||
        this.component.$destroy();
 | 
			
		||||
        this.component = undefined;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -19,50 +19,30 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import LadTable from './components/LADTable.vue';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default function LADTableViewProvider(openmct) {
 | 
			
		||||
    return {
 | 
			
		||||
        key: 'LadTable',
 | 
			
		||||
        name: 'LAD Table',
 | 
			
		||||
        cssClass: 'icon-tabular-lad',
 | 
			
		||||
        canView: function (domainObject) {
 | 
			
		||||
            return domainObject.type === 'LadTable';
 | 
			
		||||
        },
 | 
			
		||||
        canEdit: function (domainObject) {
 | 
			
		||||
            return domainObject.type === 'LadTable';
 | 
			
		||||
        },
 | 
			
		||||
        view: function (domainObject, objectPath) {
 | 
			
		||||
            let component;
 | 
			
		||||
import LADTableView from './LADTableView';
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                show: function (element) {
 | 
			
		||||
                    component = new Vue({
 | 
			
		||||
                        el: element,
 | 
			
		||||
                        components: {
 | 
			
		||||
                            LadTableComponent: LadTable
 | 
			
		||||
                        },
 | 
			
		||||
                        provide: {
 | 
			
		||||
                            openmct
 | 
			
		||||
                        },
 | 
			
		||||
                        data: () => {
 | 
			
		||||
                            return {
 | 
			
		||||
                                domainObject,
 | 
			
		||||
                                objectPath
 | 
			
		||||
                            };
 | 
			
		||||
                        },
 | 
			
		||||
                        template: '<lad-table-component :domain-object="domainObject" :object-path="objectPath"></lad-table-component>'
 | 
			
		||||
                    });
 | 
			
		||||
                },
 | 
			
		||||
                destroy: function (element) {
 | 
			
		||||
                    component.$destroy();
 | 
			
		||||
                    component = undefined;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        priority: function () {
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
export default class LADTableViewProvider {
 | 
			
		||||
    constructor(openmct) {
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.name = 'LAD Table';
 | 
			
		||||
        this.key = 'LadTable';
 | 
			
		||||
        this.cssClass = 'icon-tabular-lad';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    canView(domainObject) {
 | 
			
		||||
        return domainObject.type === 'LadTable';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    canEdit(domainObject) {
 | 
			
		||||
        return domainObject.type === 'LadTable';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    view(domainObject, objectPath) {
 | 
			
		||||
        return new LADTableView(this.openmct, domainObject, objectPath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    priority(domainObject) {
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								src/plugins/LADTable/LadTableSetView.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/plugins/LADTable/LadTableSetView.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import LadTableSet from './components/LadTableSet.vue';
 | 
			
		||||
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default class LadTableSetView {
 | 
			
		||||
    constructor(openmct, domainObject, objectPath) {
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.domainObject = domainObject;
 | 
			
		||||
        this.objectPath = objectPath;
 | 
			
		||||
        this.component = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show(element) {
 | 
			
		||||
        this.component = new Vue({
 | 
			
		||||
            el: element,
 | 
			
		||||
            components: {
 | 
			
		||||
                LadTableSet
 | 
			
		||||
            },
 | 
			
		||||
            provide: {
 | 
			
		||||
                openmct: this.openmct,
 | 
			
		||||
                objectPath: this.objectPath,
 | 
			
		||||
                currentView: this
 | 
			
		||||
            },
 | 
			
		||||
            data: () => {
 | 
			
		||||
                return {
 | 
			
		||||
                    domainObject: this.domainObject
 | 
			
		||||
                };
 | 
			
		||||
            },
 | 
			
		||||
            template: '<lad-table-set ref="ladTableSet" :domain-object="domainObject"></lad-table-set>'
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getViewContext() {
 | 
			
		||||
        if (!this.component) {
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.component.$refs.ladTableSet.getViewContext();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy(element) {
 | 
			
		||||
        this.component.$destroy();
 | 
			
		||||
        this.component = undefined;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -50,7 +50,7 @@ const CONTEXT_MENU_ACTIONS = [
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    inject: ['openmct', 'currentView'],
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -167,25 +167,23 @@ export default {
 | 
			
		||||
            this.resetValues();
 | 
			
		||||
            this.timestampKey = timeSystem.key;
 | 
			
		||||
        },
 | 
			
		||||
        getView() {
 | 
			
		||||
            return {
 | 
			
		||||
                getViewContext: () => {
 | 
			
		||||
                    return {
 | 
			
		||||
                        viewHistoricalData: true,
 | 
			
		||||
                        viewDatumAction: true,
 | 
			
		||||
                        getDatum: () => {
 | 
			
		||||
                            return this.datum;
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
        updateViewContext() {
 | 
			
		||||
            this.$emit('rowContextClick', {
 | 
			
		||||
                viewHistoricalData: true,
 | 
			
		||||
                viewDatumAction: true,
 | 
			
		||||
                getDatum: () => {
 | 
			
		||||
                    return this.datum;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        showContextMenu(event) {
 | 
			
		||||
            let actionCollection = this.openmct.actions.get(this.objectPath, this.getView());
 | 
			
		||||
            let allActions = actionCollection.getActionsObject();
 | 
			
		||||
            let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
 | 
			
		||||
            this.updateViewContext();
 | 
			
		||||
 | 
			
		||||
            this.openmct.menus.showMenu(event.x, event.y, applicableActions);
 | 
			
		||||
            const actions = CONTEXT_MENU_ACTIONS.map(key => this.openmct.actions.getAction(key));
 | 
			
		||||
            const menuItems = this.openmct.menus.actionsToMenuItems(actions, this.objectPath, this.currentView);
 | 
			
		||||
            if (menuItems.length) {
 | 
			
		||||
                this.openmct.menus.showMenu(event.x, event.y, menuItems);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        resetValues() {
 | 
			
		||||
            this.value = '---';
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@
 | 
			
		||||
                :domain-object="ladRow.domainObject"
 | 
			
		||||
                :path-to-table="objectPath"
 | 
			
		||||
                :has-units="hasUnits"
 | 
			
		||||
                @rowContextClick="updateViewContext"
 | 
			
		||||
            />
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
@@ -51,7 +52,7 @@ export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        LadRow
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    inject: ['openmct', 'currentView'],
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -64,7 +65,8 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            items: []
 | 
			
		||||
            items: [],
 | 
			
		||||
            viewContext: {}
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -114,6 +116,12 @@ export default {
 | 
			
		||||
            let metadataWithUnits = valueMetadatas.filter(metadatum => metadatum.unit);
 | 
			
		||||
 | 
			
		||||
            return metadataWithUnits.length > 0;
 | 
			
		||||
        },
 | 
			
		||||
        updateViewContext(rowContext) {
 | 
			
		||||
            this.viewContext.row = rowContext;
 | 
			
		||||
        },
 | 
			
		||||
        getViewContext() {
 | 
			
		||||
            return this.viewContext;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,7 @@
 | 
			
		||||
                :domain-object="ladRow.domainObject"
 | 
			
		||||
                :path-to-table="ladTable.objectPath"
 | 
			
		||||
                :has-units="hasUnits"
 | 
			
		||||
                @rowContextClick="updateViewContext"
 | 
			
		||||
            />
 | 
			
		||||
        </template>
 | 
			
		||||
    </tbody>
 | 
			
		||||
@@ -61,7 +62,7 @@ export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        LadRow
 | 
			
		||||
    },
 | 
			
		||||
    inject: ['openmct', 'objectPath'],
 | 
			
		||||
    inject: ['openmct', 'objectPath', 'currentView'],
 | 
			
		||||
    props: {
 | 
			
		||||
        domainObject: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
@@ -72,7 +73,8 @@ export default {
 | 
			
		||||
        return {
 | 
			
		||||
            ladTableObjects: [],
 | 
			
		||||
            ladTelemetryObjects: {},
 | 
			
		||||
            compositions: []
 | 
			
		||||
            compositions: [],
 | 
			
		||||
            viewContext: {}
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -166,6 +168,12 @@ export default {
 | 
			
		||||
 | 
			
		||||
                this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        updateViewContext(rowContext) {
 | 
			
		||||
            this.viewContext.row = rowContext;
 | 
			
		||||
        },
 | 
			
		||||
        getViewContext() {
 | 
			
		||||
            return this.viewContext;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ export default class URLTimeSettingsSynchronizer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialize() {
 | 
			
		||||
        this.updateTimeSettings();
 | 
			
		||||
        this.openmct.router.on('change:params', this.updateTimeSettings);
 | 
			
		||||
 | 
			
		||||
        TIME_EVENTS.forEach(event => {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								src/plugins/clearData/ClearDataAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/plugins/clearData/ClearDataAction.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
function inSelectionPath(openmct, domainObject) {
 | 
			
		||||
    const domainObjectIdentifier = domainObject.identifier;
 | 
			
		||||
 | 
			
		||||
    return openmct.selection.get().some(selectionPath => {
 | 
			
		||||
        return selectionPath.some(objectInPath => {
 | 
			
		||||
            const objectInPathIdentifier = objectInPath.context.item.identifier;
 | 
			
		||||
 | 
			
		||||
            return openmct.objects.areIdsEqual(objectInPathIdentifier, domainObjectIdentifier);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class ClearDataAction {
 | 
			
		||||
    constructor(openmct, appliesToObjects) {
 | 
			
		||||
        this.name = 'Clear Data for Object';
 | 
			
		||||
        this.key = 'clear-data-action';
 | 
			
		||||
        this.description = 'Clears current data for object, unsubscribes and resubscribes to data';
 | 
			
		||||
        this.cssClass = 'icon-clear-data';
 | 
			
		||||
 | 
			
		||||
        this._openmct = openmct;
 | 
			
		||||
        this._appliesToObjects = appliesToObjects;
 | 
			
		||||
    }
 | 
			
		||||
    invoke(objectPath) {
 | 
			
		||||
        let domainObject = null;
 | 
			
		||||
        if (objectPath) {
 | 
			
		||||
            domainObject = objectPath[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._openmct.objectViews.emit('clearData', domainObject);
 | 
			
		||||
    }
 | 
			
		||||
    appliesTo(objectPath) {
 | 
			
		||||
        if (!objectPath) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const contextualDomainObject = objectPath[0];
 | 
			
		||||
        // first check to see if this action applies to this sort of object at all
 | 
			
		||||
        const appliesToThisObject = this._appliesToObjects.some(type => {
 | 
			
		||||
            return contextualDomainObject.type === type;
 | 
			
		||||
        });
 | 
			
		||||
        if (!appliesToThisObject) {
 | 
			
		||||
            // we've selected something not applicable
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const objectInSelectionPath = inSelectionPath(this._openmct, contextualDomainObject);
 | 
			
		||||
        if (objectInSelectionPath) {
 | 
			
		||||
            return true;
 | 
			
		||||
        } else {
 | 
			
		||||
            // if this it doesn't match up, check to see if we're in a composition (i.e., layout)
 | 
			
		||||
            const routerPath = this._openmct.router.path[0];
 | 
			
		||||
 | 
			
		||||
            return routerPath.type === 'layout';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    './components/globalClearIndicator.vue',
 | 
			
		||||
    './clearDataAction',
 | 
			
		||||
    './ClearDataAction',
 | 
			
		||||
    'vue'
 | 
			
		||||
], function (
 | 
			
		||||
    GlobaClearIndicator,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										140
									
								
								src/plugins/clearData/test/ClearDataActionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/plugins/clearData/test/ClearDataActionSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 ClearDataActionPlugin from '../plugin.js';
 | 
			
		||||
import ClearDataAction from '../ClearDataAction.js';
 | 
			
		||||
 | 
			
		||||
describe('When the Clear Data Plugin is installed,', () => {
 | 
			
		||||
    const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
 | 
			
		||||
    const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
 | 
			
		||||
    const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
 | 
			
		||||
    const goodMockSelectionPath = [[{
 | 
			
		||||
        context: {
 | 
			
		||||
            item: {
 | 
			
		||||
                identifier: {
 | 
			
		||||
                    key: 'apple',
 | 
			
		||||
                    namespace: ''
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }]];
 | 
			
		||||
 | 
			
		||||
    const openmct = {
 | 
			
		||||
        objectViews: mockObjectViews,
 | 
			
		||||
        indicators: mockIndicatorProvider,
 | 
			
		||||
        actions: mockActionsProvider,
 | 
			
		||||
        install: function (plugin) {
 | 
			
		||||
            plugin(this);
 | 
			
		||||
        },
 | 
			
		||||
        selection: {
 | 
			
		||||
            get: function () {
 | 
			
		||||
                return goodMockSelectionPath;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        objects: {
 | 
			
		||||
            areIdsEqual: function (obj1, obj2) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const mockObjectPath = [
 | 
			
		||||
        {
 | 
			
		||||
            name: 'mockObject1',
 | 
			
		||||
            type: 'apple'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            name: 'mockObject2',
 | 
			
		||||
            type: 'banana'
 | 
			
		||||
        }
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    it('Global Clear Indicator is installed', () => {
 | 
			
		||||
        openmct.install(ClearDataActionPlugin([]));
 | 
			
		||||
 | 
			
		||||
        expect(mockIndicatorProvider.add).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('Clear Data context menu action is installed', () => {
 | 
			
		||||
        openmct.install(ClearDataActionPlugin([]));
 | 
			
		||||
 | 
			
		||||
        expect(mockActionsProvider.register).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('clear data action emits a clearData event when invoked', () => {
 | 
			
		||||
        const action = new ClearDataAction(openmct);
 | 
			
		||||
 | 
			
		||||
        action.invoke(mockObjectPath);
 | 
			
		||||
 | 
			
		||||
        expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('clears data on applicable objects', () => {
 | 
			
		||||
        let action = new ClearDataAction(openmct, ['apple']);
 | 
			
		||||
 | 
			
		||||
        const actionApplies = action.appliesTo(mockObjectPath);
 | 
			
		||||
 | 
			
		||||
        expect(actionApplies).toBe(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('does not clear data on inapplicable objects', () => {
 | 
			
		||||
        let action = new ClearDataAction(openmct, ['pineapple']);
 | 
			
		||||
 | 
			
		||||
        const actionApplies = action.appliesTo(mockObjectPath);
 | 
			
		||||
 | 
			
		||||
        expect(actionApplies).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('does not clear data if not in the selection path and not a layout', () => {
 | 
			
		||||
        openmct.objects = {
 | 
			
		||||
            areIdsEqual: function (obj1, obj2) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        openmct.router = {
 | 
			
		||||
            path: [{type: 'not-a-layout'}]
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let action = new ClearDataAction(openmct, ['apple']);
 | 
			
		||||
 | 
			
		||||
        const actionApplies = action.appliesTo(mockObjectPath);
 | 
			
		||||
 | 
			
		||||
        expect(actionApplies).toBe(false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('does clear data if not in the selection path and is a layout', () => {
 | 
			
		||||
        openmct.objects = {
 | 
			
		||||
            areIdsEqual: function (obj1, obj2) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        openmct.router = {
 | 
			
		||||
            path: [{type: 'layout'}]
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let action = new ClearDataAction(openmct, ['apple']);
 | 
			
		||||
 | 
			
		||||
        const actionApplies = action.appliesTo(mockObjectPath);
 | 
			
		||||
 | 
			
		||||
        expect(actionApplies).toBe(true);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,64 +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 ClearDataActionPlugin from '../plugin.js';
 | 
			
		||||
import ClearDataAction from '../clearDataAction.js';
 | 
			
		||||
 | 
			
		||||
describe('When the Clear Data Plugin is installed,', function () {
 | 
			
		||||
    const mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']);
 | 
			
		||||
    const mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']);
 | 
			
		||||
    const mockActionsProvider = jasmine.createSpyObj('actions', ['register']);
 | 
			
		||||
 | 
			
		||||
    const openmct = {
 | 
			
		||||
        objectViews: mockObjectViews,
 | 
			
		||||
        indicators: mockIndicatorProvider,
 | 
			
		||||
        actions: mockActionsProvider,
 | 
			
		||||
        install: function (plugin) {
 | 
			
		||||
            plugin(this);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const mockObjectPath = [
 | 
			
		||||
        {name: 'mockObject1'},
 | 
			
		||||
        {name: 'mockObject2'}
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    it('Global Clear Indicator is installed', function () {
 | 
			
		||||
        openmct.install(ClearDataActionPlugin([]));
 | 
			
		||||
 | 
			
		||||
        expect(mockIndicatorProvider.add).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('Clear Data context menu action is installed', function () {
 | 
			
		||||
        openmct.install(ClearDataActionPlugin([]));
 | 
			
		||||
 | 
			
		||||
        expect(mockActionsProvider.register).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('clear data action emits a clearData event when invoked', function () {
 | 
			
		||||
        let action = new ClearDataAction(openmct);
 | 
			
		||||
 | 
			
		||||
        action.invoke(mockObjectPath);
 | 
			
		||||
 | 
			
		||||
        expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -19,40 +19,41 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
define(function () {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An object containing key-value pairs, where keys are symbolic of
 | 
			
		||||
     * device attributes, and values are functions that take the
 | 
			
		||||
     * `agentService` as inputs and return boolean values indicating
 | 
			
		||||
     * whether or not the current device has these attributes.
 | 
			
		||||
     *
 | 
			
		||||
     * For internal use by the mobile support bundle.
 | 
			
		||||
     *
 | 
			
		||||
     * @memberof platform/commonUI/mobile
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
import Clock from './components/Clock.vue';
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default function ClockViewProvider(openmct) {
 | 
			
		||||
    return {
 | 
			
		||||
        mobile: function (agentService) {
 | 
			
		||||
            return agentService.isMobile();
 | 
			
		||||
        key: 'clock.view',
 | 
			
		||||
        name: 'Clock',
 | 
			
		||||
        cssClass: 'icon-clock',
 | 
			
		||||
        canView(domainObject) {
 | 
			
		||||
            return domainObject.type === 'clock';
 | 
			
		||||
        },
 | 
			
		||||
        phone: function (agentService) {
 | 
			
		||||
            return agentService.isPhone();
 | 
			
		||||
        },
 | 
			
		||||
        tablet: function (agentService) {
 | 
			
		||||
            return agentService.isTablet();
 | 
			
		||||
        },
 | 
			
		||||
        desktop: function (agentService) {
 | 
			
		||||
            return !agentService.isMobile();
 | 
			
		||||
        },
 | 
			
		||||
        portrait: function (agentService) {
 | 
			
		||||
            return agentService.isPortrait();
 | 
			
		||||
        },
 | 
			
		||||
        landscape: function (agentService) {
 | 
			
		||||
            return agentService.isLandscape();
 | 
			
		||||
        },
 | 
			
		||||
        touch: function (agentService) {
 | 
			
		||||
            return agentService.isTouch();
 | 
			
		||||
 | 
			
		||||
        view: function (domainObject) {
 | 
			
		||||
            let component;
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                show: function (element) {
 | 
			
		||||
                    component = new Vue({
 | 
			
		||||
                        el: element,
 | 
			
		||||
                        components: {
 | 
			
		||||
                            Clock
 | 
			
		||||
                        },
 | 
			
		||||
                        provide: {
 | 
			
		||||
                            openmct,
 | 
			
		||||
                            domainObject
 | 
			
		||||
                        },
 | 
			
		||||
                        template: '<clock />'
 | 
			
		||||
                    });
 | 
			
		||||
                },
 | 
			
		||||
                destroy: function () {
 | 
			
		||||
                    component.$destroy();
 | 
			
		||||
                    component = undefined;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										99
									
								
								src/plugins/clock/components/Clock.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/plugins/clock/components/Clock.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 Administration. All rights reserved.
 | 
			
		||||
 | 
			
		||||
 Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 You may obtain a copy of the License at
 | 
			
		||||
 http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 | 
			
		||||
 Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 License for the specific language governing permissions and limitations
 | 
			
		||||
 under the License.
 | 
			
		||||
 | 
			
		||||
 Open MCT includes source code licensed under additional open source
 | 
			
		||||
 licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div class="l-angular-ov-wrapper">
 | 
			
		||||
    <div class="u-contents">
 | 
			
		||||
        <div class="c-clock l-time-display u-style-receiver js-style-receiver">
 | 
			
		||||
            <div class="c-clock__timezone">
 | 
			
		||||
                {{ timeZoneAbbr }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="c-clock__value">
 | 
			
		||||
                {{ timeTextValue }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="c-clock__ampm">
 | 
			
		||||
                {{ timeAmPm }}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import moment from 'moment';
 | 
			
		||||
import momentTimezone from 'moment-timezone';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['openmct', 'domainObject'],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            lastTimestamp: null
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        configuration() {
 | 
			
		||||
            return this.domainObject.configuration;
 | 
			
		||||
        },
 | 
			
		||||
        baseFormat() {
 | 
			
		||||
            return this.configuration.baseFormat;
 | 
			
		||||
        },
 | 
			
		||||
        use24() {
 | 
			
		||||
            return this.configuration.use24 === 'clock24';
 | 
			
		||||
        },
 | 
			
		||||
        timezone() {
 | 
			
		||||
            return this.configuration.timezone;
 | 
			
		||||
        },
 | 
			
		||||
        timeFormat() {
 | 
			
		||||
            return this.use24 ? this.baseFormat.replace('hh', "HH") : this.baseFormat;
 | 
			
		||||
        },
 | 
			
		||||
        zoneName() {
 | 
			
		||||
            return momentTimezone.tz.names().includes(this.timezone) ? this.timezone : "UTC";
 | 
			
		||||
        },
 | 
			
		||||
        momentTime() {
 | 
			
		||||
            return this.zoneName ? moment.utc(this.lastTimestamp).tz(this.zoneName) : moment.utc(this.lastTimestamp);
 | 
			
		||||
        },
 | 
			
		||||
        timeZoneAbbr() {
 | 
			
		||||
            return this.momentTime.zoneAbbr();
 | 
			
		||||
        },
 | 
			
		||||
        timeTextValue() {
 | 
			
		||||
            return this.timeFormat && this.momentTime.format(this.timeFormat);
 | 
			
		||||
        },
 | 
			
		||||
        timeAmPm() {
 | 
			
		||||
            return this.use24 ? '' : this.momentTime.format("A");
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        const TickerService = this.openmct.$injector.get('tickerService');
 | 
			
		||||
        this.unlisten = TickerService.listen(this.tick);
 | 
			
		||||
    },
 | 
			
		||||
    beforeDestroy() {
 | 
			
		||||
        if (this.unlisten) {
 | 
			
		||||
            this.unlisten();
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        tick(timestamp) {
 | 
			
		||||
            this.lastTimestamp = timestamp;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -19,10 +19,46 @@
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<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>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div class="c-indicator t-indicator-clock icon-clock no-minify c-indicator--not-clickable">
 | 
			
		||||
    <span class="label c-indicator__label">
 | 
			
		||||
        {{ timeTextValue }}
 | 
			
		||||
    </span>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import moment from 'moment';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    inject: ['openmct'],
 | 
			
		||||
    props: {
 | 
			
		||||
        indicatorFormat: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            required: true
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            timeTextValue: null
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.openmct.on('start', () => {
 | 
			
		||||
            const TickerService = this.openmct.$injector.get('tickerService');
 | 
			
		||||
            this.unlisten = TickerService.listen(this.tick);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    beforeDestroy() {
 | 
			
		||||
        if (this.unlisten) {
 | 
			
		||||
            this.unlisten();
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        tick(timestamp) {
 | 
			
		||||
            this.timeTextValue = `${moment.utc(timestamp).format(this.indicatorFormat)} UTC`;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user