Compare commits
	
		
			60 Commits
		
	
	
		
			mmgis-cust
			...
			view-large
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 40e05e636b | ||
|   | d7c9c9cb98 | ||
|   | 63fb10a9a9 | ||
|   | 2131ef2397 | ||
|   | 48c22369a1 | ||
|   | 6506077f4d | ||
|   | 6af5801b1a | ||
|   | ffe9f04ac6 | ||
|   | df28fe47cc | ||
|   | b2dd51b30d | ||
|   | 248e8c9caf | ||
|   | 32cd981e74 | ||
|   | 9e9e4bff67 | ||
|   | 34597c2a5d | ||
|   | b7f8060523 | ||
|   | 20b6fb3014 | ||
|   | 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 | 
| @@ -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. | ||||
							
								
								
									
										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. | ||||
|  | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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: { | ||||
|   | ||||
							
								
								
									
										21
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,10 +1,8 @@ | ||||
| { | ||||
|   "name": "openmct", | ||||
|   "version": "1.7.4", | ||||
|   "version": "1.7.8-SNAPSHOT", | ||||
|   "description": "The Open MCT core platform", | ||||
|   "dependencies": { | ||||
|     "openmct-mmgis": "git+https://trunk.arc.nasa.gov/bitbucket/scm/vipergds/openmct-mmgis.git#api-mmgis-inspector" | ||||
|   }, | ||||
|   "dependencies": {}, | ||||
|   "devDependencies": { | ||||
|     "angular": ">=1.8.0", | ||||
|     "angular-route": "1.4.14", | ||||
| @@ -39,17 +37,18 @@ | ||||
|     "html2canvas": "^1.0.0-rc.7", | ||||
|     "imports-loader": "^0.8.0", | ||||
|     "istanbul-instrumenter-loader": "^3.0.1", | ||||
|     "jasmine-core": "^3.1.0", | ||||
|     "jasmine-core": "^3.7.1", | ||||
|     "jsdoc": "^3.3.2", | ||||
|     "karma": "5.1.1", | ||||
|     "karma": "6.3.4", | ||||
|     "karma-chrome-launcher": "3.1.0", | ||||
|     "karma-firefox-launcher": "2.1.1", | ||||
|     "karma-cli": "2.0.0", | ||||
|     "karma-coverage": "2.0.3", | ||||
|     "karma-coverage-istanbul-reporter": "3.0.3", | ||||
|     "karma-firefox-launcher": "1.3.0", | ||||
|     "karma-junit-reporter": "2.0.1", | ||||
|     "karma-html-reporter": "0.2.7", | ||||
|     "karma-jasmine": "3.3.1", | ||||
|     "karma-sourcemap-loader": "0.3.7", | ||||
|     "karma-jasmine": "4.0.1", | ||||
|     "karma-sourcemap-loader": "0.3.8", | ||||
|     "karma-webpack": "4.0.2", | ||||
|     "location-bar": "^3.0.1", | ||||
|     "lodash": "^4.17.12", | ||||
| @@ -91,6 +90,7 @@ | ||||
|     "test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run", | ||||
|     "test:debug": "cross-env NODE_ENV=debug karma start --no-single-run", | ||||
|     "test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run", | ||||
|     "test:coverage:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless", | ||||
|     "test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run", | ||||
|     "verify": "concurrently 'npm:test' 'npm:lint'", | ||||
|     "jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api", | ||||
| @@ -102,6 +102,9 @@ | ||||
|     "type": "git", | ||||
|     "url": "https://github.com/nasa/openmct.git" | ||||
|   }, | ||||
|   "engines": { | ||||
|     "node": ">=10.10.2 <16.0.0" | ||||
|   }, | ||||
|   "author": "", | ||||
|   "license": "Apache-2.0", | ||||
|   "private": true | ||||
|   | ||||
| @@ -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,58 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2021, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| 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 | ||||
|      */ | ||||
|     return { | ||||
|         mobile: function (agentService) { | ||||
|             return agentService.isMobile(); | ||||
|         }, | ||||
|         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(); | ||||
|         } | ||||
|     }; | ||||
| }); | ||||
| @@ -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", | ||||
|   | ||||
| @@ -181,7 +181,7 @@ define([ | ||||
|                         ], | ||||
|                         "category": "contextual", | ||||
|                         "name": "Stop", | ||||
|                         "cssClass": "icon-box", | ||||
|                         "cssClass": "icon-box-round-corners", | ||||
|                         "priority": "preferred" | ||||
|                     } | ||||
|                 ], | ||||
|   | ||||
| @@ -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,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,28 +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. | ||||
| --> | ||||
| <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> | ||||
| @@ -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]; | ||||
|   | ||||
| @@ -122,6 +122,7 @@ define([ | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         this.destroy = this.destroy.bind(this); | ||||
|         /** | ||||
|          * Tracks current selection state of the application. | ||||
|          * @private | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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
											
										
									
								
							
							
								
								
									
										388
									
								
								src/api/telemetry/TelemetryCollection.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										388
									
								
								src/api/telemetry/TelemetryCollection.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,388 @@ | ||||
| /***************************************************************************** | ||||
|  * 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; | ||||
|  | ||||
|         try { | ||||
|             this.requestAbort = new AbortController(); | ||||
|             this.options.signal = this.requestAbort.signal; | ||||
|             historicalData = await this.historicalProvider.request(this.domainObject, this.options); | ||||
|             this.requestAbort = undefined; | ||||
|         } catch (error) { | ||||
|             console.error('Error requesting telemetry data...'); | ||||
|             this.requestAbort = undefined; | ||||
|             this._error(error); | ||||
|         } | ||||
|  | ||||
|         this._processNewTelemetry(historicalData); | ||||
|  | ||||
|     } | ||||
|     /** | ||||
|      * This uses the built in subscription function from Telemetry API | ||||
|      * @private | ||||
|      */ | ||||
|     _initiateSubscriptionTelemetry() { | ||||
|  | ||||
|         if (this.unsubscribe) { | ||||
|             this.unsubscribe(); | ||||
|         } | ||||
|  | ||||
|         this.unsubscribe = this.openmct.telemetry | ||||
|             .subscribe( | ||||
|                 this.domainObject, | ||||
|                 datum => this._processNewTelemetry(datum), | ||||
|                 this.options | ||||
|             ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Filter any new telemetry (add/page, historical, subscription) based on | ||||
|      * time bounds and dupes | ||||
|      * | ||||
|      * @param  {(Object|Object[])} telemetryData - telemetry data object or | ||||
|      * array of telemetry data objects | ||||
|      * @private | ||||
|      */ | ||||
|     _processNewTelemetry(telemetryData) { | ||||
|         let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData]; | ||||
|         let parsedValue; | ||||
|         let beforeStartOfBounds; | ||||
|         let afterEndOfBounds; | ||||
|         let added = []; | ||||
|  | ||||
|         for (let datum of data) { | ||||
|             parsedValue = this.parseTime(datum); | ||||
|             beforeStartOfBounds = parsedValue < this.lastBounds.start; | ||||
|             afterEndOfBounds = parsedValue > this.lastBounds.end; | ||||
|  | ||||
|             if (!afterEndOfBounds && !beforeStartOfBounds) { | ||||
|                 let isDuplicate = false; | ||||
|                 let startIndex = this._sortedIndex(datum); | ||||
|                 let endIndex = undefined; | ||||
|  | ||||
|                 // dupe check | ||||
|                 if (startIndex !== this.boundedTelemetry.length) { | ||||
|                     endIndex = _.sortedLastIndexBy( | ||||
|                         this.boundedTelemetry, | ||||
|                         datum, | ||||
|                         boundedDatum => this.parseTime(boundedDatum) | ||||
|                     ); | ||||
|  | ||||
|                     if (endIndex > startIndex) { | ||||
|                         let potentialDupes = this.boundedTelemetry.slice(startIndex, endIndex); | ||||
|  | ||||
|                         isDuplicate = potentialDupes.some(_.isEqual.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._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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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', | ||||
|   | ||||
| @@ -19,25 +19,14 @@ | ||||
|  * 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"; | ||||
| 
 | ||||
| <template> | ||||
| <li class="c-inspect-properties__row"> | ||||
|     <div class="c-inspect-properties__label"> | ||||
|         {{ detail.name }} | ||||
|     </div> | ||||
|     <div class="c-inspect-properties__value"> | ||||
|         {{ detail.value }} | ||||
|     </div> | ||||
| </li> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
|     props: { | ||||
|         detail: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         } | ||||
|     } | ||||
| export default () => { | ||||
|     return (openmct) => { | ||||
|         openmct.on("start", () => { | ||||
|             const agent = new Agent(window); | ||||
|             DeviceClassifier(agent, window.document); | ||||
|         }); | ||||
|     }; | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										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 | ||||
|                         ); | ||||
|                     }); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -20,22 +20,38 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| 
 | ||||
| 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'; | ||||
| /** | ||||
|  * 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 | ||||
|  */ | ||||
| 
 | ||||
|         this._openmct = openmct; | ||||
|         this._appliesToObjects = appliesToObjects; | ||||
| 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(); | ||||
|     } | ||||
|     invoke(objectPath) { | ||||
|         this._openmct.objectViews.emit('clearData', objectPath[0]); | ||||
|     } | ||||
|     appliesTo(objectPath) { | ||||
|         let contextualDomainObject = objectPath[0]; | ||||
| 
 | ||||
|         return this._appliesToObjects.filter(type => contextualDomainObject.type === type).length; | ||||
|     } | ||||
| } | ||||
| }; | ||||
							
								
								
									
										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]); | ||||
|     }); | ||||
| }); | ||||
| @@ -41,7 +41,7 @@ export default class ConditionManager extends EventEmitter { | ||||
|         this.subscriptions = {}; | ||||
|         this.telemetryObjects = {}; | ||||
|         this.testData = { | ||||
|             conditionTestData: [], | ||||
|             conditionTestInputs: this.conditionSetDomainObject.configuration.conditionTestData, | ||||
|             applied: false | ||||
|         }; | ||||
|         this.initialize(); | ||||
| @@ -154,8 +154,10 @@ export default class ConditionManager extends EventEmitter { | ||||
|  | ||||
|     updateConditionDescription(condition) { | ||||
|         const found = this.conditionSetDomainObject.configuration.conditionCollection.find(conditionConfiguration => (conditionConfiguration.id === condition.id)); | ||||
|         found.summary = condition.description; | ||||
|         this.persistConditions(); | ||||
|         if (found.summary !== condition.description) { | ||||
|             found.summary = condition.description; | ||||
|             this.persistConditions(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     initCondition(conditionConfiguration, index) { | ||||
| @@ -414,8 +416,10 @@ export default class ConditionManager extends EventEmitter { | ||||
|     } | ||||
|  | ||||
|     updateTestData(testData) { | ||||
|         this.testData = testData; | ||||
|         this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs); | ||||
|         if (!_.isEqual(testData, this.testData)) { | ||||
|             this.testData = testData; | ||||
|             this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     persistConditions() { | ||||
|   | ||||
| @@ -215,7 +215,8 @@ export default { | ||||
|         }, | ||||
|         isEditing: { | ||||
|             type: Boolean, | ||||
|             required: true | ||||
|             required: true, | ||||
|             default: false | ||||
|         }, | ||||
|         telemetry: { | ||||
|             type: Array, | ||||
|   | ||||
| @@ -273,10 +273,7 @@ export default { | ||||
|             this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then( | ||||
|                 (objectPath) => { | ||||
|                     this.objectPath = objectPath; | ||||
|                     this.navigateToPath = '#/browse/' + this.objectPath | ||||
|                         .map(o => o && this.openmct.objects.makeKeyString(o.identifier)) | ||||
|                         .reverse() | ||||
|                         .join('/'); | ||||
|                     this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath); | ||||
|                 } | ||||
|             ); | ||||
|         }, | ||||
|   | ||||
| @@ -141,6 +141,7 @@ const NON_STYLEABLE_CONTAINER_TYPES = [ | ||||
| const NON_STYLEABLE_LAYOUT_ITEM_TYPES = [ | ||||
|     'line-view', | ||||
|     'box-view', | ||||
|     'ellipse-view', | ||||
|     'image-view' | ||||
| ]; | ||||
|  | ||||
| @@ -296,10 +297,7 @@ export default { | ||||
|             this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then( | ||||
|                 (objectPath) => { | ||||
|                     this.objectPath = objectPath; | ||||
|                     this.navigateToPath = '#/browse/' + this.objectPath | ||||
|                         .map(o => o && this.openmct.objects.makeKeyString(o.identifier)) | ||||
|                         .reverse() | ||||
|                         .join('/'); | ||||
|                     this.navigateToPath = '#/browse/' + this.openmct.objects.getRelativePath(this.objectPath); | ||||
|                 } | ||||
|             ); | ||||
|         }, | ||||
| @@ -321,7 +319,7 @@ export default { | ||||
|             if (item) { | ||||
|                 const type = this.openmct.types.get(item.type); | ||||
|                 if (type && type.definition) { | ||||
|                     creatable = (type.definition.creatable === true); | ||||
|                     creatable = (type.definition.creatable !== undefined && (type.definition.creatable === 'true' || type.definition.creatable === true)); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -230,7 +230,7 @@ describe('the plugin', function () { | ||||
|         }; | ||||
|         const staticStyle = { | ||||
|             "style": { | ||||
|                 "backgroundColor": "#717171", | ||||
|                 "backgroundColor": "#666666", | ||||
|                 "border": "1px solid #00ffff" | ||||
|             } | ||||
|         }; | ||||
| @@ -238,7 +238,7 @@ describe('the plugin', function () { | ||||
|             "conditionId": "39584410-cbf9-499e-96dc-76f27e69885d", | ||||
|             "style": { | ||||
|                 "isStyleInvisible": "", | ||||
|                 "backgroundColor": "#717171", | ||||
|                 "backgroundColor": "#666666", | ||||
|                 "border": "1px solid #ffff00" | ||||
|             } | ||||
|         }; | ||||
| @@ -250,7 +250,7 @@ describe('the plugin', function () { | ||||
|                 "configuration": { | ||||
|                     "items": [ | ||||
|                         { | ||||
|                             "fill": "#717171", | ||||
|                             "fill": "#666666", | ||||
|                             "stroke": "", | ||||
|                             "x": 1, | ||||
|                             "y": 1, | ||||
| @@ -259,12 +259,22 @@ describe('the plugin', function () { | ||||
|                             "type": "box-view", | ||||
|                             "id": "89b88746-d325-487b-aec4-11b79afff9e8" | ||||
|                         }, | ||||
|                         { | ||||
|                             "fill": "#666666", | ||||
|                             "stroke": "", | ||||
|                             "x": 1, | ||||
|                             "y": 1, | ||||
|                             "width": 10, | ||||
|                             "height": 5, | ||||
|                             "type": "ellipse-view", | ||||
|                             "id": "19b88746-d325-487b-aec4-11b79afff9z8" | ||||
|                         }, | ||||
|                         { | ||||
|                             "x": 18, | ||||
|                             "y": 9, | ||||
|                             "x2": 23, | ||||
|                             "y2": 4, | ||||
|                             "stroke": "#717171", | ||||
|                             "stroke": "#666666", | ||||
|                             "type": "line-view", | ||||
|                             "id": "57d49a28-7863-43bd-9593-6570758916f0" | ||||
|                         }, | ||||
| @@ -299,12 +309,12 @@ describe('the plugin', function () { | ||||
|                 "y": 9, | ||||
|                 "x2": 23, | ||||
|                 "y2": 4, | ||||
|                 "stroke": "#717171", | ||||
|                 "stroke": "#666666", | ||||
|                 "type": "line-view", | ||||
|                 "id": "57d49a28-7863-43bd-9593-6570758916f0" | ||||
|             }; | ||||
|             boxLayoutItem = { | ||||
|                 "fill": "#717171", | ||||
|                 "fill": "#666666", | ||||
|                 "stroke": "", | ||||
|                 "x": 1, | ||||
|                 "y": 1, | ||||
|   | ||||
| @@ -29,9 +29,10 @@ const styleProps = { | ||||
|         noneValue: NONE_VALUE, | ||||
|         applicableForType: type => { | ||||
|             return !type ? true : (type === 'text-view' | ||||
|                                       || type === 'telemetry-view' | ||||
|                                       || type === 'box-view' | ||||
|                                       || type === 'subobject-view'); | ||||
|                                             || type === 'telemetry-view' | ||||
|                                             || type === 'box-view' | ||||
|                                             || type === 'ellipse-view' | ||||
|                                             || type === 'subobject-view'); | ||||
|         } | ||||
|     }, | ||||
|     border: { | ||||
| @@ -41,6 +42,7 @@ const styleProps = { | ||||
|             return !type ? true : (type === 'text-view' | ||||
|                                             || type === 'telemetry-view' | ||||
|                                             || type === 'box-view' | ||||
|                                             || type === 'ellipse-view' | ||||
|                                             || type === 'image-view' | ||||
|                                             || type === 'line-view' | ||||
|                                             || type === 'subobject-view'); | ||||
|   | ||||
| @@ -20,71 +20,78 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './components/AlphanumericFormatView.vue', | ||||
|     'vue' | ||||
| ], function (AlphanumericFormatView, Vue) { | ||||
| import AlphanumericFormat from './components/AlphanumericFormat.vue'; | ||||
|  | ||||
|     function AlphanumericFormatViewProvider(openmct, options) { | ||||
|         function isTelemetryObject(selectionPath) { | ||||
|             let selectedObject = selectionPath[0].context.item; | ||||
|             let parentObject = selectionPath[1].context.item; | ||||
|             let selectedLayoutItem = selectionPath[0].context.layoutItem; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
|             return parentObject | ||||
|                 && parentObject.type === 'layout' | ||||
|                 && selectedObject | ||||
|                 && selectedLayoutItem | ||||
|                 && selectedLayoutItem.type === 'telemetry-view' | ||||
|                 && openmct.telemetry.isTelemetryObject(selectedObject) | ||||
|                 && !options.showAsView.includes(selectedObject.type); | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             key: 'alphanumeric-format', | ||||
|             name: 'Alphanumeric Format', | ||||
|             canView: function (selection) { | ||||
|                 if (selection.length === 0 || selection[0].length === 1) { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 return selection.every(isTelemetryObject); | ||||
|             }, | ||||
|             view: function (domainObject, objectPath) { | ||||
|                 let component; | ||||
|  | ||||
|                 return { | ||||
|                     show: function (element) { | ||||
|                         component = new Vue({ | ||||
|                             el: element, | ||||
|                             components: { | ||||
|                                 AlphanumericFormatView: AlphanumericFormatView.default | ||||
|                             }, | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 objectPath | ||||
|                             }, | ||||
|                             template: '<alphanumeric-format-view ref="alphanumericFormatView"></alphanumeric-format-view>' | ||||
|                         }); | ||||
|                     }, | ||||
|                     getViewContext() { | ||||
|                         if (component) { | ||||
|                             return component.$refs.alphanumericFormatView.getViewContext(); | ||||
|                         } else { | ||||
|                             return {}; | ||||
|                         } | ||||
|                     }, | ||||
|                     destroy: function () { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                     } | ||||
|                 }; | ||||
|             }, | ||||
|             priority: function () { | ||||
|                 return 1; | ||||
|             } | ||||
|         }; | ||||
| class AlphanumericFormatView { | ||||
|     constructor(openmct, domainObject, objectPath) { | ||||
|         this.openmct = openmct; | ||||
|         this.domainObject = domainObject; | ||||
|         this.objectPath = objectPath; | ||||
|         this.component = undefined; | ||||
|     } | ||||
|  | ||||
|     return AlphanumericFormatViewProvider; | ||||
| }); | ||||
|     show(element) { | ||||
|         this.component = new Vue({ | ||||
|             el: element, | ||||
|             name: 'AlphanumericFormat', | ||||
|             components: { | ||||
|                 AlphanumericFormat | ||||
|             }, | ||||
|             provide: { | ||||
|                 openmct: this.openmct, | ||||
|                 objectPath: this.objectPath, | ||||
|                 currentView: this | ||||
|             }, | ||||
|             template: '<alphanumeric-format ref="alphanumericFormat"></alphanumeric-format>' | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     getViewContext() { | ||||
|         if (this.component) { | ||||
|             return {}; | ||||
|         } | ||||
|  | ||||
|         return this.component.$refs.alphanumericFormat.getViewContext(); | ||||
|     } | ||||
|  | ||||
|     destroy() { | ||||
|         this.component.$destroy(); | ||||
|         this.component = undefined; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default function AlphanumericFormatViewProvider(openmct, options) { | ||||
|     function isTelemetryObject(selectionPath) { | ||||
|         let selectedObject = selectionPath[0].context.item; | ||||
|         let parentObject = selectionPath[1].context.item; | ||||
|         let selectedLayoutItem = selectionPath[0].context.layoutItem; | ||||
|  | ||||
|         return parentObject | ||||
|             && parentObject.type === 'layout' | ||||
|             && selectedObject | ||||
|             && selectedLayoutItem | ||||
|             && selectedLayoutItem.type === 'telemetry-view' | ||||
|             && openmct.telemetry.isTelemetryObject(selectedObject) | ||||
|             && !options.showAsView.includes(selectedObject.type); | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         key: 'alphanumeric-format', | ||||
|         name: 'Alphanumeric Format', | ||||
|         canView: function (selection) { | ||||
|             if (selection.length === 0 || selection[0].length === 1) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             return selection.every(isTelemetryObject); | ||||
|         }, | ||||
|         view: function (domainObject, objectPath) { | ||||
|             return new AlphanumericFormatView(openmct, domainObject, objectPath); | ||||
|         }, | ||||
|         priority: function () { | ||||
|             return 1; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -149,6 +149,7 @@ define(['lodash'], function (_) { | ||||
|                         return type === 'text-view' | ||||
|                             || type === 'telemetry-view' | ||||
|                             || type === 'box-view' | ||||
|                             || type === 'ellipse-view' | ||||
|                             || type === 'image-view' | ||||
|                             || type === 'line-view' | ||||
|                             || type === 'subobject-view'; | ||||
| @@ -180,6 +181,10 @@ define(['lodash'], function (_) { | ||||
|                                     "name": "Box", | ||||
|                                     "class": "icon-box-round-corners" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Ellipse", | ||||
|                                     "class": "icon-circle" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Line", | ||||
|                                     "class": "icon-line-horz" | ||||
| @@ -745,7 +750,7 @@ define(['lodash'], function (_) { | ||||
|                         if (toolbar.remove.length === 0) { | ||||
|                             toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)]; | ||||
|                         } | ||||
|                     } else if (layoutItem.type === 'box-view') { | ||||
|                     } else if (layoutItem.type === 'box-view' || layoutItem.type === 'ellipse-view') { | ||||
|                         if (toolbar.position.length === 0) { | ||||
|                             toolbar.position = [ | ||||
|                                 getStackOrder(selectedParent, selectionPath), | ||||
|   | ||||
| @@ -14,7 +14,7 @@ export default class CopyToClipboardAction { | ||||
|  | ||||
|     invoke(objectPath, view = {}) { | ||||
|         const viewContext = view.getViewContext && view.getViewContext(); | ||||
|         const formattedValue = viewContext.formattedValueForCopy(); | ||||
|         const formattedValue = viewContext.row.formattedValueForCopy(); | ||||
|  | ||||
|         clipboard.updateClipboard(formattedValue) | ||||
|             .then(() => { | ||||
| @@ -26,9 +26,13 @@ export default class CopyToClipboardAction { | ||||
|     } | ||||
|  | ||||
|     appliesTo(objectPath, view = {}) { | ||||
|         let viewContext = view.getViewContext && view.getViewContext(); | ||||
|         const viewContext = view.getViewContext && view.getViewContext(); | ||||
|         const row = viewContext && viewContext.row; | ||||
|         if (!row) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return viewContext && viewContext.formattedValueForCopy | ||||
|             && typeof viewContext.formattedValueForCopy === 'function'; | ||||
|         return row.formattedValueForCopy | ||||
|             && typeof row.formattedValueForCopy === 'function'; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -52,7 +52,8 @@ | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     name: 'AlphanumericFormat', | ||||
|     inject: ['openmct', 'objectPath'], | ||||
|     data() { | ||||
|         return { | ||||
|             isEditing: this.openmct.editor.isEditing(), | ||||
| @@ -43,7 +43,7 @@ import conditionalStylesMixin from '../mixins/objectStyles-mixin'; | ||||
| export default { | ||||
|     makeDefinition() { | ||||
|         return { | ||||
|             fill: '#717171', | ||||
|             fill: '#666666', | ||||
|             stroke: '', | ||||
|             x: 1, | ||||
|             y: 1, | ||||
|   | ||||
| @@ -56,6 +56,7 @@ | ||||
|         :index="index" | ||||
|         :multi-select="selectedLayoutItems.length > 1" | ||||
|         :is-editing="isEditing" | ||||
|         @contextClick="updateViewContext" | ||||
|         @move="move" | ||||
|         @endMove="endMove" | ||||
|         @endLineResize="endLineResize" | ||||
| @@ -75,6 +76,7 @@ import uuid from 'uuid'; | ||||
| import SubobjectView from './SubobjectView.vue'; | ||||
| import TelemetryView from './TelemetryView.vue'; | ||||
| import BoxView from './BoxView.vue'; | ||||
| import EllipseView from './EllipseView.vue'; | ||||
| import TextView from './TextView.vue'; | ||||
| import LineView from './LineView.vue'; | ||||
| import ImageView from './ImageView.vue'; | ||||
| @@ -111,6 +113,7 @@ const ITEM_TYPE_VIEW_MAP = { | ||||
|     'subobject-view': SubobjectView, | ||||
|     'telemetry-view': TelemetryView, | ||||
|     'box-view': BoxView, | ||||
|     'ellipse-view': EllipseView, | ||||
|     'line-view': LineView, | ||||
|     'text-view': TextView, | ||||
|     'image-view': ImageView | ||||
| @@ -140,7 +143,7 @@ function getItemDefinition(itemType, ...options) { | ||||
|  | ||||
| export default { | ||||
|     components: components, | ||||
|     inject: ['openmct', 'options', 'objectPath'], | ||||
|     inject: ['openmct', 'objectPath', 'options', 'objectUtils', 'currentView'], | ||||
|     props: { | ||||
|         domainObject: { | ||||
|             type: Object, | ||||
| @@ -155,7 +158,8 @@ export default { | ||||
|         return { | ||||
|             initSelectIndex: undefined, | ||||
|             selection: [], | ||||
|             showGrid: true | ||||
|             showGrid: true, | ||||
|             viewContext: {} | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -819,6 +823,12 @@ export default { | ||||
|         }, | ||||
|         toggleGrid() { | ||||
|             this.showGrid = !this.showGrid; | ||||
|         }, | ||||
|         updateViewContext(viewContext) { | ||||
|             this.viewContext.row = viewContext; | ||||
|         }, | ||||
|         getViewContext() { | ||||
|             return this.viewContext; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -28,19 +28,19 @@ | ||||
| > | ||||
|     <div | ||||
|         class="c-frame-edit__handle c-frame-edit__handle--nw" | ||||
|         @mousedown="startResize([1,1], [-1,-1], $event)" | ||||
|         @mousedown.left="startResize([1,1], [-1,-1], $event)" | ||||
|     ></div> | ||||
|     <div | ||||
|         class="c-frame-edit__handle c-frame-edit__handle--ne" | ||||
|         @mousedown="startResize([0,1], [1,-1], $event)" | ||||
|         @mousedown.left="startResize([0,1], [1,-1], $event)" | ||||
|     ></div> | ||||
|     <div | ||||
|         class="c-frame-edit__handle c-frame-edit__handle--sw" | ||||
|         @mousedown="startResize([1,0], [-1,1], $event)" | ||||
|         @mousedown.left="startResize([1,0], [-1,1], $event)" | ||||
|     ></div> | ||||
|     <div | ||||
|         class="c-frame-edit__handle c-frame-edit__handle--se" | ||||
|         @mousedown="startResize([0,0], [1,1], $event)" | ||||
|         @mousedown.left="startResize([0,0], [1,1], $event)" | ||||
|     ></div> | ||||
| </div> | ||||
| </template> | ||||
|   | ||||
							
								
								
									
										122
									
								
								src/plugins/displayLayout/components/EllipseView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/plugins/displayLayout/components/EllipseView.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| /***************************************************************************** | ||||
| * 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> | ||||
| <layout-frame | ||||
|     :item="item" | ||||
|     :grid-size="gridSize" | ||||
|     :is-editing="isEditing" | ||||
|     @move="(gridDelta) => $emit('move', gridDelta)" | ||||
|     @endMove="() => $emit('endMove')" | ||||
| > | ||||
|     <div | ||||
|         class="c-ellipse-view u-style-receiver js-style-receiver" | ||||
|         :class="[styleClass]" | ||||
|         :style="style" | ||||
|     ></div> | ||||
| </layout-frame> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import LayoutFrame from './LayoutFrame.vue'; | ||||
| import conditionalStylesMixin from '../mixins/objectStyles-mixin'; | ||||
|  | ||||
| export default { | ||||
|     makeDefinition() { | ||||
|         return { | ||||
|             fill: '#666666', | ||||
|             stroke: '', | ||||
|             x: 1, | ||||
|             y: 1, | ||||
|             width: 10, | ||||
|             height: 10 | ||||
|         }; | ||||
|     }, | ||||
|     components: { | ||||
|         LayoutFrame | ||||
|     }, | ||||
|     mixins: [conditionalStylesMixin], | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         item: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         }, | ||||
|         gridSize: { | ||||
|             type: Array, | ||||
|             required: true, | ||||
|             validator: (arr) => arr && arr.length === 2 | ||||
|                     && arr.every(el => typeof el === 'number') | ||||
|         }, | ||||
|         index: { | ||||
|             type: Number, | ||||
|             required: true | ||||
|         }, | ||||
|         initSelect: Boolean, | ||||
|         isEditing: { | ||||
|             type: Boolean, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         style() { | ||||
|             if (this.itemStyle) { | ||||
|                 return this.itemStyle; | ||||
|             } else { | ||||
|                 return { | ||||
|                     backgroundColor: this.item.fill, | ||||
|                     border: this.item.stroke ? '1px solid ' + this.item.stroke : '' | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         index(newIndex) { | ||||
|             if (!this.context) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.context.index = newIndex; | ||||
|         }, | ||||
|         item(newItem) { | ||||
|             if (!this.context) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.context.layoutItem = newItem; | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.context = { | ||||
|             layoutItem: this.item, | ||||
|             index: this.index | ||||
|         }; | ||||
|         this.removeSelectable = this.openmct.selection.selectable( | ||||
|             this.$el, this.context, this.initSelect); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         if (this.removeSelectable) { | ||||
|             this.removeSelectable(); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
| @@ -33,7 +33,7 @@ | ||||
|     <slot></slot> | ||||
|     <div | ||||
|         class="c-frame__move-bar" | ||||
|         @mousedown="isEditing ? startMove([1,1], [0,0], $event) : null" | ||||
|         @mousedown.left="startMove($event)" | ||||
|     ></div> | ||||
| </div> | ||||
| </template> | ||||
| @@ -93,7 +93,11 @@ export default { | ||||
|                 return value - this.initialPosition[index]; | ||||
|             }.bind(this)); | ||||
|         }, | ||||
|         startMove(posFactor, dimFactor, event) { | ||||
|         startMove(event, posFactor = [1, 1], dimFactor = [0, 0]) { | ||||
|             if (!this.isEditing) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             document.body.addEventListener('mousemove', this.continueMove); | ||||
|             document.body.addEventListener('mouseup', this.endMove); | ||||
|             this.dragPosition = { | ||||
|   | ||||
| @@ -45,7 +45,7 @@ | ||||
|  | ||||
|     <div | ||||
|         class="c-frame__move-bar" | ||||
|         @mousedown="startDrag($event)" | ||||
|         @mousedown.left="startDrag($event)" | ||||
|     ></div> | ||||
|     <div | ||||
|         v-if="showFrameEdit" | ||||
| @@ -96,7 +96,7 @@ export default { | ||||
|             y: 10, | ||||
|             x2: 10, | ||||
|             y2: 5, | ||||
|             stroke: '#717171' | ||||
|             stroke: '#666666' | ||||
|         }; | ||||
|     }, | ||||
|     mixins: [conditionalStylesMixin], | ||||
|   | ||||
| @@ -72,7 +72,7 @@ | ||||
| <script> | ||||
| import LayoutFrame from './LayoutFrame.vue'; | ||||
| import conditionalStylesMixin from "../mixins/objectStyles-mixin"; | ||||
| import { getDefaultNotebook } from '@/plugins/notebook/utils/notebook-storage.js'; | ||||
| import { getDefaultNotebook, getNotebookSectionAndPage } from '@/plugins/notebook/utils/notebook-storage.js'; | ||||
|  | ||||
| const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5]; | ||||
| const DEFAULT_POSITION = [1, 1]; | ||||
| @@ -102,7 +102,7 @@ export default { | ||||
|         LayoutFrame | ||||
|     }, | ||||
|     mixins: [conditionalStylesMixin], | ||||
|     inject: ['openmct', 'objectPath'], | ||||
|     inject: ['openmct', 'objectPath', 'currentView'], | ||||
|     props: { | ||||
|         item: { | ||||
|             type: Object, | ||||
| @@ -294,16 +294,6 @@ export default { | ||||
|                 this.requestHistoricalData(this.domainObject); | ||||
|             } | ||||
|         }, | ||||
|         getView() { | ||||
|             return { | ||||
|                 getViewContext: () => { | ||||
|                     return { | ||||
|                         viewHistoricalData: true, | ||||
|                         formattedValueForCopy: this.formattedValueForCopy | ||||
|                     }; | ||||
|                 } | ||||
|             }; | ||||
|         }, | ||||
|         setObject(domainObject) { | ||||
|             this.domainObject = domainObject; | ||||
|             this.mutablePromise = undefined; | ||||
| @@ -338,30 +328,41 @@ export default { | ||||
|  | ||||
|             this.$emit('formatChanged', this.item, format); | ||||
|         }, | ||||
|         updateViewContext() { | ||||
|             this.$emit('contextClick', { | ||||
|                 viewHistoricalData: true, | ||||
|                 formattedValueForCopy: this.formattedValueForCopy | ||||
|             }); | ||||
|         }, | ||||
|         async getContextMenuActions() { | ||||
|             const defaultNotebook = getDefaultNotebook(); | ||||
|             const domainObject = defaultNotebook && await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier); | ||||
|             const actionCollection = this.openmct.actions.get(this.currentObjectPath, this.getView()); | ||||
|             const actionsObject = actionCollection.getActionsObject(); | ||||
|  | ||||
|             let copyToNotebookAction = actionsObject.copyToNotebook; | ||||
|  | ||||
|             let defaultNotebookName; | ||||
|             if (defaultNotebook) { | ||||
|                 const defaultPath = domainObject && `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`; | ||||
|                 copyToNotebookAction.name = `Copy to Notebook ${defaultPath}`; | ||||
|             } else { | ||||
|                 actionsObject.copyToNotebook = undefined; | ||||
|                 delete actionsObject.copyToNotebook; | ||||
|                 const domainObject = await this.openmct.objects.get(defaultNotebook.identifier); | ||||
|                 const { section, page } = getNotebookSectionAndPage(domainObject, defaultNotebook.defaultSectionId, defaultNotebook.defaultPageId); | ||||
|                 if (section && page) { | ||||
|                     const defaultPath = domainObject && `${domainObject.name} - ${section.name} - ${page.name}`; | ||||
|                     defaultNotebookName = `Copy to Notebook ${defaultPath}`; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return CONTEXT_MENU_ACTIONS.map(actionKey => { | ||||
|                 return actionsObject[actionKey]; | ||||
|             }).filter(action => action !== undefined); | ||||
|             return CONTEXT_MENU_ACTIONS | ||||
|                 .map(actionKey => { | ||||
|                     const action = this.openmct.actions.getAction(actionKey); | ||||
|                     if (action.key === 'copyToNotebook') { | ||||
|                         action.name = defaultNotebookName; | ||||
|                     } | ||||
|  | ||||
|                     return action; | ||||
|                 }) | ||||
|                 .filter(action => action.name !== undefined); | ||||
|         }, | ||||
|         async showContextMenu(event) { | ||||
|             this.updateViewContext(); | ||||
|             const contextMenuActions = await this.getContextMenuActions(); | ||||
|  | ||||
|             this.openmct.menus.showMenu(event.x, event.y, contextMenuActions); | ||||
|             const menuItems = this.openmct.menus.actionsToMenuItems(contextMenuActions, this.currentObjectPath, this.currentView); | ||||
|             this.openmct.menus.showMenu(event.x, event.y, menuItems); | ||||
|         }, | ||||
|         setStatus(status) { | ||||
|             this.status = status; | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| .c-box-view { | ||||
| .c-box-view, | ||||
| .c-ellipse-view { | ||||
|     border-width: $drawingObjBorderW !important; | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
| @@ -8,6 +9,10 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-ellipse-view { | ||||
|     border-radius: 50%; | ||||
| } | ||||
|  | ||||
| .c-line-view { | ||||
|     &.c-frame { | ||||
|         box-shadow: none !important; | ||||
|   | ||||
| @@ -20,13 +20,81 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| import Layout from './components/DisplayLayout.vue'; | ||||
| import Vue from 'vue'; | ||||
| import objectUtils from 'objectUtils'; | ||||
| import DisplayLayoutType from './DisplayLayoutType.js'; | ||||
| import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'; | ||||
| import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js'; | ||||
| import CopyToClipboardAction from './actions/CopyToClipboardAction'; | ||||
| import DisplayLayout from './components/DisplayLayout.vue'; | ||||
| import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'; | ||||
| import DisplayLayoutType from './DisplayLayoutType.js'; | ||||
|  | ||||
| import objectUtils from 'objectUtils'; | ||||
|  | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| class DisplayLayoutView { | ||||
|     constructor(openmct, domainObject, objectPath, options) { | ||||
|         this.openmct = openmct; | ||||
|         this.domainObject = domainObject; | ||||
|         this.objectPath = objectPath; | ||||
|         this.options = options; | ||||
|  | ||||
|         this.component = undefined; | ||||
|     } | ||||
|  | ||||
|     show(container, isEditing) { | ||||
|         this.component = new Vue({ | ||||
|             el: container, | ||||
|             components: { | ||||
|                 DisplayLayout | ||||
|             }, | ||||
|             provide: { | ||||
|                 openmct: this.openmct, | ||||
|                 objectPath: this.objectPath, | ||||
|                 options: this.options, | ||||
|                 objectUtils, | ||||
|                 currentView: this | ||||
|             }, | ||||
|             data: () => { | ||||
|                 return { | ||||
|                     domainObject: this.domainObject, | ||||
|                     isEditing | ||||
|                 }; | ||||
|             }, | ||||
|             template: '<display-layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></display-layout>' | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     getViewContext() { | ||||
|         if (!this.component) { | ||||
|             return {}; | ||||
|         } | ||||
|  | ||||
|         return this.component.$refs.displayLayout.getViewContext(); | ||||
|     } | ||||
|  | ||||
|     getSelectionContext() { | ||||
|         return { | ||||
|             item: this.domainObject, | ||||
|             supportsMultiSelect: true, | ||||
|             addElement: this.component && this.component.$refs.displayLayout.addElement, | ||||
|             removeItem: this.component && this.component.$refs.displayLayout.removeItem, | ||||
|             orderItem: this.component && this.component.$refs.displayLayout.orderItem, | ||||
|             duplicateItem: this.component && this.component.$refs.displayLayout.duplicateItem, | ||||
|             switchViewType: this.component && this.component.$refs.displayLayout.switchViewType, | ||||
|             mergeMultipleTelemetryViews: this.component && this.component.$refs.displayLayout.mergeMultipleTelemetryViews, | ||||
|             mergeMultipleOverlayPlots: this.component && this.component.$refs.displayLayout.mergeMultipleOverlayPlots, | ||||
|             toggleGrid: this.component && this.component.$refs.displayLayout.toggleGrid | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     onEditModeChange(isEditing) { | ||||
|         this.component.isEditing = isEditing; | ||||
|     } | ||||
|  | ||||
|     destroy() { | ||||
|         this.component.$destroy(); | ||||
|         this.component = undefined; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default function DisplayLayoutPlugin(options) { | ||||
|     return function (openmct) { | ||||
| @@ -41,51 +109,7 @@ export default function DisplayLayoutPlugin(options) { | ||||
|                 return domainObject.type === 'layout'; | ||||
|             }, | ||||
|             view: function (domainObject, objectPath) { | ||||
|                 let component; | ||||
|  | ||||
|                 return { | ||||
|                     show(container) { | ||||
|                         component = new Vue({ | ||||
|                             el: container, | ||||
|                             components: { | ||||
|                                 Layout | ||||
|                             }, | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 objectUtils, | ||||
|                                 options, | ||||
|                                 objectPath | ||||
|                             }, | ||||
|                             data() { | ||||
|                                 return { | ||||
|                                     domainObject: domainObject, | ||||
|                                     isEditing: openmct.editor.isEditing() | ||||
|                                 }; | ||||
|                             }, | ||||
|                             template: '<layout ref="displayLayout" :domain-object="domainObject" :is-editing="isEditing"></layout>' | ||||
|                         }); | ||||
|                     }, | ||||
|                     getSelectionContext() { | ||||
|                         return { | ||||
|                             item: domainObject, | ||||
|                             supportsMultiSelect: true, | ||||
|                             addElement: component && component.$refs.displayLayout.addElement, | ||||
|                             removeItem: component && component.$refs.displayLayout.removeItem, | ||||
|                             orderItem: component && component.$refs.displayLayout.orderItem, | ||||
|                             duplicateItem: component && component.$refs.displayLayout.duplicateItem, | ||||
|                             switchViewType: component && component.$refs.displayLayout.switchViewType, | ||||
|                             mergeMultipleTelemetryViews: component && component.$refs.displayLayout.mergeMultipleTelemetryViews, | ||||
|                             mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots, | ||||
|                             toggleGrid: component && component.$refs.displayLayout.toggleGrid | ||||
|                         }; | ||||
|                     }, | ||||
|                     onEditModeChange: function (isEditing) { | ||||
|                         component.isEditing = isEditing; | ||||
|                     }, | ||||
|                     destroy() { | ||||
|                         component.$destroy(); | ||||
|                     } | ||||
|                 }; | ||||
|                 return new DisplayLayoutView(openmct, domainObject, objectPath, options); | ||||
|             }, | ||||
|             priority() { | ||||
|                 return 100; | ||||
|   | ||||
| @@ -186,7 +186,7 @@ describe('the plugin', function () { | ||||
|                 'configuration': { | ||||
|                     'items': [ | ||||
|                         { | ||||
|                             'fill': '#717171', | ||||
|                             'fill': '#666666', | ||||
|                             'stroke': '', | ||||
|                             'x': 1, | ||||
|                             'y': 1, | ||||
| @@ -195,12 +195,22 @@ describe('the plugin', function () { | ||||
|                             'type': 'box-view', | ||||
|                             'id': '89b88746-d325-487b-aec4-11b79afff9e8' | ||||
|                         }, | ||||
|                         { | ||||
|                             'fill': '#666666', | ||||
|                             'stroke': '', | ||||
|                             'x': 1, | ||||
|                             'y': 1, | ||||
|                             'width': 10, | ||||
|                             'height': 10, | ||||
|                             'type': 'ellipse-view', | ||||
|                             'id': '19b88746-d325-487b-aec4-11b79afff9z8' | ||||
|                         }, | ||||
|                         { | ||||
|                             'x': 18, | ||||
|                             'y': 9, | ||||
|                             'x2': 23, | ||||
|                             'y2': 4, | ||||
|                             'stroke': '#717171', | ||||
|                             'stroke': '#666666', | ||||
|                             'type': 'line-view', | ||||
|                             'id': '57d49a28-7863-43bd-9593-6570758916f0' | ||||
|                         }, | ||||
| @@ -341,7 +351,7 @@ describe('the plugin', function () { | ||||
|         it('provides controls including separators', () => { | ||||
|             const displayLayoutToolbar = openmct.toolbars.get(selection); | ||||
|  | ||||
|             expect(displayLayoutToolbar.length).toBe(9); | ||||
|             expect(displayLayoutToolbar.length).toBe(7); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user