Compare commits

..

5 Commits

Author SHA1 Message Date
Shefali Joshi
484226b229 Fix version number (#4986) 2022-03-23 17:51:30 +00:00
Shefali Joshi
abfa971dd6 Update time conductor inputs realtime (#4877)
* Update time conductor inputs realtime
2022-03-21 14:26:12 -07:00
Jamie V
6d50dd571a Link action fix (#4945)
* handling edge case for linking a root item

* added location to viper plans (couch search folder) set to ROOT, added a check to remove action for alias (so you can remove linked nonpersistable items)

* added check for no parent in remove action (which means it is a root item)

* updating test
2022-03-17 19:17:50 +01:00
Shefali Joshi
b58e38ee15 Fix display layout items getting cut off on the bottom (like plots) (#4903)
* Fix display layout items getting cut off on the bottom (like plots)
Also fix Vue warnings

* Add partial e2e test for this bug fix. WIP.

* Address review comments

Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-03-15 14:33:32 -05:00
Shefali Joshi
d2923a672c Correctly use creatable attribute and persistability when working with domainObjects (#4898) (#4936)
* making move action location check persistability

* adding persistence check instead of creatability for styles

* added check for link action to make sure parent is persistable

* debug

* adding parent to link action and move action form location controls so they can be used in the form

* adding parent persistability check for duplicate

* updating multilple actions appliesTo methods to check for persistability

* updated the tree to not require an initial selection if being used in a form

* remove noneditable folder plugin

* added persistence check for the parent, in the create wizard

* minor name change

* removing noneditabl folder from default plugins as well

* checking the correct parent for persistability in create wizard

* importing file-saver correctly

* updated tests for import as json

* changes addressing PR review: using consts, removing comments, removing unneccessary code

Co-authored-by: Scott Bell <scott@traclabs.com>

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Scott Bell <scott@traclabs.com>
2022-03-14 13:34:52 -07:00
157 changed files with 1914 additions and 4353 deletions

View File

@@ -2,7 +2,7 @@ version: 2.1
executors:
pw-focal-development:
docker:
- image: mcr.microsoft.com/playwright:v1.19.2-focal
- image: mcr.microsoft.com/playwright:v1.19.1-focal
environment:
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
parameters:
@@ -101,7 +101,7 @@ jobs:
equal: [ "FirefoxESR", <<parameters.browser>> ]
steps:
- browser-tools/install-firefox:
version: "91.7.1esr" #https://archive.mozilla.org/pub/firefox/releases/
version: "91.4.0esr" #https://archive.mozilla.org/pub/firefox/releases/
- when:
condition:
equal: [ "FirefoxHeadless", <<parameters.browser>> ]
@@ -113,7 +113,7 @@ jobs:
steps:
- browser-tools/install-chrome:
replace-existing: false
- run: npm run test -- --browsers=<<parameters.browser>>
- run: npm run test:coverage -- --browsers=<<parameters.browser>>
- save_cache_cmd:
node-version: <<parameters.node-version>>
- store_test_results:
@@ -142,8 +142,11 @@ workflows:
overall-circleci-commit-status: #These jobs run on every commit
jobs:
- lint:
name: node16-lint
node-version: lts/gallium
- unit-test:
name: node12-chrome
node-version: lts/erbium
browser: ChromeHeadless
- unit-test:
name: node14-chrome
node-version: lts/fermium
@@ -161,9 +164,13 @@ workflows:
the-nightly: #These jobs do not run on PRs, but against master at night
jobs:
- unit-test:
name: node16-firefoxESR-nightly
node-version: lts/gallium
name: node12-firefoxESR-nightly
node-version: lts/erbium
browser: FirefoxESR
- unit-test:
name: node12-chrome-nightly
node-version: lts/erbium
browser: ChromeHeadless
- unit-test:
name: node14-firefox-nightly
node-version: lts/fermium

View File

@@ -11,14 +11,12 @@ module.exports = {
},
"extends": [
"eslint:recommended",
"plugin:compat/recommended",
"plugin:vue/recommended",
"plugin:you-dont-need-lodash-underscore/compatible"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@babel/eslint-parser",
"requireConfigFile": false,
"parser": "babel-eslint",
"allowImportExportEverywhere": true,
"ecmaVersion": 2015,
"ecmaFeatures": {
@@ -37,6 +35,7 @@ module.exports = {
"no-inner-declarations": "off",
"no-use-before-define": ["error", "nofunc"],
"no-caller": "error",
"no-sequences": "error",
"no-irregular-whitespace": "error",
"no-new": "error",
"no-shadow": "error",
@@ -240,12 +239,13 @@ module.exports = {
],
"vue/max-attributes-per-line": ["error", {
"singleline": 1,
"multiline": 1,
"multiline": {
"max": 1,
"allowFirstLine": true
}
}],
"vue/first-attribute-linebreak": "error",
"vue/multiline-html-element-content-newline": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/multi-word-component-names": "off", // TODO enable, align with conventions
"vue/no-mutating-props": "off"
},

View File

@@ -8,7 +8,7 @@ on:
jobs:
e2e-full:
if: ${{ github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'pr:e2e' }} || ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }}
if: ${{ github.event.label.name == 'pr:e2e' }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
@@ -30,18 +30,8 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: '16'
check-latest: true
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
- run: npx playwright install-deps
- run: npm install
- run: npx playwright install
- run: npm run test:e2e:full
- name: Archive test results
uses: actions/upload-artifact@v2

View File

@@ -10,25 +10,15 @@ on:
jobs:
e2e-visual:
if: ${{ github.event.label.name == 'pr:visual' }} || ${{ github.event.workflow_dispatch }} || ${{ github.event.schedule }} || ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }}
if: ${{ github.event.label.name == 'pr:visual' }} || ${{ github.event.workflow_dispatch }} || ${{ github.event.schedule }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
check-latest: true
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
- run: npx playwright install-deps
- run: npm install
- run: npx playwright install
- name: Run the e2e visual tests
run: npm run test:e2e:visual
env:

21
.github/workflows/e2e.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: "e2e"
on:
workflow_dispatch:
inputs:
version:
description: 'Which branch do you want to test?' # Limited to branch for now
required: false
default: 'master'
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.version }}
- uses: actions/setup-node@v3
with:
node-version: '16'
- run: npm install
- name: Run the e2e tests
run: npm run test:e2e:ci

View File

@@ -6,6 +6,11 @@ on:
description: 'Which branch do you want to test?' # Limited to branch for now
required: false
default: 'master'
pull_request:
types:
- labeled
schedule:
- cron: '28 21 * * 1-5'
jobs:
lighthouse-pr:
if: ${{ github.event.label.name == 'pr:lighthouse' }}
@@ -15,10 +20,10 @@ jobs:
uses: actions/checkout@v3
with:
ref: master #explicitly checkout master for baseline
- name: Install Node 16
- name: Install Node 14
uses: actions/setup-node@v3
with:
node-version: '16'
node-version: '14'
- name: Cache node modules
uses: actions/cache@v2
env:
@@ -49,11 +54,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Node 16
- name: Install Node 14
uses: actions/setup-node@v3
with:
node-version: '16'
check-latest: true
node-version: '14'
- name: Cache node modules
uses: actions/cache@v2
env:
@@ -79,10 +83,9 @@ jobs:
- name: Install Node 14
uses: actions/setup-node@v3
with:
node-version: '16'
check-latest: true
node-version: '14'
- name: Cache node modules
uses: actions/cache@v3
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:

View File

@@ -27,7 +27,6 @@ jobs:
with:
node-version: 16
registry-url: https://registry.npmjs.org/
check-latest: true
- run: npm install
- run: npm publish --access public --tag unstable
env:

View File

@@ -12,14 +12,13 @@ jobs:
fail-fast: false
matrix:
os:
- ubuntu-20.04 #MMOC Ubuntu version
- ubuntu-latest
- macos-latest
- macos-10.15
- windows-latest
node_version:
- 12
- 14
- 16
- 17
architecture:
- x64
name: Node ${{ matrix.node_version }} - ${{ matrix.architecture }} on ${{ matrix.os }}
@@ -30,16 +29,6 @@ jobs:
with:
node-version: ${{ matrix.node_version }}
architecture: ${{ matrix.architecture }}
check-latest: true
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-${{ matrix.node_version }}--build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-${{ matrix.node_version }}--build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
- run: npm install
- run: npm test
- run: npm run lint -- --quiet

View File

@@ -1,27 +1,44 @@
# Ignore everything first (will not ignore special files like LICENSE.md,
# README.md, and package.json)...
/**/*
*.scssc
*.zip
*.gzip
*.tgz
*.DS_Store
# ...but include these folders...
!/dist/**/*
!/src/**/*
*.sass-cache
*COMPILE.css
# We might be able to remove this if it is not imported by any project directly.
# https://github.com/nasa/openmct/issues/4992
!/example/**/*
# Intellij project configuration files
*.idea
*.iml
# We will remove this in https://github.com/nasa/openmct/issues/4922
!/app.js
# External dependencies
# ...except for these files in the above folders.
/src/**/*Spec.js
/src/**/test/
# TODO move test utils into test/ folders
/src/utils/testing.js
# Build output
target
# Also include these special top-level files.
!copyright-notice.js
!copyright-notice.html
!index.html
!openmct.js
!SECURITY.md
# Mac OS X Finder
.DS_Store
# Closed source libraries
closed-lib
# Node, Bower dependencies
node_modules
bower_components
Procfile
# Protractor logs
protractor/logs
# npm-debug log
npm-debug.log
# Infra and tests
.circleci
.github
e2e
codecov.yml
lighthouserc.yml
*.Spec.js
karma.conf.js

5
.npmrc
View File

@@ -1,4 +1,9 @@
loglevel=warn
# Temporary: istanbul-instrumenter-loader is working with webpack 5, but states
# webpack 4 being the latest version it supports, so this legacy-peer-deps
# allows us to install it anyway.
legacy-peer-deps=true
#Prevent folks from ignoring an important error when building from source
engine-strict=true

View File

@@ -65,12 +65,6 @@ Open MCT is built using [`npm`](http://npmjs.com/) and [`webpack`](https://webpa
See our documentation for a guide on [building Applications with Open MCT](https://github.com/nasa/openmct/blob/master/API.md#starting-an-open-mct-application).
## Compatibility
This is a fast moving project and we do our best to test and support the widest possible range of browsers, operating systems, and nodejs APIs. We have a published list of support available in our package.json's `browserslist` key.
If you encounter an issue with a particular browser, OS, or nodejs API, please file a [GitHub issue](https://github.com/nasa/openmct/issues/new/choose)
## Plugins
Open MCT can be extended via plugins that make calls to the Open MCT API. A plugin is a group

2
app.js
View File

@@ -64,7 +64,7 @@ app.use(require('webpack-dev-middleware')(
compiler,
{
publicPath: '/dist',
stats: 'errors-warnings'
logLevel: 'warn'
}
));

View File

@@ -1,9 +0,0 @@
// This is a Babel config that webpack.coverage.js uses in order to instrument
// code with coverage instrumentation.
const babelConfig = {
plugins: [['babel-plugin-istanbul', {
extension: ['.js', '.vue']
}]]
};
module.exports = babelConfig;

View File

@@ -1,62 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 test suite is dedicated to tests which verify branding related components.
*/
const { test, expect } = require('@playwright/test');
test.describe('Branding tests', () => {
test('About Modal launches with basic branding properties', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click About button
await page.click('.l-shell__app-logo');
// Verify that the NASA Logo Appears
await expect(await page.locator('.c-about__image')).toBeVisible();
// Modify the Build information in 'about' Modal
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info');
await expect(versionInformationLocator).toBeEnabled();
await expect.soft(versionInformationLocator).toContainText(/Version: \d/);
await expect.soft(versionInformationLocator).toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/);
await expect.soft(versionInformationLocator).toContainText(/Revision: \b[0-9a-f]{5,40}\b/);
await expect.soft(versionInformationLocator).toContainText(/Branch: ./);
});
test('Verify Links in About Modal', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click About button
await page.click('.l-shell__app-logo');
// Verify that clicking on the third party licenses information opens up another tab on licenses url
const [page2] = await Promise.all([
page.waitForEvent('popup'),
page.locator('text=click here for third party licensing information').click()
]);
expect(page2.waitForURL('**\/licenses**')).toBeTruthy();
});
});

View File

@@ -1,62 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 test suite is dedicated to tests which verify the basic operations surrounding the example event generator.
*/
const { test, expect } = require('@playwright/test');
test.describe('Example Event Generator Operations', () => {
test('Can create example event generator with a name', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// let's make an event generator
await page.locator('button:has-text("Create")').click();
// Click li:has-text("Event Message Generator")
await page.locator('li:has-text("Event Message Generator")').click();
// Click text=Properties Title Notes >> input[type="text"]
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
// Fill text=Properties Title Notes >> input[type="text"]
await page.locator('text=Properties Title Notes >> input[type="text"]').fill('Test Event Generator');
// Press Enter
await page.locator('text=Properties Title Notes >> input[type="text"]').press('Enter');
// Click text=OK
await Promise.all([
page.waitForNavigation({ url: /.*&view=table/ }),
page.locator('text=OK').click()
]);
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Test Event Generator');
// Click button:has-text("Fixed Timespan")
await page.locator('button:has-text("Fixed Timespan")').click();
});
test.fixme('telemetry is coming in for test event', async ({ page }) => {
// Go to object created in step one
// Verify the telemetry table is filled with > 1 row
});
test.fixme('telemetry is sorted by time ascending', async ({ page }) => {
// Go to object created in step one
// Verify the telemetry table has a class with "is-sorting asc"
});
});

View File

@@ -1,166 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
*/
const { test, expect } = require('@playwright/test');
test.describe('Sine Wave Generator', () => {
test('Create new Sine Wave Generator Object and validate create Form Logic', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click Sine Wave Generator
await page.click('text=Sine Wave Generator');
// Verify that the each required field has required indicator
// Title
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(['c-form-row__state-indicator req']);
// Verify that the Notes row does not have a required indicator
await expect(page.locator('.c-form__section div:nth-child(3) .form-row .c-form-row__state-indicator')).not.toContain('.req');
// Period
await expect(page.locator('.c-form__section div:nth-child(4) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Amplitude
await expect(page.locator('.c-form__section div:nth-child(5) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Offset
await expect(page.locator('.c-form__section div:nth-child(6) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Data Rate
await expect(page.locator('.c-form__section div:nth-child(7) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Phase
await expect(page.locator('.c-form__section div:nth-child(8) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Randomness
await expect(page.locator('.c-form__section div:nth-child(9) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Verify that by removing value from required text field shows invalid indicator
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('');
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(['c-form-row__state-indicator req invalid']);
// Verify that by adding value to empty required text field changes invalid to valid indicator
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('non empty');
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(['c-form-row__state-indicator req valid']);
// Verify that by removing value from required number field shows invalid indicator
await page.locator('.field.control.l-input-sm input').first().fill('');
await expect(page.locator('.c-form__section div:nth-child(4) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req invalid']);
// Verify that by adding value to empty required number field changes invalid to valid indicator
await page.locator('.field.control.l-input-sm input').first().fill('3');
await expect(page.locator('.c-form__section div:nth-child(4) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req valid']);
// Verify that can change value of number field by up/down arrows keys
// Click .field.control.l-input-sm input >> nth=0
await page.locator('.field.control.l-input-sm input').first().click();
// Press ArrowUp 3 times to change value from 3 to 6
await page.locator('.field.control.l-input-sm input').first().press('ArrowUp');
await page.locator('.field.control.l-input-sm input').first().press('ArrowUp');
await page.locator('.field.control.l-input-sm input').first().press('ArrowUp');
const value = await page.locator('.field.control.l-input-sm input').first().inputValue();
await expect(value).toBe('6');
// Click .c-form-row__state-indicator.grows
await page.locator('.c-form-row__state-indicator.grows').click();
// Click text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').click();
// Click .c-form-row__state-indicator >> nth=0
await page.locator('.c-form-row__state-indicator').first().click();
// Fill text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('New Sine Wave Generator');
// Double click div:nth-child(4) .form-row .c-form-row__controls
await page.locator('div:nth-child(4) .form-row .c-form-row__controls').dblclick();
// Click .field.control.l-input-sm input >> nth=0
await page.locator('.field.control.l-input-sm input').first().click();
// Click div:nth-child(4) .form-row .c-form-row__state-indicator
await page.locator('div:nth-child(4) .form-row .c-form-row__state-indicator').click();
// Click .field.control.l-input-sm input >> nth=0
await page.locator('.field.control.l-input-sm input').first().click();
// Click .field.control.l-input-sm input >> nth=0
await page.locator('.field.control.l-input-sm input').first().click();
// Click div:nth-child(5) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click();
// Click div:nth-child(5) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click();
// Click div:nth-child(5) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click();
// Click div:nth-child(6) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(6) .form-row .c-form-row__controls .form-control .field input').click();
// Double click div:nth-child(7) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').dblclick();
// Click div:nth-child(7) .form-row .c-form-row__state-indicator
await page.locator('div:nth-child(7) .form-row .c-form-row__state-indicator').click();
// Click div:nth-child(7) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').click();
// Fill div:nth-child(7) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').fill('3');
//Click text=OK
await Promise.all([
page.waitForNavigation(),
page.click('text=OK')
]);
// Verify that the Sine Wave Generator is displayed and correct
// Verify object properties
await expect(page.locator('.l-browse-bar__object-name')).toContainText('New Sine Wave Generator');
// Verify canvas rendered
await page.locator('canvas').nth(1).click({
position: {
x: 341,
y: 28
}
});
// Verify that where we click on canvas shows the number we clicked on
// Note that any number will do, we just care that a number exists
await expect(page.locator('.value-to-display-nearestValue')).toContainText(/[+-]?([0-9]*[.])?[0-9]+/);
});
});

View File

@@ -1,42 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 test suite is dedicated to tests which verify the basic operations surrounding moving objects.
*/
const { test, expect } = require('@playwright/test');
test.describe('Move item tests', () => {
test.fixme('Create a basic object and verify that it can be moved to another Folder', async ({ page }) => {
//Create and save Folder
//Create and save Domain Object
//Verify that the newly created domain object can be moved to Folder from Step 1.
//Verify that newly moved object appears in the correct point in Tree
//Verify that newly moved object appears correctly in Inspector panel
});
test.fixme('Create a basic object and verify that it cannot be moved to object without Composition Provider', async ({ page }) => {
//Create and save Telemetry Object
//Create and save Domain Object
//Verify that the newly created domain object cannot be moved to Telemetry Object from step 1.
});
});

View File

@@ -1,27 +0,0 @@
(function () {
document.addEventListener('DOMContentLoaded', () => {
const PERSISTENCE_KEY = 'persistence-tests';
const openmct = window.openmct;
openmct.objects.addRoot({
namespace: PERSISTENCE_KEY,
key: PERSISTENCE_KEY
});
openmct.objects.addProvider(PERSISTENCE_KEY, {
get(identifier) {
if (identifier.key !== PERSISTENCE_KEY) {
return undefined;
} else {
return Promise.resolve({
identifier,
type: 'folder',
name: 'Persistence Testing',
location: 'ROOT',
composition: []
});
}
}
});
});
}());

View File

@@ -1,77 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
*/
const { test, expect } = require('@playwright/test');
const path = require('path');
// https://github.com/nasa/openmct/issues/4323#issuecomment-1067282651
test.describe('Persistence operations', () => {
// add non persistable root item
test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef
await page.addInitScript({ path: path.join(__dirname, 'addNoneditableObject.js') });
});
test('Persistability should be respected in the create form location field', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click the Create button
await page.click('button:has-text("Create")');
// Click text=Condition Set
await page.click('text=Condition Set');
// Click form[name="mctForm"] >> text=Persistence Testing
await page.locator('form[name="mctForm"] >> text=Persistence Testing').click();
// Check that "OK" button is disabled
const okButton = page.locator('button:has-text("OK")');
await expect(okButton).toBeDisabled();
});
test('Non-persistable objects should not show persistence related actions', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click text=Persistence Testing >> nth=0
await page.locator('text=Persistence Testing').first().click({
button: 'right'
});
const menuOptions = page.locator('.c-menu ul');
await expect.soft(menuOptions).toContainText(['Open In New Tab', 'View', 'Create Link']);
await expect(menuOptions).not.toContainText(['Move', 'Duplicate', 'Remove', 'Add New Folder', 'Edit Properties...', 'Export as JSON', 'Import from JSON']);
});
test.fixme('Cannot move a previously created domain object to non-peristable boject in Move Modal', async ({ page }) => {
//Create a domain object
//Save Domain object
//Move Object and verify that cannot select non-persistable object
//Move Object to My Items
//Verify successful move
});
});

View File

@@ -1,48 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 test suite is dedicated to tests which verify the basic operations surrounding exportAsJSON.
*/
const { test, expect } = require('@playwright/test');
test.describe('ExportAsJSON', () => {
test.fixme('Create a basic object and verify that it can be exported as JSON from Tree', async ({ page }) => {
//Create domain object
//Save Domain Object
//Verify that the newly created domain object can be exported as JSON from the Tree
});
test.fixme('Create a basic object and verify that it can be exported as JSON from 3 dot menu', async ({ page }) => {
//Create domain object
//Save Domain Object
//Verify that the newly created domain object can be exported as JSON from the 3 dot menu
});
test.fixme('Verify that a nested Object can be exported as JSON', async ({ page }) => {
// Create 2 objects with hierarchy
// Export as JSON
// Verify Hiearchy
});
test.fixme('Verify that the ExportAsJSON dropdown does not appear for the item X', async ({ page }) => {
// Other than non-persistible objects
});
});

View File

@@ -1,46 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 test suite is dedicated to tests which verify the basic operations surrounding importAsJSON.
*/
const { test, expect } = require('@playwright/test');
test.describe('ExportAsJSON', () => {
test.fixme('Verify that domain object can be importAsJSON from Tree', async ({ page }) => {
//Verify that an testdata JSON file can be imported from Tree
//Verify correctness of imported domain object
});
test.fixme('Verify that domain object can be importAsJSON from 3 dot menu on folder', async ({ page }) => {
//Verify that an testdata JSON file can be imported from 3 dot menu on folder domain object
//Verify correctness of imported domain object
});
test.fixme('Verify that a nested Objects can be importAsJSON', async ({ page }) => {
// Testdata with hierarchy
// ImportAsJSON on Tree
// Verify Hierarchy
});
test.fixme('Verify that the ImportAsJSON dropdown does not appear for the item X', async ({ page }) => {
// Other than non-persistible objects
});
});

View File

@@ -1,66 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 test suite is dedicated to tests which verify the basic operations surrounding Clock.
*/
const { test, expect } = require('@playwright/test');
test.describe('Clock Generator', () => {
test('Timezone dropdown will collapse when clicked outside or on dropdown icon again', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/4878'
});
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click Clock
await page.click('text=Clock');
// Click .icon-arrow-down
await page.locator('.icon-arrow-down').click();
//verify if the autocomplete dropdown is visible
await expect(page.locator(".optionPreSelected")).toBeVisible();
// Click .icon-arrow-down
await page.locator('.icon-arrow-down').click();
// Verify clicking on the autocomplete arrow collapses the dropdown
await expect(page.locator(".optionPreSelected")).not.toBeVisible();
// Click timezone input to open dropdown
await page.locator('.autocompleteInput').click();
//verify if the autocomplete dropdown is visible
await expect(page.locator(".optionPreSelected")).toBeVisible();
// Verify clicking outside the autocomplete dropdown collapses it
await page.locator('text=Timezone').click();
// Verify clicking on the autocomplete arrow collapses the dropdown
await expect(page.locator(".optionPreSelected")).not.toBeVisible();
});
});

View File

@@ -1,217 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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 test suite is dedicated to tests which verify the basic operations surrounding imagery,
but only assume that example imagery is present.
*/
const { test, expect } = require('@playwright/test');
test.describe('Example Imagery', () => {
test.beforeEach(async ({ page }) => {
page.on('console', msg => console.log(msg.text()))
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click text=Example Imagery
await page.click('text=Example Imagery');
// Click text=OK
await Promise.all([
page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/dab945d4-5a84-480e-8180-222b4aa730fa?tc.mode=fixed&tc.startBound=1639696164435&tc.endBound=1639697964435&tc.timeSystem=utc&view=conditionSet.view' }*/),
page.click('text=OK')
]);
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
});
const backgroundImageSelector = '.c-imagery__main-image__background-image';
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
const bgImageLocator = await page.locator(backgroundImageSelector);
const deltaYStep = 100; //equivalent to 1x zoom
await bgImageLocator.hover();
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
// zoom in
await bgImageLocator.hover();
await page.mouse.wheel(0, deltaYStep * 2);
// wait for zoom animation to finish
await bgImageLocator.hover();
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
// zoom out
await bgImageLocator.hover();
await page.mouse.wheel(0, -deltaYStep);
// wait for zoom animation to finish
await bgImageLocator.hover();
const imageMouseZoomedOut = await page.locator(backgroundImageSelector).boundingBox();
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
expect(imageMouseZoomedOut.height).toBeLessThan(imageMouseZoomedIn.height);
expect(imageMouseZoomedOut.width).toBeLessThan(imageMouseZoomedIn.width);
});
test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
const deltaYStep = 100; //equivalent to 1x zoom
const bgImageLocator = await page.locator(backgroundImageSelector);
await bgImageLocator.hover();
// zoom in
await page.mouse.wheel(0, deltaYStep * 2);
await bgImageLocator.hover();
const zoomedBoundingBox = await bgImageLocator.boundingBox();
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
// move to the right
// center the mouse pointer
await page.mouse.move(imageCenterX, imageCenterY);
// pan right
await page.keyboard.down('Alt');
await page.mouse.down();
await page.mouse.move(imageCenterX - 200, imageCenterY, 10);
await page.mouse.up();
await page.keyboard.up('Alt');
const afterRightPanBoundingBox = await bgImageLocator.boundingBox();
expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x);
// pan left
await page.keyboard.down('Alt');
await page.mouse.down();
await page.mouse.move(imageCenterX, imageCenterY, 10);
await page.mouse.up();
await page.keyboard.up('Alt');
const afterLeftPanBoundingBox = await bgImageLocator.boundingBox();
expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x);
// pan up
await page.mouse.move(imageCenterX, imageCenterY);
await page.keyboard.down('Alt');
await page.mouse.down();
await page.mouse.move(imageCenterX, imageCenterY + 200, 10);
await page.mouse.up();
await page.keyboard.up('Alt');
const afterUpPanBoundingBox = await bgImageLocator.boundingBox();
expect(afterUpPanBoundingBox.y).toBeGreaterThan(afterLeftPanBoundingBox.y);
// pan down
await page.keyboard.down('Alt');
await page.mouse.down();
await page.mouse.move(imageCenterX, imageCenterY - 200, 10);
await page.mouse.up();
await page.keyboard.up('Alt');
const afterDownPanBoundingBox = await bgImageLocator.boundingBox();
expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y);
});
test('Can use + - buttons to zoom on the image', async ({ page }) => {
const bgImageLocator = await page.locator(backgroundImageSelector);
await bgImageLocator.hover();
const zoomInBtn = await page.locator('.t-btn-zoom-in');
const zoomOutBtn = await page.locator('.t-btn-zoom-out');
const initialBoundingBox = await bgImageLocator.boundingBox();
await zoomInBtn.click();
await zoomInBtn.click();
// wait for zoom animation to finish
await bgImageLocator.hover();
const zoomedInBoundingBox = await bgImageLocator.boundingBox();
expect(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
expect(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
await zoomOutBtn.click();
// wait for zoom animation to finish
await bgImageLocator.hover();
const zoomedOutBoundingBox = await bgImageLocator.boundingBox();
expect(zoomedOutBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
expect(zoomedOutBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width);
});
test('Can use the reset button to reset the image', async ({ page }) => {
const bgImageLocator = await page.locator(backgroundImageSelector);
await bgImageLocator.hover();
const zoomInBtn = await page.locator('.t-btn-zoom-in');
const zoomResetBtn = await page.locator('.t-btn-zoom-reset');
const initialBoundingBox = await bgImageLocator.boundingBox();
await zoomInBtn.click();
await zoomInBtn.click();
// wait for zoom animation to finish
await bgImageLocator.hover();
const zoomedInBoundingBox = await bgImageLocator.boundingBox();
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
await zoomResetBtn.click();
await bgImageLocator.hover();
const resetBoundingBox = await bgImageLocator.boundingBox();
expect.soft(resetBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
expect.soft(resetBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width);
expect.soft(resetBoundingBox.height).toEqual(initialBoundingBox.height);
expect(resetBoundingBox.width).toEqual(initialBoundingBox.width);
});
//test('Can use Mouse Wheel to zoom in and out of previous image');
//test('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
//test.skip('Can zoom into a previous image from thumbstrip in real-time or fixed-time');
//test.skip('Clicking on the left arrow should pause the imagery and go to previous image');
//test.skip('If the imagery view is in pause mode, it should not be updated when new images come in');
//test.skip('If the imagery view is not in pause mode, it should be updated when new images come in');
});
test.describe('Example Imagery in Display layout', () => {
test.skip('Can use Mouse Wheel to zoom in and out of previous image');
test.skip('Can use alt+drag to move around image once zoomed in');
test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
test.skip('Clicking on the left arrow should pause the imagery and go to previous image');
test.skip('If the imagery view is in pause mode, it should not be updated when new images come in');
test.skip('If the imagery view is not in pause mode, it should be updated when new images come in');
});
test.describe('Example Imagery in Flexible layout', () => {
test.skip('Can use Mouse Wheel to zoom in and out of previous image');
test.skip('Can use alt+drag to move around image once zoomed in');
test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
test.skip('Clicking on the left arrow should pause the imagery and go to previous image');
test.skip('If the imagery view is in pause mode, it should not be updated when new images come in');
test.skip('If the imagery view is not in pause mode, it should be updated when new images come in');
});
test.describe('Example Imagery in Tabs view', () => {
test.skip('Can use Mouse Wheel to zoom in and out of previous image');
test.skip('Can use alt+drag to move around image once zoomed in');
test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
test.skip('Can zoom into a previous image from thumbstrip in real-time or fixed-time');
test.skip('Clicking on the left arrow should pause the imagery and go to previous image');
test.skip('If the imagery view is in pause mode, it should not be updated when new images come in');
test.skip('If the imagery view is not in pause mode, it should be updated when new images come in');
});

View File

@@ -1,69 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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.
*****************************************************************************/
const { test, expect } = require('@playwright/test');
test.describe('Time counductor operations', () => {
test('validate start time does not exceeds end time', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
const year = new Date().getFullYear();
let startDate = 'xxxx-01-01 01:00:00.000Z';
startDate = year + startDate.substring(4);
let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4);
const startTimeLocator = page.locator('input[type="text"]').first();
const endTimeLocator = page.locator('input[type="text"]').nth(1);
// Click start time
await startTimeLocator.click();
// Click end time
await endTimeLocator.click();
await endTimeLocator.fill(endDate.toString());
await startTimeLocator.fill(startDate.toString());
// invalid start date
startDate = (year + 1) + startDate.substring(4);
await startTimeLocator.fill(startDate.toString());
await endTimeLocator.click();
const startDateValidityStatus = await startTimeLocator.evaluate((element) => element.checkValidity());
expect(startDateValidityStatus).not.toBeTruthy();
// fix to valid start date
startDate = (year - 1) + startDate.substring(4);
await startTimeLocator.fill(startDate.toString());
// invalid end date
endDate = (year - 2) + endDate.substring(4);
await endTimeLocator.fill(endDate.toString());
await startTimeLocator.click();
const endDateValidityStatus = await endTimeLocator.evaluate((element) => element.checkValidity());
expect(endDateValidityStatus).not.toBeTruthy();
});
});

View File

@@ -112,48 +112,28 @@ test('Visual - Default Condition Widget', async ({ page }) => {
await percySnapshot(page, 'Default Condition Widget');
});
test('Visual - Time Conductor start time is less than end time', async ({ page }) => {
test.skip('Visual - Display layout items view', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
const year = new Date().getFullYear();
let startDate = 'xxxx-01-01 01:00:00.000Z';
startDate = year + startDate.substring(4);
//Click the Create button
await page.click('button:has-text("Create")');
let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4);
// Click text=Display Layout
await page.click('text=Display Layout');
await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
await page.locator('input[type="text"]').first().fill(startDate.toString());
// Click text=OK
await page.click('text=OK');
// verify no error msg
// Take a snapshot of the newly created Display Layout object
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'Default Time conductor');
await percySnapshot(page, 'Default Display Layout');
startDate = (year + 1) + startDate.substring(4);
await page.locator('input[type="text"]').first().fill(startDate.toString());
await page.locator('input[type="text"]').nth(1).click();
// Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
// verify error msg for start time (unable to capture snapshot of popup)
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'Start time error');
startDate = (year - 1) + startDate.substring(4);
await page.locator('input[type="text"]').first().fill(startDate.toString());
endDate = (year - 2) + endDate.substring(4);
await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
await page.locator('input[type="text"]').first().click();
// verify error msg for end time (unable to capture snapshot of popup)
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'End time error');
});
test('Visual - Sine Wave Generator Form', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click text=Save and Finish Editing
await page.locator('text=Save and Finish Editing').click();
//Click the Create button
await page.click('button:has-text("Create")');
@@ -161,13 +141,20 @@ test('Visual - Sine Wave Generator Form', async ({ page }) => {
// Click text=Sine Wave Generator
await page.click('text=Sine Wave Generator');
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'Default Sine Wave Generator Form');
// Click text=Save In Open MCT No items >> input[type="search"]
await page.locator('text=Save In Open MCT No items >> input[type="search"]').click();
await page.locator('.field.control.l-input-sm input').first().click();
await page.locator('.field.control.l-input-sm input').first().fill('');
// Fill text=Save In Open MCT No items >> input[type="search"]
await page.locator('text=Save In Open MCT No items >> input[type="search"]').fill('Unnamed Display Layout');
// Validate red x mark
// Click text=OK Cancel
await page.locator('text=OK Cancel').click();
// Click text=OK
await page.click('text=OK');
// Take a snapshot of the newly created Display Layout object
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'removed amplitude property value');
await percySnapshot(page, 'Default Sine Wave Generator');
});

View File

@@ -33,7 +33,7 @@ class EventTelemetryProvider {
generateData(firstObservedTime, count, startTime, duration, name) {
const millisecondsSinceStart = startTime - firstObservedTime;
const utc = startTime + (count * duration);
const utc = Math.floor(startTime / duration) * duration;
const ind = count % messages.length;
const message = messages[ind] + " - [" + millisecondsSinceStart + "]";
@@ -75,7 +75,7 @@ class EventTelemetryProvider {
const duration = domainObject.telemetry.duration * 1000;
const size = options.size ? options.size : this.defaultSize;
const data = [];
const firstObservedTime = options.start;
const firstObservedTime = Date.now();
let count = 0;
if (options.strategy === 'latest' || options.size === 1) {
@@ -83,7 +83,7 @@ class EventTelemetryProvider {
}
while (start <= end && data.length < size) {
const startTime = options.start + count;
const startTime = Date.now() + count;
data.push(this.generateData(firstObservedTime, count, startTime, duration, domainObject.name));
start += duration;
count += 1;

View File

@@ -35,7 +35,6 @@ describe('the plugin', () => {
telemetry: {
duration: 0
},
options: {},
type: 'eventGenerator'
};
@@ -62,13 +61,7 @@ describe('the plugin', () => {
});
});
it("supports requests without start/end defined", async () => {
const telemetry = await openmct.telemetry.request(mockDomainObject);
expect(telemetry[0].message).toContain('CC: Eagle, Houston');
});
it("supports requests with arbitrary start time in the past", async () => {
mockDomainObject.options.start = 100000000000; // Mar 03 1973
it("supports requests", async () => {
const telemetry = await openmct.telemetry.request(mockDomainObject);
expect(telemetry[0].message).toContain('CC: Eagle, Houston');
});

View File

@@ -26,7 +26,7 @@ import {
} from '../../src/utils/testing';
import ExampleUserProvider from './ExampleUserProvider';
xdescribe("The Example User Plugin", () => {
describe("The Example User Plugin", () => {
let openmct;
beforeEach(() => {

View File

@@ -35,8 +35,8 @@ define([
phase: 0
};
function GeneratorProvider(openmct) {
this.workerInterface = new WorkerInterface(openmct);
function GeneratorProvider() {
this.workerInterface = new WorkerInterface();
}
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {

View File

@@ -21,13 +21,20 @@
*****************************************************************************/
define([
'raw-loader!./generatorWorker.js',
'uuid'
], function (
workerText,
uuid
) {
function WorkerInterface(openmct) {
// eslint-disable-next-line no-undef
const workerUrl = `${openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}generatorWorker.js`;
var workerBlob = new Blob(
[workerText],
{type: 'application/javascript'}
);
var workerUrl = URL.createObjectURL(workerBlob);
function WorkerInterface() {
this.worker = new Worker(workerUrl);
this.worker.onmessage = this.onMessage.bind(this);
this.callbacks = {};

View File

@@ -146,7 +146,7 @@ define([
}
});
openmct.telemetry.addProvider(new GeneratorProvider(openmct));
openmct.telemetry.addProvider(new GeneratorProvider());
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
openmct.telemetry.addProvider(new SinewaveLimitProvider());
};

View File

@@ -1,2 +1,3 @@
const testsContext = require.context('.', true, /^\.\/(src|example)\/.*Spec.js$/);
const testsContext = require.context('.', true, /\/(src|platform|\.\/example)\/.*Spec.js$/);
testsContext.keys().forEach(testsContext);

View File

@@ -22,9 +22,29 @@
/*global module,process*/
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
const coverageEnabled = process.env.COVERAGE === 'true';
const reporters = ['spec', 'junit'];
if (coverageEnabled) {
reporters.push('coverage-istanbul');
}
module.exports = (config) => {
const webpackConfig = require('./webpack.coverage.js');
const webpackConfig = require('./webpack.dev.js');
delete webpackConfig.output;
if (coverageEnabled) {
webpackConfig.module.rules.push({
test: /\.js$/,
exclude: /node_modules|e2e|example|lib|dist|\.*.*Spec\.js/,
use: {
loader: 'istanbul-instrumenter-loader',
options: {
esModules: true
}
}
});
}
config.set({
basePath: '',
@@ -38,15 +58,11 @@ module.exports = (config) => {
{
pattern: 'dist/inMemorySearchWorker.js*',
included: false
},
{
pattern: 'dist/generatorWorker.js*',
included: false
}
],
port: 9876,
reporters: ['spec', 'junit', 'coverage-istanbul'],
browsers: [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'],
reporters: reporters,
browsers: browsers,
client: {
jasmine: {
random: false,
@@ -67,6 +83,12 @@ module.exports = (config) => {
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
// HTML test reporting.
// htmlReporter: {
// outputDir: "dist/reports/tests",
// preserveDescribeNesting: true,
// foldAll: false
// },
junitReporter: {
outputDir: "dist/reports/tests",
outputFile: "test-results.xml",
@@ -74,7 +96,9 @@ module.exports = (config) => {
},
coverageIstanbulReporter: {
fixWebpackSourcePaths: true,
dir: "dist/reports/coverage",
dir: process.env.CIRCLE_ARTIFACTS
? process.env.CIRCLE_ARTIFACTS + '/coverage'
: "dist/reports/coverage",
reports: ['lcovonly', 'text-summary'],
thresholds: {
global: {
@@ -96,7 +120,8 @@ module.exports = (config) => {
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-warnings'
stats: 'errors-only',
logLevel: 'warn'
},
concurrency: 1,
singleRun: true,

View File

@@ -1,45 +1,40 @@
{
"name": "openmct",
"version": "2.0.2-SNAPSHOT",
"version": "2.0.1",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.16.3",
"@braintree/sanitize-url": "6.0.0",
"@percy/cli": "1.0.0-beta.76",
"@percy/cli": "1.0.0-beta.75",
"@percy/playwright": "1.0.1",
"@playwright/test": "1.19.2",
"@types/eventemitter3": "^1.0.0",
"@types/jasmine": "^4.0.1",
"@types/karma": "^6.3.2",
"@types/lodash": "^4.14.178",
"@types/mocha": "^9.1.0",
"allure-playwright": "2.0.0-beta.15",
"babel-loader": "8.2.3",
"babel-plugin-istanbul": "6.1.1",
"babel-eslint": "10.1.0",
"comma-separated-values": "3.6.4",
"copy-webpack-plugin": "10.2.0",
"core-js": "3.21.1",
"core-js": "3.20.3",
"cross-env": "7.0.3",
"css-loader": "4.0.0",
"d3-axis": "1.0.x",
"d3-scale": "1.0.x",
"d3-selection": "1.3.x",
"eslint": "8.11.0",
"eslint-plugin-compat": "4.0.2",
"eslint": "7.0.0",
"eslint-plugin-playwright": "0.8.0",
"eslint-plugin-vue": "8.5.0",
"eslint-plugin-vue": "7.5.0",
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
"exports-loader": "0.7.0",
"express": "4.13.1",
"file-loader": "6.1.0",
"file-saver": "2.0.5",
"git-rev-sync": "3.0.2",
"git-rev-sync": "1.4.0",
"html-loader": "0.5.5",
"html2canvas": "1.4.1",
"imports-loader": "0.8.0",
"jasmine-core": "4.0.1",
"istanbul-instrumenter-loader": "^3.0.1",
"jasmine-core": "4.0.0",
"jsdoc": "3.5.5",
"karma": "6.3.15",
"karma-chrome-launcher": "3.1.1",
"karma-chrome-launcher": "3.1.0",
"karma-cli": "2.0.0",
"karma-coverage": "2.1.1",
"karma-coverage-istanbul-reporter": "3.0.3",
@@ -48,53 +43,53 @@
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.33",
"karma-webpack": "5.0.0",
"lighthouse": "9.5.0",
"location-bar": "3.0.1",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.6.0",
"karma-webpack": "^5.0.0",
"location-bar": "^3.0.1",
"lodash": "^4.17.12",
"mini-css-extract-plugin": "2.4.5",
"moment": "2.29.1",
"moment-duration-format": "2.2.2",
"moment-timezone": "0.5.34",
"node-bourbon": "4.2.3",
"painterro": "1.2.56",
"plotly.js-basic-dist": "2.5.0",
"plotly.js-gl2d-dist": "2.5.0",
"printj": "1.3.1",
"request": "2.88.2",
"moment-duration-format": "^2.2.2",
"moment-timezone": "0.5.28",
"node-bourbon": "^4.2.3",
"painterro": "^1.2.56",
"plotly.js-basic-dist": "^2.5.0",
"plotly.js-gl2d-dist": "^2.5.0",
"printj": "^1.2.1",
"raw-loader": "^0.5.1",
"request": "^2.69.0",
"resolve-url-loader": "4.0.0",
"sass": "1.49.9",
"sass-loader": "12.6.0",
"sass": "1.49.0",
"sass-loader": "12.4.0",
"sinon": "13.0.1",
"style-loader": "^1.0.1",
"uuid": "3.3.3",
"uuid": "^3.3.3",
"vue": "2.6.14",
"vue-eslint-parser": "8.2.0",
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.68.0",
"webpack-cli": "4.9.2",
"webpack-dev-middleware": "5.3.1",
"webpack-hot-middleware": "2.25.1",
"webpack-dev-middleware": "^3.1.3",
"webpack-hot-middleware": "^2.22.3",
"webpack-merge": "5.8.0",
"zepto": "1.2.0"
"zepto": "^1.2.0"
},
"scripts": {
"clean": "rm -rf ./dist ./node_modules ./package-lock.json",
"clean": "rm -rf ./dist ./node_modules; rm package-lock.json",
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
"start": "node app.js",
"lint": "eslint example src --ext .js,.vue openmct.js",
"lint:fix": "eslint example src --ext .js,.vue openmct.js --fix",
"build:prod": "cross-env webpack --config webpack.prod.js",
"build:dev": "webpack --config webpack.dev.js",
"build:coverage": "webpack --config webpack.coverage.js",
"build:watch": "webpack --config webpack.dev.js --watch",
"info": "npx envinfo --system --browsers --npmPackages --binaries --languages --markdown",
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
"test:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition timeConductor",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
"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:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition.e2e",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js",
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js default",
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js",
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
@@ -110,15 +105,8 @@
"url": "https://github.com/nasa/openmct.git"
},
"engines": {
"node": ">=14.19.1"
"node": ">=12.22.0"
},
"browserslist": [
"Firefox ESR",
"not IE 11",
"last 2 Chrome versions",
"unreleased Chrome versions",
"ios_saf > 15"
],
"author": "",
"license": "Apache-2.0",
"private": true

View File

@@ -65,11 +65,10 @@ export default class FormControl {
*/
_getControlViewProvider(control) {
const self = this;
let rowComponent;
return {
show(element, model, onChange) {
rowComponent = new Vue({
const rowComponent = new Vue({
el: element,
components: {
FormControlComponent: DEFAULT_CONTROLS_MAP[control]
@@ -87,9 +86,6 @@ export default class FormControl {
});
return rowComponent;
},
destroy() {
rowComponent.$destroy();
}
};
}

View File

@@ -26,52 +26,45 @@
<div class="c-overlay__dialog-title">{{ model.title }}</div>
<div class="c-overlay__dialog-hint hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
</div>
<form
name="mctForm"
class="c-form__contents"
autocomplete="off"
@submit.prevent
<form name="mctForm"
class="c-form__contents"
autocomplete="off"
@submit.prevent
>
<div
v-for="section in formSections"
:key="section.id"
class="c-form__section"
:class="section.cssClass"
<div v-for="section in formSections"
:key="section.id"
class="c-form__section"
:class="section.cssClass"
>
<h2
v-if="section.name"
<h2 v-if="section.name"
class="c-form__section-header"
>
{{ section.name }}
</h2>
<div
v-for="(row, index) in section.rows"
:key="row.id"
class="u-contents"
<div v-for="(row, index) in section.rows"
:key="row.id"
class="u-contents"
>
<FormRow
:css-class="section.cssClass"
:first="index < 1"
:row="row"
@onChange="onChange"
<FormRow :css-class="section.cssClass"
:first="index < 1"
:row="row"
@onChange="onChange"
/>
</div>
</div>
</form>
<div class="mct-form__controls c-overlay__button-bar c-form__bottom-bar">
<button
tabindex="0"
:disabled="isInvalid"
class="c-button c-button--major"
@click="onSave"
<button tabindex="0"
:disabled="isInvalid"
class="c-button c-button--major"
@click="onSave"
>
{{ submitLabel }}
</button>
<button
tabindex="0"
class="c-button"
@click="onDismiss"
<button tabindex="0"
class="c-button"
@click="onDismiss"
>
{{ cancelLabel }}
</button>

View File

@@ -21,25 +21,21 @@
*****************************************************************************/
<template>
<div
class="form-row c-form__row"
:class="[{ 'first': first }]"
@onChange="onChange"
<div class="form-row c-form__row"
:class="[{ 'first': first }]"
@onChange="onChange"
>
<div
class="c-form-row__label"
:title="row.description"
<div class="c-form-row__label"
:title="row.description"
>
{{ row.name }}
</div>
<div
class="c-form-row__state-indicator"
:class="rowClass"
<div class="c-form-row__state-indicator"
:class="rowClass"
>
</div>
<div
v-if="row.control"
class="c-form-row__controls"
<div v-if="row.control"
class="c-form-row__controls"
>
<div ref="rowElement"></div>
</div>

View File

@@ -22,26 +22,20 @@
<template>
<div class="form-control autocomplete">
<span class="autocompleteInputAndArrow">
<input
v-model="field"
class="autocompleteInput"
type="text"
@click="inputClicked()"
@keydown="keyDown($event)"
>
<span
class="icon-arrow-down"
@click="arrowClicked()"
></span>
</span>
<div
class="autocompleteOptions"
@blur="hideOptions = true"
<input v-model="field"
class="autocompleteInput"
type="text"
@click="inputClicked()"
@keydown="keyDown($event)"
>
<span class="icon-arrow-down"
@click="arrowClicked()"
></span>
<div class="autocompleteOptions"
@blur="hideOptions = true"
>
<ul v-if="!hideOptions">
<li
v-for="opt in filteredOptions"
<li v-for="opt in filteredOptions"
:key="opt.optionId"
:class="{'optionPreSelected': optionIndex === opt.optionId}"
@click="fillInputWithString(opt.name)"
@@ -110,21 +104,10 @@ export default {
this.$emit('onChange', data);
}
},
hideOptions(newValue) {
if (!newValue) {
// adding a event listener when the hideOpntions is false (dropdown is visible)
// handleoutsideclick can collapse the dropdown when clicked outside autocomplete
document.body.addEventListener('click', this.handleOutsideClick);
} else {
//removing event listener when hideOptions become true (dropdown is collapsed)
document.body.removeEventListener('click', this.handleOutsideClick);
}
}
},
mounted() {
this.options = this.model.options;
this.autocompleteInputAndArrow = this.$el.getElementsByClassName('autocompleteInputAndArrow')[0];
this.autocompleteInputElement = this.$el.getElementsByClassName('autocompleteInput')[0];
if (this.options[0].name) {
// If "options" include name, value pair
@@ -136,9 +119,6 @@ export default {
this.optionNames = this.options;
}
},
destroyed() {
document.body.removeEventListener('click', this.handleOutsideClick);
},
methods: {
decrementOptionIndex() {
if (this.optionIndex === 0) {
@@ -192,21 +172,7 @@ export default {
// to show them all the options
this.showFilteredOptions = false;
this.autocompleteInputElement.select();
if (this.hideOptions) {
this.showOptions();
} else {
this.hideOptions = true;
}
},
handleOutsideClick(event) {
// if click event is detected outside autocomplete (both input & arrow) while the
// dropdown is visible, this will collapse the dropdown.
const clickedInsideAutocomplete = this.autocompleteInputAndArrow.contains(event.target);
if (!clickedInsideAutocomplete && !this.hideOptions) {
this.hideOptions = true;
}
this.showOptions();
},
optionMouseover(optionId) {
this.optionIndex = optionId;

View File

@@ -22,11 +22,10 @@
<template>
<div class="c-form-control--clock-display-format-fields">
<SelectField
v-for="item in items"
:key="item.key"
:model="item"
@onChange="onChange"
<SelectField v-for="item in items"
:key="item.key"
:model="item"
@onChange="onChange"
/>
</div>
</template>

View File

@@ -22,13 +22,12 @@
<template>
<span>
<CompositeItem
v-for="(item, index) in model.items"
:key="item.name"
:first="index < 1"
:value="JSON.stringify(model.value[index])"
:item="item"
@onChange="onChange"
<CompositeItem v-for="(item, index) in model.items"
:key="item.name"
:first="index < 1"
:value="JSON.stringify(model.value[index])"
:item="item"
@onChange="onChange"
/>
</span>
</template>

View File

@@ -22,11 +22,10 @@
<template>
<div :class="compositeCssClass">
<FormRow
:css-class="item.cssClass"
:first="first"
:row="row"
@onChange="onChange"
<FormRow :css-class="item.cssClass"
:first="first"
:row="row"
@onChange="onChange"
/>
<span class="composite-control-label">
{{ item.name }}

View File

@@ -27,55 +27,50 @@
<div class="hint time sm">Min</div>
<div class="hint time sm">Sec</div>
<div class="hint timezone">Timezone</div>
<form
ref="dateTimeForm"
prevent
class="u-contents"
<form ref="dateTimeForm"
prevent
class="u-contents"
>
<div class="field control date">
<input
v-model="date"
:pattern="/\d{4}-\d{2}-\d{2}/"
:placeholder="format"
type="date"
name="date"
@change="onChange"
<input v-model="date"
:pattern="/\d{4}-\d{2}-\d{2}/"
:placeholder="format"
type="date"
name="date"
@change="onChange"
>
</div>
<div class="field control hour sm">
<input
v-model="hour"
:pattern="/\d+/"
type="number"
name="hour"
maxlength="10"
min="0"
max="23"
@change="onChange"
<input v-model="hour"
:pattern="/\d+/"
type="number"
name="hour"
maxlength="10"
min="0"
max="23"
@change="onChange"
>
</div>
<div class="field control min sm">
<input
v-model="min"
:pattern="/\d+/"
type="number"
name="min"
maxlength="2"
min="0"
max="59"
@change="onChange"
<input v-model="min"
:pattern="/\d+/"
type="number"
name="min"
maxlength="2"
min="0"
max="59"
@change="onChange"
>
</div>
<div class="field control sec sm">
<input
v-model="sec"
:pattern="/\d+/"
type="number"
name="sec"
maxlength="2"
min="0"
max="59"
@change="onChange"
<input v-model="sec"
:pattern="/\d+/"
type="number"
name="sec"
maxlength="2"
min="0"
max="59"
@change="onChange"
>
</div>
<div class="field control timezone">

View File

@@ -22,21 +22,18 @@
<template>
<span class="form-control shell">
<span
class="field control"
:class="model.cssClass"
<span class="field control"
:class="model.cssClass"
>
<input
id="fileElem"
ref="fileInput"
type="file"
accept=".json"
style="display:none"
<input id="fileElem"
ref="fileInput"
type="file"
accept=".json"
style="display:none"
>
<button
id="fileSelect"
class="c-button"
@click="selectFile"
<button id="fileSelect"
class="c-button"
@click="selectFile"
>
{{ name }}
</button>

View File

@@ -22,25 +22,21 @@
<template>
<span class="form-control shell">
<span
class="field control"
:class="model.cssClass"
<span class="field control"
:class="model.cssClass"
>
<input
v-model="field"
type="number"
:min="model.min"
:max="model.max"
:step="model.step"
@input="updateText()"
<input v-model="field"
type="number"
:min="model.min"
:max="model.max"
:step="model.step"
@blur="blur()"
>
</span>
</span>
</template>
<script>
import { throttle } from 'lodash';
export default {
props: {
model: {
@@ -53,12 +49,8 @@ export default {
field: this.model.value
};
},
mounted() {
this.updateText = throttle(this.updateText.bind(this), 200);
},
methods: {
updateText() {
console.log('updateText', this.field);
blur() {
const data = {
model: this.model,
value: this.field

View File

@@ -22,16 +22,14 @@
<template>
<div class="form-control select-field">
<select
v-model="selected"
required="model.required"
name="mctControl"
@change="onChange($event)"
<select v-model="selected"
required="model.required"
name="mctControl"
@change="onChange($event)"
>
<option
v-for="option in model.options"
:key="option.name"
:value="option.value"
<option v-for="option in model.options"
:key="option.name"
:value="option.value"
>
{{ option.name }}
</option>

View File

@@ -22,15 +22,13 @@
<template>
<span class="form-control shell">
<span
class="field control"
:class="model.cssClass"
<span class="field control"
:class="model.cssClass"
>
<textarea
v-model="field"
type="text"
:size="model.size"
@input="updateText()"
<textarea v-model="field"
type="text"
:size="model.size"
@blur="blur()"
>
</textarea>
</span>
@@ -38,8 +36,6 @@
</template>
<script>
import { throttle } from 'lodash';
export default {
props: {
model: {
@@ -52,11 +48,8 @@ export default {
field: this.model.value
};
},
mounted() {
this.updateText = throttle(this.updateText.bind(this), 500);
},
methods: {
updateText() {
blur() {
const data = {
model: this.model,
value: this.field

View File

@@ -22,23 +22,19 @@
<template>
<span class="form-control shell">
<span
class="field control"
:class="model.cssClass"
<span class="field control"
:class="model.cssClass"
>
<input
v-model="field"
type="text"
:size="model.size"
@input="updateText()"
<input v-model="field"
type="text"
:size="model.size"
@blur="blur()"
>
</span>
</span>
</template>
<script>
import { throttle } from 'lodash';
export default {
props: {
model: {
@@ -51,11 +47,8 @@ export default {
field: this.model.value
};
},
mounted() {
this.updateText = throttle(this.updateText.bind(this), 500);
},
methods: {
updateText() {
blur() {
const data = {
model: this.model,
value: this.field

View File

@@ -1,7 +1,6 @@
<template>
<div
class="c-menu"
:class="options.menuClass"
<div class="c-menu"
:class="options.menuClass"
>
<ul v-if="options.actions.length && options.actions[0].length">
<template

View File

@@ -1,10 +1,8 @@
<template>
<div
class="c-menu"
:class="[options.menuClass, 'c-super-menu']"
<div class="c-menu"
:class="[options.menuClass, 'c-super-menu']"
>
<ul
v-if="options.actions.length && options.actions[0].length"
<ul v-if="options.actions.length && options.actions[0].length"
class="c-super-menu__menu"
>
<template
@@ -36,8 +34,7 @@
</template>
</ul>
<ul
v-else
<ul v-else
class="c-super-menu__menu"
>
<li

View File

@@ -365,7 +365,3 @@ class TimeContext extends EventEmitter {
}
export default TimeContext;
/**
@typedef {{start: number, end: number}} Bounds
*/

View File

@@ -1,34 +1,28 @@
<template>
<div
ref="plotWrapper"
class="has-local-controls"
:class="{ 's-unsynced' : isZoomed }"
<div ref="plotWrapper"
class="has-local-controls"
:class="{ 's-unsynced' : isZoomed }"
>
<div
v-if="isZoomed"
class="l-state-indicators"
<div v-if="isZoomed"
class="l-state-indicators"
>
<span
class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
></span>
</div>
<div
ref="plot"
class="c-bar-chart"
@plotly_relayout="zoom"
<div ref="plot"
class="c-bar-chart"
@plotly_relayout="zoom"
></div>
<div
v-if="false"
ref="localControl"
class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
<div v-if="false"
ref="localControl"
class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
>
<button
v-if="data.length"
class="c-button icon-reset"
:disabled="!isZoomed"
title="Reset pan/zoom"
@click="reset()"
<button v-if="data.length"
class="c-button icon-reset"
:disabled="!isZoomed"
title="Reset pan/zoom"
@click="reset()"
>
</button>
</div>

View File

@@ -21,13 +21,12 @@
-->
<template>
<BarGraph
ref="barGraph"
class="c-plot c-bar-chart-view"
:data="trace"
:plot-axis-title="plotAxisTitle"
@subscribe="subscribeToAll"
@unsubscribe="removeAllSubscriptions"
<BarGraph ref="barGraph"
class="c-plot c-bar-chart-view"
:data="trace"
:plot-axis-title="plotAxisTitle"
@subscribe="subscribeToAll"
@unsubscribe="removeAllSubscriptions"
/>
</template>

View File

@@ -22,13 +22,11 @@
<template>
<ul class="c-tree c-bar-graph-options">
<h2 title="Display properties for this object">Bar Graph Series</h2>
<li
v-for="series in domainObject.composition"
<li v-for="series in domainObject.composition"
:key="series.key"
>
<series-options
:item="series"
:color-palette="colorPalette"
<series-options :item="series"
:color-palette="colorPalette"
/>
</li>
</ul>

View File

@@ -21,14 +21,12 @@
-->
<template>
<ul>
<li
class="c-tree__item menus-to-left"
<li class="c-tree__item menus-to-left"
:class="aliasCss"
>
<span
class="c-disclosure-triangle is-enabled flex-elem"
:class="expandedCssClass"
@click="expanded = !expanded"
<span class="c-disclosure-triangle is-enabled flex-elem"
:class="expandedCssClass"
@click="expanded = !expanded"
>
</span>
@@ -38,15 +36,14 @@
<div class="c-object-label__name">{{ name }}</div>
</div>
</li>
<ColorSwatch
v-if="expanded"
:current-color="currentColor"
title="Manually set the color for this bar graph series."
edit-title="Manually set the color for this bar graph series"
view-title="The color for this bar graph series."
short-label="Color"
class="grid-properties"
@colorSet="setColor"
<ColorSwatch v-if="expanded"
:current-color="currentColor"
title="Manually set the color for this bar graph series."
edit-title="Manually set the color for this bar graph series"
view-title="The color for this bar graph series."
short-label="Color"
class="grid-properties"
@colorSet="setColor"
/>
</ul>
</template>

View File

@@ -21,35 +21,31 @@
*****************************************************************************/
<template>
<div
class="c-condition-h"
:class="{ 'is-drag-target': draggingOver }"
@dragover.prevent
@drop.prevent="dropCondition($event, conditionIndex)"
@dragenter="dragEnter($event, conditionIndex)"
@dragleave="dragLeave($event, conditionIndex)"
<div class="c-condition-h"
:class="{ 'is-drag-target': draggingOver }"
@dragover.prevent
@drop.prevent="dropCondition($event, conditionIndex)"
@dragenter="dragEnter($event, conditionIndex)"
@dragleave="dragLeave($event, conditionIndex)"
>
<div class="c-condition-h__drop-target"></div>
<div
v-if="isEditing"
:class="{'is-current': condition.id === currentConditionId}"
class="c-condition c-condition--edit"
<div v-if="isEditing"
:class="{'is-current': condition.id === currentConditionId}"
class="c-condition c-condition--edit"
>
<!-- Edit view -->
<div class="c-condition__header">
<span
class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
title="Drag to reorder conditions"
:class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
:draggable="!condition.isDefault"
@dragstart="dragStart"
@dragend="dragEnd"
<span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
title="Drag to reorder conditions"
:class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
:draggable="!condition.isDefault"
@dragstart="dragStart"
@dragend="dragEnd"
></span>
<span
class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
<span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
></span>
<span class="c-condition__name">{{ condition.configuration.name }}</span>
@@ -58,123 +54,107 @@
Define criteria
</template>
<span v-else>
<condition-description
:show-label="false"
:condition="condition"
<condition-description :show-label="false"
:condition="condition"
/>
</span>
</span>
<div class="c-condition__buttons">
<button
v-if="!condition.isDefault"
class="c-click-icon c-condition__duplicate-button icon-duplicate"
title="Duplicate this condition"
@click="cloneCondition"
<button v-if="!condition.isDefault"
class="c-click-icon c-condition__duplicate-button icon-duplicate"
title="Duplicate this condition"
@click="cloneCondition"
></button>
<button
v-if="!condition.isDefault"
class="c-click-icon c-condition__delete-button icon-trash"
title="Delete this condition"
@click="removeCondition"
<button v-if="!condition.isDefault"
class="c-click-icon c-condition__delete-button icon-trash"
title="Delete this condition"
@click="removeCondition"
></button>
</div>
</div>
<div
v-if="expanded"
class="c-condition__definition c-cdef"
<div v-if="expanded"
class="c-condition__definition c-cdef"
>
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Condition Name</span>
<span class="c-cdef__controls">
<input
v-model="condition.configuration.name"
class="t-condition-input__name"
type="text"
@change="persist"
<input v-model="condition.configuration.name"
class="t-condition-input__name"
type="text"
@change="persist"
>
</span>
<span class="c-cdef__label">Output</span>
<span class="c-cdef__controls">
<span class="c-cdef__control">
<select
v-model="selectedOutputSelection"
@change="setOutputValue"
<select v-model="selectedOutputSelection"
@change="setOutputValue"
>
<option
v-for="option in outputOptions"
:key="option"
:value="option"
<option v-for="option in outputOptions"
:key="option"
:value="option"
>
{{ initCap(option) }}
</option>
</select>
</span>
<span class="c-cdef__control">
<input
v-if="selectedOutputSelection === outputOptions[2]"
v-model="condition.configuration.output"
class="t-condition-name-input"
type="text"
@change="persist"
<input v-if="selectedOutputSelection === outputOptions[2]"
v-model="condition.configuration.output"
class="t-condition-name-input"
type="text"
@change="persist"
>
</span>
</span>
<div
v-if="!condition.isDefault"
class="c-cdef__match-and-criteria"
<div v-if="!condition.isDefault"
class="c-cdef__match-and-criteria"
>
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Match</span>
<span class="c-cdef__controls">
<select
v-model="condition.configuration.trigger"
@change="persist"
<select v-model="condition.configuration.trigger"
@change="persist"
>
<option
v-for="option in triggers"
:key="option.value"
:value="option.value"
<option v-for="option in triggers"
:key="option.value"
:value="option.value"
> {{ option.label }}</option>
</select>
</span>
<template v-if="telemetry.length || condition.configuration.criteria.length">
<div
v-for="(criterion, index) in condition.configuration.criteria"
:key="criterion.id"
class="c-cdef__criteria"
<div v-for="(criterion, index) in condition.configuration.criteria"
:key="criterion.id"
class="c-cdef__criteria"
>
<Criterion
:telemetry="telemetry"
:criterion="criterion"
:index="index"
:trigger="condition.configuration.trigger"
:is-default="condition.configuration.criteria.length === 1"
@persist="persist"
<Criterion :telemetry="telemetry"
:criterion="criterion"
:index="index"
:trigger="condition.configuration.trigger"
:is-default="condition.configuration.criteria.length === 1"
@persist="persist"
/>
<div class="c-cdef__criteria__buttons">
<button
class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
title="Duplicate this criteria"
@click="cloneCriterion(index)"
<button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
title="Duplicate this criteria"
@click="cloneCriterion(index)"
></button>
<button
v-if="!(condition.configuration.criteria.length === 1)"
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
title="Delete this criteria"
@click="removeCriterion(index)"
<button v-if="!(condition.configuration.criteria.length === 1)"
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
title="Delete this criteria"
@click="removeCriterion(index)"
></button>
</div>
</div>
</template>
<div class="c-cdef__separator c-row-separator"></div>
<div
class="c-cdef__controls"
:disabled="!telemetry.length"
<div class="c-cdef__controls"
:disabled="!telemetry.length"
>
<button
class="c-cdef__add-criteria-button c-button c-button--labeled icon-plus"
@@ -186,10 +166,9 @@
</div>
</div>
</div>
<div
v-else
class="c-condition c-condition--browse"
:class="{'is-current': condition.id === currentConditionId}"
<div v-else
class="c-condition c-condition--browse"
:class="{'is-current': condition.id === currentConditionId}"
>
<!-- Browse view -->
<div class="c-condition__header">
@@ -201,9 +180,8 @@
</span>
</div>
<div class="c-condition__summary">
<condition-description
:show-label="false"
:condition="condition"
<condition-description :show-label="false"
:condition="condition"
/>
</div>
</div>

View File

@@ -21,9 +21,8 @@
*****************************************************************************/
<template>
<section
id="conditionCollection"
:class="{ 'is-expanded': expanded }"
<section id="conditionCollection"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__header c-section__header">
<span
@@ -33,14 +32,12 @@
></span>
<div class="c-cs__header-label c-section__label">Conditions</div>
</div>
<div
v-if="expanded"
class="c-cs__content"
<div v-if="expanded"
class="c-cs__content"
>
<div
v-show="isEditing"
class="hint"
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
<div v-show="isEditing"
class="hint"
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
>
<template v-if="!telemetryObjs.length">Drag telemetry into this Condition Set to configure Conditions and add criteria.</template>
<template v-else>The first condition to match is the one that is applied. Drag conditions to reorder.</template>
@@ -55,26 +52,24 @@
<span class="c-cs-button__label">Add Condition</span>
</button>
<div
class="c-cs__conditions-h"
:class="{ 'is-active-dragging': isDragging }"
<div class="c-cs__conditions-h"
:class="{ 'is-active-dragging': isDragging }"
>
<Condition
v-for="(condition, index) in conditionCollection"
:key="condition.id"
:condition="condition"
:current-condition-id="currentConditionId"
:condition-index="index"
:telemetry="telemetryObjs"
:is-editing="isEditing"
:move-index="moveIndex"
:is-dragging="isDragging"
@updateCondition="updateCondition"
@removeCondition="removeCondition"
@cloneCondition="cloneCondition"
@setMoveIndex="setMoveIndex"
@dragComplete="dragComplete"
@dropCondition="dropCondition"
<Condition v-for="(condition, index) in conditionCollection"
:key="condition.id"
:condition="condition"
:current-condition-id="currentConditionId"
:condition-index="index"
:telemetry="telemetryObjs"
:is-editing="isEditing"
:move-index="moveIndex"
:is-dragging="isDragging"
@updateCondition="updateCondition"
@removeCondition="removeCondition"
@cloneCondition="cloneCondition"
@setMoveIndex="setMoveIndex"
@dragComplete="dragComplete"
@dropCondition="dropCondition"
/>
</div>
</div>

View File

@@ -22,21 +22,18 @@
<template>
<div class="c-style__condition-desc">
<span
v-if="showLabel && condition"
class="c-style__condition-desc__name c-condition__name"
<span v-if="showLabel && condition"
class="c-style__condition-desc__name c-condition__name"
>
{{ condition.configuration.name }}
</span>
<span
v-if="!condition.isDefault"
class="c-style__condition-desc__text"
<span v-if="!condition.isDefault"
class="c-style__condition-desc__text"
>
{{ description }}
</span>
<span
v-else
class="c-style__condition-desc__text"
<span v-else
class="c-style__condition-desc__text"
>
Match if no other condition is matched
</span>

View File

@@ -21,14 +21,12 @@
*****************************************************************************/
<template>
<div
v-if="conditionErrors.length"
class="c-condition__errors"
<div v-if="conditionErrors.length"
class="c-condition__errors"
>
<div
v-for="(error, index) in conditionErrors"
:key="index"
class="u-alert u-alert--block u-alert--with-icon"
<div v-for="(error, index) in conditionErrors"
:key="index"
class="u-alert u-alert--block u-alert--with-icon"
>{{ error.message.errorText }} {{ error.additionalInfo }}
</div>
</div>

View File

@@ -36,20 +36,18 @@
</div>
</section>
<div class="c-cs__test-data-and-conditions-w">
<TestData
class="c-cs__test-data"
:is-editing="isEditing"
:test-data="testData"
:telemetry="telemetryObjs"
@updateTestData="updateTestData"
<TestData class="c-cs__test-data"
:is-editing="isEditing"
:test-data="testData"
:telemetry="telemetryObjs"
@updateTestData="updateTestData"
/>
<ConditionCollection
class="c-cs__conditions"
:is-editing="isEditing"
:test-data="testData"
@conditionSetResultUpdated="updateCurrentOutput"
@updateDefaultOutput="updateDefaultOutput"
@telemetryUpdated="updateTelemetry"
<ConditionCollection class="c-cs__conditions"
:is-editing="isEditing"
:test-data="testData"
@conditionSetResultUpdated="updateCurrentOutput"
@updateDefaultOutput="updateDefaultOutput"
@telemetryUpdated="updateTelemetry"
/>
</div>
</div>

View File

@@ -26,89 +26,76 @@
<span class="c-cdef__label">{{ setRowLabel }}</span>
<span class="c-cdef__controls">
<span class="c-cdef__control">
<select
ref="telemetrySelect"
v-model="criterion.telemetry"
@change="updateMetadataOptions"
<select ref="telemetrySelect"
v-model="criterion.telemetry"
@change="updateMetadataOptions"
>
<option value="">- Select Telemetry -</option>
<option value="all">all telemetry</option>
<option value="any">any telemetry</option>
<option
v-for="telemetryOption in telemetry"
:key="telemetryOption.identifier.key"
:value="telemetryOption.identifier"
<option v-for="telemetryOption in telemetry"
:key="telemetryOption.identifier.key"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
</option>
</select>
</span>
<span
v-if="criterion.telemetry"
class="c-cdef__control"
<span v-if="criterion.telemetry"
class="c-cdef__control"
>
<select
ref="metadataSelect"
v-model="criterion.metadata"
@change="updateOperations"
<select ref="metadataSelect"
v-model="criterion.metadata"
@change="updateOperations"
>
<option value="">- Select Field -</option>
<option
v-for="option in telemetryMetadataOptions"
:key="option.key"
:value="option.key"
<option v-for="option in telemetryMetadataOptions"
:key="option.key"
:value="option.key"
>
{{ option.name }}
</option>
<option value="dataReceived">any data received</option>
</select>
</span>
<span
v-if="criterion.telemetry && criterion.metadata"
class="c-cdef__control"
<span v-if="criterion.telemetry && criterion.metadata"
class="c-cdef__control"
>
<select
v-model="criterion.operation"
@change="updateInputVisibilityAndValues"
<select v-model="criterion.operation"
@change="updateInputVisibilityAndValues"
>
<option value="">- Select Comparison -</option>
<option
v-for="option in filteredOps"
:key="option.name"
:value="option.name"
<option v-for="option in filteredOps"
:key="option.name"
:value="option.name"
>
{{ option.text }}
</option>
</select>
<template v-if="!enumerations.length">
<span
v-for="(item, inputIndex) in inputCount"
:key="inputIndex"
class="c-cdef__control__inputs"
<span v-for="(item, inputIndex) in inputCount"
:key="inputIndex"
class="c-cdef__control__inputs"
>
<input
v-model="criterion.input[inputIndex]"
class="c-cdef__control__input"
:type="setInputType"
@change="persist"
<input v-model="criterion.input[inputIndex]"
class="c-cdef__control__input"
:type="setInputType"
@change="persist"
>
<span v-if="inputIndex < inputCount-1">and</span>
</span>
<span v-if="criterion.metadata === 'dataReceived'">seconds</span>
</template>
<span v-else>
<span
v-if="inputCount && criterion.operation"
class="c-cdef__control"
<span v-if="inputCount && criterion.operation"
class="c-cdef__control"
>
<select
v-model="criterion.input[0]"
@change="persist"
<select v-model="criterion.input[0]"
@change="persist"
>
<option
v-for="option in enumerations"
:key="option.string"
:value="option.value.toString()"
<option v-for="option in enumerations"
:key="option.string"
:value="option.value.toString()"
>
{{ option.string }}
</option>

View File

@@ -21,10 +21,9 @@
*****************************************************************************/
<template>
<section
v-show="isEditing"
id="test-data"
:class="{ 'is-expanded': expanded }"
<section v-show="isEditing"
id="test-data"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__header c-section__header">
<span
@@ -34,13 +33,11 @@
></span>
<div class="c-cs__header-label c-section__label">Test Data</div>
</div>
<div
v-if="expanded"
class="c-cs__content"
<div v-if="expanded"
class="c-cs__content"
>
<div
class="c-cs__test-data__controls c-cdef__controls"
:disabled="!telemetry.length"
<div class="c-cs__test-data__controls c-cdef__controls"
:disabled="!telemetry.length"
>
<label class="c-toggle-switch">
<input
@@ -53,69 +50,59 @@
</label>
</div>
<div class="c-cs-tests">
<span
v-for="(testInput, tIndex) in testInputs"
:key="tIndex"
class="c-test-datum c-cs-test"
<span v-for="(testInput, tIndex) in testInputs"
:key="tIndex"
class="c-test-datum c-cs-test"
>
<span class="c-cs-test__label">Set</span>
<span class="c-cs-test__controls">
<span class="c-cdef__control">
<select
v-model="testInput.telemetry"
@change="updateMetadata(testInput)"
<select v-model="testInput.telemetry"
@change="updateMetadata(testInput)"
>
<option value="">- Select Telemetry -</option>
<option
v-for="(telemetryOption, index) in telemetry"
:key="index"
:value="telemetryOption.identifier"
<option v-for="(telemetryOption, index) in telemetry"
:key="index"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
</option>
</select>
</span>
<span
v-if="testInput.telemetry"
class="c-cdef__control"
<span v-if="testInput.telemetry"
class="c-cdef__control"
>
<select
v-model="testInput.metadata"
@change="updateTestData"
<select v-model="testInput.metadata"
@change="updateTestData"
>
<option value="">- Select Field -</option>
<option
v-for="(option, index) in telemetryMetadataOptions[getId(testInput.telemetry)]"
:key="index"
:value="option.key"
<option v-for="(option, index) in telemetryMetadataOptions[getId(testInput.telemetry)]"
:key="index"
:value="option.key"
>
{{ option.name }}
</option>
</select>
</span>
<span
v-if="testInput.metadata"
class="c-cdef__control__inputs"
<span v-if="testInput.metadata"
class="c-cdef__control__inputs"
>
<input
v-model="testInput.value"
placeholder="Enter test input"
type="text"
class="c-cdef__control__input"
@change="updateTestData"
<input v-model="testInput.value"
placeholder="Enter test input"
type="text"
class="c-cdef__control__input"
@change="updateTestData"
>
</span>
</span>
<div class="c-cs-test__buttons">
<button
class="c-click-icon c-test-data__duplicate-button icon-duplicate"
title="Duplicate this test datum"
@click="addTestInput(testInput)"
<button class="c-click-icon c-test-data__duplicate-button icon-duplicate"
title="Duplicate this test datum"
@click="addTestInput(testInput)"
></button>
<button
class="c-click-icon c-test-data__delete-button icon-trash"
title="Delete this test datum"
@click="removeTestInput(tIndex)"
<button class="c-click-icon c-test-data__delete-button icon-trash"
title="Delete this test datum"
@click="removeTestInput(tIndex)"
></button>
</div>
</span>

View File

@@ -23,60 +23,52 @@
<template>
<div class="c-style has-local-controls c-toolbar">
<div class="c-style__controls">
<div
:class="[
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
]"
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
class="c-style-thumb"
<div :class="[
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
]"
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
class="c-style-thumb"
>
<span
class="c-style-thumb__text"
:class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
<span class="c-style-thumb__text"
:class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
>
ABC
</span>
</div>
<toolbar-color-picker
v-if="hasProperty(styleItem.style.border)"
class="c-style__toolbar-button--border-color u-menu-to--center"
:options="borderColorOption"
@change="updateStyleValue"
<toolbar-color-picker v-if="hasProperty(styleItem.style.border)"
class="c-style__toolbar-button--border-color u-menu-to--center"
:options="borderColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker
v-if="hasProperty(styleItem.style.backgroundColor)"
class="c-style__toolbar-button--background-color u-menu-to--center"
:options="backgroundColorOption"
@change="updateStyleValue"
<toolbar-color-picker v-if="hasProperty(styleItem.style.backgroundColor)"
class="c-style__toolbar-button--background-color u-menu-to--center"
:options="backgroundColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker
v-if="hasProperty(styleItem.style.color)"
class="c-style__toolbar-button--color u-menu-to--center"
:options="colorOption"
@change="updateStyleValue"
<toolbar-color-picker v-if="hasProperty(styleItem.style.color)"
class="c-style__toolbar-button--color u-menu-to--center"
:options="colorOption"
@change="updateStyleValue"
/>
<toolbar-button
v-if="hasProperty(styleItem.style.imageUrl)"
class="c-style__toolbar-button--image-url"
:options="imageUrlOption"
@change="updateStyleValue"
<toolbar-button v-if="hasProperty(styleItem.style.imageUrl)"
class="c-style__toolbar-button--image-url"
:options="imageUrlOption"
@change="updateStyleValue"
/>
<toolbar-toggle-button
v-if="hasProperty(styleItem.style.isStyleInvisible)"
class="c-style__toolbar-button--toggle-visible"
:options="isStyleInvisibleOption"
@change="updateStyleValue"
<toolbar-toggle-button v-if="hasProperty(styleItem.style.isStyleInvisible)"
class="c-style__toolbar-button--toggle-visible"
:options="isStyleInvisibleOption"
@change="updateStyleValue"
/>
</div>
<!-- Save Styles -->
<toolbar-button
v-if="canSaveStyle"
class="c-style__toolbar-button--save c-local-controls--show-on-hover c-icon-button c-icon-button--major"
:options="saveOptions"
@click="saveItemStyle()"
<toolbar-button v-if="canSaveStyle"
class="c-style__toolbar-button--save c-local-controls--show-on-hover c-icon-button c-icon-button--major"
:options="saveOptions"
@click="saveItemStyle()"
/>
</div>
</template>

View File

@@ -22,9 +22,8 @@
<template>
<div class="c-inspector__styles c-inspect-styles">
<div
v-if="isStaticAndConditionalStyles"
class="c-inspect-styles__mixed-static-and-conditional u-alert u-alert--block u-alert--with-icon"
<div v-if="isStaticAndConditionalStyles"
class="c-inspect-styles__mixed-static-and-conditional u-alert u-alert--block u-alert--with-icon"
>
Your selection includes one or more items that use Conditional Styling. Applying a static style below will replace any Conditional Styling with the new choice.
</div>
@@ -38,18 +37,16 @@
@set-font-property="setFontProperty"
/>
<div class="c-inspect-styles__content">
<div
v-if="staticStyle"
class="c-inspect-styles__style"
<div v-if="staticStyle"
class="c-inspect-styles__style"
>
<StyleEditor
class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="allowEditing"
:mixed-styles="mixedStyles"
:non-specific-font-properties="nonSpecificFontProperties"
@persist="updateStaticStyle"
@save-style="saveStyle"
<StyleEditor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="allowEditing"
:mixed-styles="mixedStyles"
:non-specific-font-properties="nonSpecificFontProperties"
@persist="updateStaticStyle"
@save-style="saveStyle"
/>
</div>
<button
@@ -67,10 +64,9 @@
Conditional Object Styles
</div>
<div class="c-inspect-styles__content c-inspect-styles__condition-set c-inspect-styles__elem">
<a
v-if="conditionSetDomainObject"
class="c-object-label"
@click="navigateOrPreview"
<a v-if="conditionSetDomainObject"
class="c-object-label"
@click="navigateOrPreview"
>
<span class="c-object-label__type-icon icon-conditional"></span>
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
@@ -84,17 +80,15 @@
<span class="c-button__label">Change...</span>
</button>
<button
class="c-click-icon icon-x"
title="Remove conditional styles"
@click="removeConditionSet"
<button class="c-click-icon icon-x"
title="Remove conditional styles"
@click="removeConditionSet"
></button>
</template>
</div>
<div
v-if="isConditionWidget && allowEditing"
class="c-inspect-styles__elem c-inspect-styles__output-label-toggle"
<div v-if="isConditionWidget && allowEditing"
class="c-inspect-styles__elem c-inspect-styles__output-label-toggle"
>
<label class="c-toggle-switch">
<input
@@ -106,9 +100,8 @@
<span class="c-toggle-switch__label">Use Condition Set output as label</span>
</label>
</div>
<div
v-if="isConditionWidget && !allowEditing"
class="c-inspect-styles__elem"
<div v-if="isConditionWidget && !allowEditing"
class="c-inspect-styles__elem"
>
<span class="c-toggle-switch__label">Condition Set output as label:
<span v-if="useConditionSetOutputAsLabel"> Yes</span><span v-else> No</span>
@@ -121,32 +114,27 @@
@set-font-property="setFontProperty"
/>
<div
v-if="conditionsLoaded"
class="c-inspect-styles__conditions"
<div v-if="conditionsLoaded"
class="c-inspect-styles__conditions"
>
<div
v-for="(conditionStyle, index) in conditionalStyles"
:key="index"
class="c-inspect-styles__condition"
:class="{'is-current': conditionStyle.conditionId === selectedConditionId}"
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
<div v-for="(conditionStyle, index) in conditionalStyles"
:key="index"
class="c-inspect-styles__condition"
:class="{'is-current': conditionStyle.conditionId === selectedConditionId}"
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
>
<condition-error
:show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
<condition-error :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<condition-description
:show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
<condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<StyleEditor
class="c-inspect-styles__editor"
:style-item="conditionStyle"
:non-specific-font-properties="nonSpecificFontProperties"
:is-editing="allowEditing"
@persist="updateConditionalStyle"
@save-style="saveStyle"
<StyleEditor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:non-specific-font-properties="nonSpecificFontProperties"
:is-editing="allowEditing"
@persist="updateConditionalStyle"
@save-style="saveStyle"
/>
</div>
</div>

View File

@@ -21,10 +21,9 @@
*****************************************************************************/
<template>
<component
:is="urlDefined ? 'a' : 'span'"
class="c-condition-widget u-style-receiver js-style-receiver"
:href="url"
<component :is="urlDefined ? 'a' : 'span'"
class="c-condition-widget u-style-receiver js-style-receiver"
:href="url"
>
<div class="c-condition-widget__label">
{{ internalDomainObject.conditionalLabel || internalDomainObject.label }}

View File

@@ -37,9 +37,8 @@
:data-font="item.font"
@contextmenu.prevent="showContextMenu"
>
<div
class="is-status__indicator"
:title="`This item is ${status}`"
<div class="is-status__indicator"
:title="`This item is ${status}`"
></div>
<div
v-if="showLabel"

View File

@@ -26,9 +26,8 @@
</div>
</div>
<div class="c-grid-item__controls">
<div
class="is-status__indicator"
:title="`This item is ${status}`"
<div class="is-status__indicator"
:title="`This item is ${status}`"
></div>
<div
class="icon-people"

View File

@@ -17,9 +17,8 @@
class="c-object-label__type-icon c-list-item__name__type-icon"
:class="item.type.cssClass"
>
<span
class="is-status__indicator"
:title="`This item is ${status}`"
<span class="is-status__indicator"
:title="`This item is ${status}`"
></span>
</div>
<div class="c-object-label__name c-list-item__name__name">{{ item.model.name }}</div>

View File

@@ -22,13 +22,12 @@
<template>
<a
class="c-hyperlink"
:class="{
'c-hyperlink--button' : isButton
}"
:target="domainObject.linkTarget"
:href="url"
<a class="c-hyperlink"
:class="{
'c-hyperlink--button' : isButton
}"
:target="domainObject.linkTarget"
:href="url"
>
<span class="c-hyperlink__label">{{ domainObject.displayText }}</span>
</a>

View File

@@ -21,144 +21,124 @@
*****************************************************************************/
<template>
<div
ref="compassRoseWrapper"
class="w-direction-rose"
:class="compassRoseSizingClasses"
@click="toggleLockCompass"
<div ref="compassRoseWrapper"
class="w-direction-rose"
:class="compassRoseSizingClasses"
@click="toggleLockCompass"
>
<svg
ref="compassRoseSvg"
class="c-compass-rose-svg"
viewBox="0 0 100 100"
<svg ref="compassRoseSvg"
class="c-compass-rose-svg"
viewBox="0 0 100 100"
>
<mask
id="mask0"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="100"
height="100"
<mask id="mask0"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="100"
height="100"
>
<circle
cx="50"
cy="50"
r="50"
fill="black"
<circle cx="50"
cy="50"
r="50"
fill="black"
/>
</mask>
<g class="c-cr__compass-wrapper">
<g
class="c-cr__compass-main"
mask="url(#mask0)"
<g class="c-cr__compass-main"
mask="url(#mask0)"
>
<!-- Background and clipped elements -->
<rect
class="c-cr__bg"
width="100"
height="100"
fill="black"
<rect class="c-cr__bg"
width="100"
height="100"
fill="black"
/>
<rect
class="c-cr__edge"
width="100"
height="100"
fill="url(#paint0_radial)"
<rect class="c-cr__edge"
width="100"
height="100"
fill="url(#paint0_radial)"
/>
<rect
v-if="hasSunHeading"
class="c-cr__sun"
width="100"
height="100"
fill="url(#paint1_radial)"
:style="sunHeadingStyle"
<rect v-if="hasSunHeading"
class="c-cr__sun"
width="100"
height="100"
fill="url(#paint1_radial)"
:style="sunHeadingStyle"
/>
<!-- Camera FOV -->
<mask
id="mask2"
class="c-cr__cam-fov-l-mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="50"
height="100"
<mask id="mask2"
class="c-cr__cam-fov-l-mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="50"
height="100"
>
<rect
width="51"
height="100"
<rect width="51"
height="100"
/>
</mask>
<mask
id="mask1"
class="c-cr__cam-fov-r-mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="50"
y="0"
width="50"
height="100"
<mask id="mask1"
class="c-cr__cam-fov-r-mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="50"
y="0"
width="50"
height="100"
>
<rect
x="49"
width="51"
height="100"
<rect x="49"
width="51"
height="100"
/>
</mask>
<g
class="c-cr__cam-fov"
:style="cameraPanStyle"
<g class="c-cr__cam-fov"
:style="cameraPanStyle"
>
<g mask="url(#mask2)">
<rect
class="c-cr__cam-fov-r"
x="49"
width="51"
height="100"
:style="cameraFOVStyleRightHalf"
<rect class="c-cr__cam-fov-r"
x="49"
width="51"
height="100"
:style="cameraFOVStyleRightHalf"
/>
</g>
<g mask="url(#mask1)">
<rect
class="c-cr__cam-fov-l"
width="51"
height="100"
:style="cameraFOVStyleLeftHalf"
<rect class="c-cr__cam-fov-l"
width="51"
height="100"
:style="cameraFOVStyleLeftHalf"
/>
</g>
</g>
</g>
<!-- Spacecraft body -->
<path
v-if="hasHeading"
class="c-cr__spacecraft-body"
fill-rule="evenodd"
clip-rule="evenodd"
d="M37 49C35.3431 49 34 50.3431 34 52V82C34 83.6569 35.3431 85 37 85H63C64.6569 85 66 83.6569 66 82V52C66 50.3431 64.6569 49 63 49H37ZM50 52L58 60H55V67H45V60H42L50 52Z"
:style="headingStyle"
<path v-if="hasHeading"
class="c-cr__spacecraft-body"
fill-rule="evenodd"
clip-rule="evenodd"
d="M37 49C35.3431 49 34 50.3431 34 52V82C34 83.6569 35.3431 85 37 85H63C64.6569 85 66 83.6569 66 82V52C66 50.3431 64.6569 49 63 49H37ZM50 52L58 60H55V67H45V60H42L50 52Z"
:style="headingStyle"
/>
<!-- NSEW and ticks -->
<g
class="c-cr__nsew"
:style="compassRoseStyle"
<g class="c-cr__nsew"
:style="compassRoseStyle"
>
<g class="c-cr__ticks-major">
<path d="M50 3L43 10H57L50 3Z" />
<path
d="M4 51V49H10V51H4Z"
class="--hide-min"
<path d="M4 51V49H10V51H4Z"
class="--hide-min"
/>
<path
d="M49 96V90H51V96H49Z"
class="--hide-min"
<path d="M49 96V90H51V96H49Z"
class="--hide-min"
/>
<path
d="M90 49V51H96V49H90Z"
class="--hide-min"
<path d="M90 49V51H96V49H90Z"
class="--hide-min"
/>
</g>
<g class="c-cr__ticks-minor --hide-small">
@@ -168,63 +148,53 @@
<path d="M51 10L49 10V4L51 4V10Z" />
</g>
<g class="c-cr__nsew-text">
<path
:style="cardinalTextRotateW"
class="c-cr__nsew-w --hide-small"
d="M56.7418 45.004H54.1378L52.7238 52.312H52.6958L51.2258 45.004H48.7758L47.3058 52.312H47.2778L45.8638 45.004H43.2598L45.9618 55H48.6078L49.9798 48.112H50.0078L51.3798 55H53.9838L56.7418 45.004Z"
<path :style="cardinalTextRotateW"
class="c-cr__nsew-w --hide-small"
d="M56.7418 45.004H54.1378L52.7238 52.312H52.6958L51.2258 45.004H48.7758L47.3058 52.312H47.2778L45.8638 45.004H43.2598L45.9618 55H48.6078L49.9798 48.112H50.0078L51.3798 55H53.9838L56.7418 45.004Z"
/>
<path
:style="cardinalTextRotateE"
class="c-cr__nsew-e --hide-small"
d="M46.104 55H54.21V52.76H48.708V50.856H53.608V48.84H48.708V47.09H54.07V45.004H46.104V55Z"
<path :style="cardinalTextRotateE"
class="c-cr__nsew-e --hide-small"
d="M46.104 55H54.21V52.76H48.708V50.856H53.608V48.84H48.708V47.09H54.07V45.004H46.104V55Z"
/>
<path
:style="cardinalTextRotateS"
class="c-cr__nsew-s --hide-small"
d="M45.6531 51.64C45.6671 54.202 47.6971 55.21 49.9931 55.21C52.1911 55.21 54.3471 54.398 54.3471 51.864C54.3471 50.058 52.8911 49.386 51.4491 48.98C49.9931 48.574 48.5511 48.434 48.5511 47.664C48.5511 47.006 49.2511 46.81 49.8111 46.81C50.6091 46.81 51.4631 47.104 51.4211 48.014H54.0251C54.0111 45.76 52.0091 44.794 50.0211 44.794C48.1451 44.794 45.9471 45.648 45.9471 47.832C45.9471 49.666 47.4451 50.31 48.8731 50.716C50.3151 51.122 51.7431 51.29 51.7431 52.172C51.7431 52.914 50.9311 53.194 50.1471 53.194C49.0411 53.194 48.3131 52.816 48.2571 51.64H45.6531Z"
<path :style="cardinalTextRotateS"
class="c-cr__nsew-s --hide-small"
d="M45.6531 51.64C45.6671 54.202 47.6971 55.21 49.9931 55.21C52.1911 55.21 54.3471 54.398 54.3471 51.864C54.3471 50.058 52.8911 49.386 51.4491 48.98C49.9931 48.574 48.5511 48.434 48.5511 47.664C48.5511 47.006 49.2511 46.81 49.8111 46.81C50.6091 46.81 51.4631 47.104 51.4211 48.014H54.0251C54.0111 45.76 52.0091 44.794 50.0211 44.794C48.1451 44.794 45.9471 45.648 45.9471 47.832C45.9471 49.666 47.4451 50.31 48.8731 50.716C50.3151 51.122 51.7431 51.29 51.7431 52.172C51.7431 52.914 50.9311 53.194 50.1471 53.194C49.0411 53.194 48.3131 52.816 48.2571 51.64H45.6531Z"
/>
<path
:style="cardinalTextRotateN"
class="c-cr__nsew-n"
d="M42.5935 60H46.7935V49.32H46.8415L52.7935 60H57.3775V42.864H53.1775V53.424H53.1295L47.1775 42.864H42.5935V60Z"
<path :style="cardinalTextRotateN"
class="c-cr__nsew-n"
d="M42.5935 60H46.7935V49.32H46.8415L52.7935 60H57.3775V42.864H53.1775V53.424H53.1295L47.1775 42.864H42.5935V60Z"
/>
</g>
</g>
</g>
<defs>
<radialGradient
id="paint0_radial"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(50 50) rotate(90) scale(50)"
<radialGradient id="paint0_radial"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(50 50) rotate(90) scale(50)"
>
<stop
offset="0.751387"
stop-opacity="0"
<stop offset="0.751387"
stop-opacity="0"
/>
<stop
offset="1"
stop-color="white"
<stop offset="1"
stop-color="white"
/>
</radialGradient>
<radialGradient
id="paint1_radial"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(50 -7) rotate(-90) scale(18.5)"
<radialGradient id="paint1_radial"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(50 -7) rotate(-90) scale(18.5)"
>
<stop
offset="0.716377"
stop-color="#FFCC00"
<stop offset="0.716377"
stop-color="#FFCC00"
/>
<stop
offset="1"
stop-color="#FF9900"
stop-opacity="0"
<stop offset="1"
stop-color="#FF9900"
stop-opacity="0"
/>
</radialGradient>
</defs>

View File

@@ -1,273 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover c-image-controls__controls">
<div class="c-image-controls__control c-image-controls__zoom icon-magnify">
<div class="c-button-set c-button-set--strip-h">
<button
class="c-button t-btn-zoom-out icon-minus"
title="Zoom out"
@click="zoomOut"
></button>
<button
class="c-button t-btn-zoom-in icon-plus"
title="Zoom in"
@click="zoomIn"
></button>
</div>
<button
class="c-button t-btn-zoom-lock"
title="Lock current zoom and pan across all images"
:class="{'icon-unlocked': !panZoomLocked, 'icon-lock': panZoomLocked}"
@click="toggleZoomLock"
></button>
<button
class="c-button icon-reset t-btn-zoom-reset"
title="Remove zoom and pan"
@click="handleResetImage"
></button>
<span class="c-image-controls__zoom-factor">x{{ formattedZoomFactor }}</span>
</div>
<div class="c-image-controls__control c-image-controls__brightness-contrast">
<span
class="c-image-controls__sliders"
draggable="true"
@dragstart.stop.prevent
>
<div class="c-image-controls__input icon-brightness">
<input
v-model="filters.contrast"
type="range"
min="0"
max="500"
@change="notifyFiltersChanged"
>
</div>
<div class="c-image-controls__input icon-contrast">
<input
v-model="filters.brightness"
type="range"
min="0"
max="500"
@change="notifyFiltersChanged"
>
</div>
</span>
<span class="t-reset-btn-holder c-imagery__lc__reset-btn c-image-controls__btn-reset">
<button
class="c-icon-link icon-reset t-btn-reset"
@click="handleResetFilters"
></button>
</span>
</div>
</div>
</template>
<script>
import _ from 'lodash';
const DEFAULT_FILTER_VALUES = {
brightness: '100',
contrast: '100'
};
const ZOOM_LIMITS_MAX_DEFAULT = 20;
const ZOOM_LIMITS_MIN_DEFAULT = 1;
const ZOOM_STEP = 1;
const ZOOM_WHEEL_SENSITIVITY_REDUCTION = 0.01;
export default {
inject: ['openmct', 'domainObject'],
props: {
zoomFactor: {
type: Number,
required: true
},
imageUrl: String
},
data() {
return {
altPressed: false,
shiftPressed: false,
metaPressed: false,
panZoomLocked: false,
wheelZooming: false,
filters: {
brightness: 100,
contrast: 100
}
};
},
computed: {
formattedZoomFactor() {
return Number.parseFloat(this.zoomFactor).toPrecision(2);
},
cursorStates() {
const isPannable = this.altPressed && this.zoomFactor > 1;
const showCursorZoomIn = this.metaPressed && !this.shiftPressed;
const showCursorZoomOut = this.metaPressed && this.shiftPressed;
const modifierKeyPressed = Boolean(this.metaPressed || this.shiftPressed || this.altPressed);
return {
isPannable,
showCursorZoomIn,
showCursorZoomOut,
modifierKeyPressed
};
}
},
watch: {
imageUrl(newUrl, oldUrl) {
// reset image pan/zoom if newUrl only if not locked
if (newUrl && !this.panZoomLocked) {
this.$emit('resetImage');
}
},
cursorStates(states) {
this.$emit('cursorsUpdated', states);
}
},
mounted() {
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('keyup', this.handleKeyUp);
this.clearWheelZoom = _.debounce(this.clearWheelZoom, 600);
},
beforeDestroy() {
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
},
methods: {
handleResetImage() {
this.$emit('resetImage');
},
handleUpdatePanZoom(options) {
this.$emit('panZoomUpdated', options);
},
toggleZoomLock() {
this.panZoomLocked = !this.panZoomLocked;
},
notifyFiltersChanged() {
this.$emit('filtersUpdated', this.filters);
},
handleResetFilters() {
this.filters = DEFAULT_FILTER_VALUES;
this.notifyFiltersChanged();
},
limitZoomRange(factor) {
return Math.min(Math.max(ZOOM_LIMITS_MIN_DEFAULT, factor), ZOOM_LIMITS_MAX_DEFAULT);
},
// used to increment the zoom without knowledge of current level
processZoom(increment, userCoordX, userCoordY) {
const newFactor = this.limitZoomRange(this.zoomFactor + increment);
this.zoomImage(newFactor, userCoordX, userCoordY);
},
zoomImage(newScaleFactor, screenClientX, screenClientY) {
if (!(newScaleFactor || Number.isInteger(newScaleFactor))) {
console.error('Scale factor provided is invalid');
return;
}
if (newScaleFactor > ZOOM_LIMITS_MAX_DEFAULT) {
newScaleFactor = ZOOM_LIMITS_MAX_DEFAULT;
}
if (newScaleFactor <= 0 || newScaleFactor <= ZOOM_LIMITS_MIN_DEFAULT) {
return this.handleResetImage();
}
this.handleUpdatePanZoom({
newScaleFactor,
screenClientX,
screenClientY
});
},
wheelZoom(e) {
// only use x,y coordinates on scrolling in
if (this.wheelZooming === false && e.deltaY > 0) {
this.wheelZooming = true;
// grab first x,y coordinates
this.processZoom(e.deltaY * ZOOM_WHEEL_SENSITIVITY_REDUCTION, e.clientX, e.clientY);
} else {
// ignore subsequent event x,y so scroll drift doesn't occur
this.processZoom(e.deltaY * ZOOM_WHEEL_SENSITIVITY_REDUCTION);
}
// debounced method that will only fire after the scroll series is complete
this.clearWheelZoom();
},
/* debounced method so that wheelZooming state will
** remain true through a zoom event series
*/
clearWheelZoom() {
this.wheelZooming = false;
},
handleKeyDown(event) {
if (event.key === 'Alt') {
this.altPressed = true;
}
if (event.metaKey) {
this.metaPressed = true;
}
if (event.shiftKey) {
this.shiftPressed = true;
}
},
handleKeyUp(event) {
if (event.key === 'Alt') {
this.altPressed = false;
}
this.shiftPressed = false;
if (!event.metaKey) {
this.metaPressed = false;
}
},
zoomIn() {
this.processZoom(ZOOM_STEP);
},
zoomOut() {
this.processZoom(-ZOOM_STEP);
},
// attached to onClick listener in ImageryView
handlePanZoomClick(e) {
if (this.altPressed) {
return this.$emit('startPan', e);
}
if (!(this.metaPressed && e.button === 0)) {
return;
}
const newScaleFactor = this.zoomFactor + (this.shiftPressed ? -ZOOM_STEP : ZOOM_STEP);
this.zoomImage(newScaleFactor, e.clientX, e.clientY);
}
}
};
</script>

View File

@@ -21,13 +21,11 @@
-->
<template>
<div
ref="imagery"
class="c-imagery-tsv c-timeline-holder"
<div ref="imagery"
class="c-imagery-tsv c-timeline-holder"
>
<div
ref="imageryHolder"
class="c-imagery-tsv__contents u-contents"
<div ref="imageryHolder"
class="c-imagery-tsv__contents u-contents"
>
</div>
</div>

View File

@@ -29,78 +29,52 @@
@mouseover="focusElement"
>
<div class="c-imagery__main-image-wrapper has-local-controls">
<ImageControls
ref="imageControls"
:zoom-factor="zoomFactor"
:image-url="imageUrl"
@resetImage="resetImage"
@panZoomUpdated="handlePanZoomUpdate"
@filtersUpdated="setFilters"
@cursorsUpdated="setCursorStates"
@startPan="startPan"
/>
<div
ref="imageBG"
class="c-imagery__main-image__bg"
:class="{
'paused unnsynced': isPaused && !isFixed,
'stale': false,
'pannable': cursorStates.isPannable,
'cursor-zoom-in': cursorStates.showCursorZoomIn,
'cursor-zoom-out': cursorStates.showCursorZoomOut
}"
@click="expand"
>
<div
v-if="zoomFactor > 1"
class="c-imagery__hints"
>Alt-drag to pan</div>
<div
ref="focusedImageWrapper"
class="image-wrapper"
:style="{
'width': `${sizedImageWidth}px`,
'height': `${sizedImageHeight}px`
}"
@mousedown="handlePanZoomClick"
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover c-image-controls__controls">
<span class="c-image-controls__sliders"
draggable="true"
@dragstart="startDrag"
>
<img
ref="focusedImage"
class="c-imagery__main-image__image js-imageryView-image "
:src="imageUrl"
:draggable="!isSelectable"
:style="{
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
}"
:data-openmct-image-timestamp="time"
:data-openmct-object-keystring="keyString"
<div class="c-image-controls__slider-wrapper icon-brightness">
<input v-model="filters.brightness"
type="range"
min="0"
max="500"
>
</div>
<div class="c-image-controls__slider-wrapper icon-contrast">
<input v-model="filters.contrast"
type="range"
min="0"
max="500"
>
</div>
</span>
<span class="t-reset-btn-holder c-imagery__lc__reset-btn c-image-controls__btn-reset">
<a class="s-icon-button icon-reset t-btn-reset"
@click="filters={brightness: 100, contrast: 100}"
></a>
</span>
</div>
<div ref="imageBG"
class="c-imagery__main-image__bg"
:class="{'paused unnsynced': isPaused && !isFixed,'stale':false }"
@click="expand"
>
<div class="image-wrapper"
:style="{
'width': `${sizedImageDimensions.width}px`,
'height': `${sizedImageDimensions.height}px`
}"
>
<img ref="focusedImage"
class="c-imagery__main-image__image js-imageryView-image"
:src="imageUrl"
:style="{
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
}"
:data-openmct-image-timestamp="time"
:data-openmct-object-keystring="keyString"
>
<div
v-if="imageUrl"
ref="focusedImageElement"
class="c-imagery__main-image__background-image"
:draggable="!isSelectable"
:style="{
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`,
'background-image':
`${imageUrl ? (
`url(${imageUrl}),
repeating-linear-gradient(
45deg,
transparent,
transparent 4px,
rgba(125,125,125,.2) 4px,
rgba(125,125,125,.2) 8px
)`
) : ''}`,
'transform': `scale(${zoomFactor}) translate(${imageTranslateX}px, ${imageTranslateY}px)`,
'transition': `${!pan && animateZoom ? 'transform 250ms ease-in' : 'initial'}`,
'width': `${sizedImageWidth}px`,
'height': `${sizedImageHeight}px`,
}"
></div>
<Compass
v-if="shouldDisplayCompass"
:compass-rose-sizing-classes="compassRoseSizingClasses"
@@ -111,18 +85,16 @@
</div>
</div>
<button
class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev"
title="Previous image"
:disabled="isPrevDisabled"
@click="prevImage()"
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev"
title="Previous image"
:disabled="isPrevDisabled"
@click="prevImage()"
></button>
<button
class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next"
title="Next image"
:disabled="isNextDisabled"
@click="nextImage()"
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next"
title="Next image"
:disabled="isNextDisabled"
@click="nextImage()"
></button>
<div class="c-imagery__control-bar">
@@ -153,38 +125,34 @@
v-if="!isFixed"
class="c-button icon-pause pause-play"
:class="{'is-paused': isPaused}"
@click="paused(!isPaused)"
@click="paused(!isPaused, 'button')"
></button>
</div>
</div>
</div>
<div
class="c-imagery__thumbs-wrapper"
:class="[
{ 'is-paused': isPaused && !isFixed },
{ 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
]"
<div class="c-imagery__thumbs-wrapper"
:class="[
{ 'is-paused': isPaused && !isFixed },
{ 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
]"
>
<div
ref="thumbsWrapper"
class="c-imagery__thumbs-scroll-area"
@scroll="handleScroll"
>
<div
v-for="(image, index) in imageHistory"
:key="image.url + image.time"
class="c-imagery__thumb c-thumb"
:class="{ selected: focusedImageIndex === index && isPaused }"
@click="thumbnailClicked(index)"
<div v-for="(image, index) in imageHistory"
:key="image.url + image.time"
class="c-imagery__thumb c-thumb"
:class="{ selected: focusedImageIndex === index && isPaused }"
@click="setFocusedImage(index, thumbnailClick)"
>
<a
href=""
:download="image.imageDownloadName"
@click.prevent
<a href=""
:download="image.imageDownloadName"
@click.prevent
>
<img
class="c-thumb__image"
:src="image.url"
<img class="c-thumb__image"
:src="image.url"
>
</a>
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
@@ -201,13 +169,12 @@
</template>
<script>
import eventHelpers from '../lib/eventHelpers';
import _ from 'lodash';
import moment from 'moment';
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
import Compass from './Compass/Compass.vue';
import ImageControls from './ImageControls.vue';
import imageryData from "../../imagery/mixins/imageryData";
const REFRESH_CSS_MS = 500;
@@ -227,12 +194,9 @@ const ARROW_LEFT = 37;
const SCROLL_LATENCY = 250;
const ZOOM_SCALE_DEFAULT = 1;
export default {
components: {
Compass,
ImageControls
Compass
},
mixins: [imageryData],
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
@@ -255,6 +219,10 @@ export default {
timeSystem: timeSystem,
keyString: undefined,
autoScroll: true,
filters: {
brightness: 100,
contrast: 100
},
thumbnailClick: THUMBNAIL_CLICKED,
isPaused: false,
refreshCSS: false,
@@ -266,37 +234,19 @@ export default {
focusedImageNaturalAspectRatio: undefined,
imageContainerWidth: undefined,
imageContainerHeight: undefined,
sizedImageWidth: 0,
sizedImageHeight: 0,
lockCompass: true,
resizingWindow: false,
timeContext: undefined,
zoomFactor: ZOOM_SCALE_DEFAULT,
filters: {
brightness: 100,
contrast: 100
},
cursorStates: {
isPannable: false,
showCursorZoomIn: false,
showCursorZoomOut: false,
modifierKeyPressed: false
},
imageTranslateX: 0,
imageTranslateY: 0,
pan: undefined,
animateZoom: true,
imagePanned: false
timeContext: undefined
};
},
computed: {
compassRoseSizingClasses() {
let compassRoseSizingClasses = '';
if (this.sizedImageWidth < 300) {
if (this.sizedImageDimensions.width < 300) {
compassRoseSizingClasses = '--rose-small --rose-min';
} else if (this.sizedImageWidth < 500) {
} else if (this.sizedImageDimensions.width < 500) {
compassRoseSizingClasses = '--rose-small';
} else if (this.sizedImageWidth > 1000) {
} else if (this.sizedImageDimensions.width > 1000) {
compassRoseSizingClasses = '--rose-max';
}
@@ -365,18 +315,10 @@ export default {
return result;
},
shouldDisplayCompass() {
const imageHeightAndWidth = this.sizedImageHeight !== 0
&& this.sizedImageWidth !== 0;
const display = this.focusedImage !== undefined
return this.focusedImage !== undefined
&& this.focusedImageNaturalAspectRatio !== undefined
&& this.imageContainerWidth !== undefined
&& this.imageContainerHeight !== undefined
&& imageHeightAndWidth
&& this.zoomFactor === 1
&& this.imagePanned !== true;
return display;
&& this.imageContainerHeight !== undefined;
},
isSpacecraftPositionFresh() {
let isFresh = undefined;
@@ -438,6 +380,20 @@ export default {
return isFresh;
},
sizedImageDimensions() {
let sizedImageDimensions = {};
if ((this.imageContainerWidth / this.imageContainerHeight) > this.focusedImageNaturalAspectRatio) {
// container is wider than image
sizedImageDimensions.width = this.imageContainerHeight * this.focusedImageNaturalAspectRatio;
sizedImageDimensions.height = this.imageContainerHeight;
} else {
// container is taller than image
sizedImageDimensions.width = this.imageContainerWidth;
sizedImageDimensions.height = this.imageContainerWidth / this.focusedImageNaturalAspectRatio;
}
return sizedImageDimensions;
},
isFixed() {
let clock;
if (this.timeContext) {
@@ -447,16 +403,6 @@ export default {
}
return clock === undefined;
},
isSelectable() {
return true;
},
sizedImageDimensions() {
return {
width: this.sizedImageWidth,
height: this.sizedImageHeight
};
}
},
watch: {
@@ -465,35 +411,16 @@ export default {
const newSize = newHistory.length;
let imageIndex;
if (this.focusedImageTimestamp !== undefined) {
const foundImageIndex = newHistory.findIndex(img => img.time === this.focusedImageTimestamp);
imageIndex = foundImageIndex > -1
? foundImageIndex
: newSize - 1;
const foundImageIndex = this.imageHistory.findIndex(image => {
return image.time === this.focusedImageTimestamp;
});
imageIndex = foundImageIndex > -1 ? foundImageIndex : newSize - 1;
} else {
imageIndex = newSize > 0
? newSize - 1
: undefined;
imageIndex = newSize > 0 ? newSize - 1 : undefined;
}
this.nextImageIndex = imageIndex;
if (this.previousFocusedImage && newHistory.length) {
const matchIndex = this.matchIndexOfPreviousImage(
this.previousFocusedImage,
newHistory
);
if (matchIndex > -1) {
this.setFocusedImage(matchIndex);
} else {
this.paused();
}
}
if (!this.isPaused) {
this.setFocusedImage(imageIndex);
this.scrollToRight();
}
this.setFocusedImage(imageIndex, false);
this.scrollToRight();
},
deep: true
},
@@ -505,10 +432,6 @@ export default {
}
},
async mounted() {
eventHelpers.extend(this);
this.focusedImageWrapper = this.$refs.focusedImageWrapper;
this.focusedImageElement = this.$refs.focusedImageElement;
//We only need to use this till the user focuses an image manually
if (this.focusedImageTimestamp !== undefined) {
this.isPaused = true;
@@ -549,7 +472,6 @@ export default {
this.thumbWrapperResizeObserver.observe(this.$refs.thumbsWrapper);
}
this.listenTo(this.focusedImageWrapper, 'wheel', this.wheelZoom, this);
},
beforeDestroy() {
this.stopFollowingTimeContext();
@@ -574,9 +496,6 @@ export default {
}
}
}
this.stopListening(this.focusedImageWrapper, 'wheel', this.wheelZoom, this);
},
methods: {
setTimeContext() {
@@ -593,11 +512,6 @@ export default {
}
},
expand() {
// check for modifier keys so it doesnt interfere with the layout
if (this.cursorStates.modifierKeyPressed) {
return;
}
const actionCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
const visibleActions = actionCollection.getVisibleActions();
const viewLargeAction = visibleActions
@@ -703,7 +617,6 @@ export default {
focusElement() {
this.$el.focus();
},
handleScroll() {
const thumbsWrapper = this.$refs.thumbsWrapper;
if (!thumbsWrapper || this.resizingWindow) {
@@ -714,15 +627,20 @@ export default {
const disableScroll = scrollWidth > Math.ceil(scrollLeft + clientWidth);
this.autoScroll = !disableScroll;
},
paused(state) {
this.isPaused = Boolean(state);
paused(state, type) {
this.isPaused = state;
if (!state) {
this.previousFocusedImage = null;
this.setFocusedImage(this.nextImageIndex);
this.autoScroll = true;
this.scrollToRight();
if (type === 'button') {
this.setFocusedImage(this.imageHistory.length - 1);
}
if (this.nextImageIndex) {
this.setFocusedImage(this.nextImageIndex);
delete this.nextImageIndex;
}
this.autoScroll = true;
this.scrollToRight();
},
scrollToFocused() {
const thumbsWrapper = this.$refs.thumbsWrapper;
@@ -761,24 +679,51 @@ export default {
&& x.time === previous.time
));
},
thumbnailClicked(index) {
this.setFocusedImage(index);
this.paused(true);
this.setPreviousFocusedImage(index);
},
setPreviousFocusedImage(index) {
this.focusedImageTimestamp = undefined;
this.previousFocusedImage = this.imageHistory[index]
? JSON.parse(JSON.stringify(this.imageHistory[index]))
: undefined;
},
setFocusedImage(index) {
setFocusedImage(index, thumbnailClick = false) {
let focusedIndex = index;
if (!(Number.isInteger(index) && index > -1)) {
return;
}
this.focusedImageIndex = index;
if (thumbnailClick) {
//We use the props till the user changes what they want to see
this.focusedImageTimestamp = undefined;
//set the previousFocusedImage when a user chooses an image
this.previousFocusedImage = this.imageHistory[focusedIndex] ? JSON.parse(JSON.stringify(this.imageHistory[focusedIndex])) : undefined;
}
if (this.previousFocusedImage) {
// determine if the previous image exists in the new bounds of imageHistory
if (!thumbnailClick) {
const matchIndex = this.matchIndexOfPreviousImage(
this.previousFocusedImage,
this.imageHistory
);
focusedIndex = matchIndex > -1 ? matchIndex : this.imageHistory.length - 1;
}
if (!(this.isPaused || thumbnailClick)
|| focusedIndex === this.imageHistory.length - 1) {
delete this.previousFocusedImage;
}
}
this.focusedImageIndex = focusedIndex;
//TODO: do we even need this anymore?
if (this.isPaused && !thumbnailClick && this.focusedImageTimestamp === undefined) {
this.nextImageIndex = focusedIndex;
//this could happen if bounds changes
if (this.focusedImageIndex > this.imageHistory.length - 1) {
this.focusedImageIndex = focusedIndex;
}
return;
}
if (thumbnailClick && !this.isPaused) {
this.paused(true);
}
},
trackDuration() {
if (this.canTrackDuration) {
@@ -816,7 +761,7 @@ export default {
let index = this.focusedImageIndex;
this.thumbnailClicked(++index);
this.setFocusedImage(++index, THUMBNAIL_CLICKED);
if (index === this.imageHistory.length - 1) {
this.paused(false);
}
@@ -829,50 +774,14 @@ export default {
let index = this.focusedImageIndex;
if (index === this.imageHistory.length - 1) {
this.thumbnailClicked(this.imageHistory.length - 2);
this.setFocusedImage(this.imageHistory.length - 2, THUMBNAIL_CLICKED);
} else {
this.thumbnailClicked(--index);
this.setFocusedImage(--index, THUMBNAIL_CLICKED);
}
},
resetImage() {
this.imagePanned = false;
this.zoomFactor = ZOOM_SCALE_DEFAULT;
this.imageTranslateX = 0;
this.imageTranslateY = 0;
},
handlePanZoomUpdate({ newScaleFactor, screenClientX, screenClientY }) {
if (!this.isPaused) {
this.paused(true);
}
if (!(screenClientX || screenClientY)) {
return this.updatePanZoom(newScaleFactor, 0, 0);
}
// handle mouse events
const imageRect = this.focusedImageWrapper.getBoundingClientRect();
const imageContainerX = screenClientX - imageRect.left;
const imageContainerY = screenClientY - imageRect.top;
const offsetFromCenterX = (imageRect.width / 2) - imageContainerX;
const offsetFromCenterY = (imageRect.height / 2) - imageContainerY;
this.updatePanZoom(newScaleFactor, offsetFromCenterX, offsetFromCenterY);
},
updatePanZoom(newScaleFactor, offsetFromCenterX, offsetFromCenterY) {
const currentScale = this.zoomFactor;
const previousTranslateX = this.imageTranslateX;
const previousTranslateY = this.imageTranslateY;
const offsetXInOriginalScale = offsetFromCenterX / currentScale;
const offsetYInOriginalScale = offsetFromCenterY / currentScale;
const translateX = offsetXInOriginalScale + previousTranslateX;
const translateY = offsetYInOriginalScale + previousTranslateY;
this.imageTranslateX = translateX;
this.imageTranslateY = translateY;
this.zoomFactor = newScaleFactor;
},
handlePanZoomClick(e) {
this.$refs.imageControls.handlePanZoomClick(e);
startDrag(e) {
e.preventDefault();
e.stopPropagation();
},
arrowDownHandler(event) {
let key = event.keyCode;
@@ -935,7 +844,7 @@ export default {
// TODO - should probably cache this
img.addEventListener('load', () => {
this.setSizedImageDimensions();
this.focusedImageNaturalAspectRatio = img.naturalWidth / img.naturalHeight;
}, { once: true });
},
resizeImageContainer() {
@@ -950,21 +859,6 @@ export default {
if (this.$refs.imageBG.clientHeight !== this.imageContainerHeight) {
this.imageContainerHeight = this.$refs.imageBG.clientHeight;
}
this.setSizedImageDimensions();
},
setSizedImageDimensions() {
this.focusedImageNaturalAspectRatio = this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight;
if ((this.imageContainerWidth / this.imageContainerHeight) > this.focusedImageNaturalAspectRatio) {
// container is wider than image
this.sizedImageWidth = this.imageContainerHeight * this.focusedImageNaturalAspectRatio;
this.sizedImageHeight = this.imageContainerHeight;
} else {
// container is taller than image
this.sizedImageWidth = this.imageContainerWidth;
this.sizedImageHeight = this.imageContainerWidth / this.focusedImageNaturalAspectRatio;
}
},
handleThumbWindowResizeStart() {
if (!this.autoScroll) {
@@ -983,73 +877,6 @@ export default {
this.$nextTick(() => {
this.resizingWindow = false;
});
},
// debounced method
clearWheelZoom() {
this.$refs.imageControls.clearWheelZoom();
},
wheelZoom(e) {
e.preventDefault();
if (!this.isPaused) {
this.paused(true);
}
this.$refs.imageControls.wheelZoom(e);
},
startPan(e) {
e.preventDefault();
if (!this.pan && this.zoomFactor > 1) {
this.animateZoom = false;
this.imagePanned = true;
this.pan = {
x: e.clientX,
y: e.clientY
};
this.listenTo(window, 'mouseup', this.onMouseUp, this);
this.listenTo(window, 'mousemove', this.trackMousePosition, this);
}
return false;
},
trackMousePosition(e) {
if (!e.altKey) {
return this.onMouseUp(e);
}
this.updatePan(e);
e.preventDefault();
},
updatePan(e) {
if (!this.pan) {
return;
}
const dX = e.clientX - this.pan.x;
const dY = e.clientY - this.pan.y;
this.pan = {
x: e.clientX,
y: e.clientY
};
this.updatePanZoom(this.zoomFactor, dX, dY);
},
endPan() {
this.pan = undefined;
this.animateZoom = true;
},
onMouseUp(event) {
this.stopListening(window, 'mouseup', this.onMouseUp, this);
this.stopListening(window, 'mousemove', this.trackMousePosition, this);
if (this.pan) {
return this.endPan(event);
}
},
setFilters(filtersObj) {
this.filters = filtersObj;
},
setCursorStates(states) {
this.cursorStates = states;
}
}
};

View File

@@ -18,11 +18,6 @@
flex: 1 1 auto;
}
.image-wrapper {
overflow: visible clip;
background-image: repeating-linear-gradient(45deg, transparent, transparent 4px, rgba(125, 125, 125, 0.2) 4px, rgba(125, 125, 125, 0.2) 8px);
}
&__main-image {
&__bg {
background-color: $colorPlotBg;
@@ -32,46 +27,18 @@
justify-content: center;
flex: 1 1 auto;
height: 0;
overflow: hidden;
&.unnsynced{
@include sUnsynced();
}
&.cursor-zoom-in {
cursor: zoom-in;
}
&.cursor-zoom-out {
cursor: zoom-out;
}
&.pannable {
@include cursorGrab();
}
}
}
&__background-image {
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
&__image {
height: 100%;
width: 100%;
visibility: hidden;
display: contents;
}
}
&__hints {
$m: $interiorMargin;
background: rgba(black, 0.2);
border-radius: $smallCr;
padding: 2px $interiorMargin;
position: absolute;
right: $m;
top: $m;
opacity: 0.9;
z-index: 2;
}
&__control-bar,
&__time {
display: flex;
@@ -210,8 +177,8 @@
z-index: 2;
background: $colorLocalControlOvrBg;
border-radius: $basicCr;
max-width: 250px;
min-width: 170px;
max-width: 200px;
min-width: 70px;
width: 35%;
align-items: center;
padding: $interiorMargin $interiorMarginLg;
@@ -235,7 +202,6 @@
&__lc {
&__reset-btn {
// Span that holds bracket graphics and button
$bc: $scrollbarTrackColorBg;
&:before,
@@ -256,50 +222,18 @@
border-bottom: 1px solid $bc;
margin-top: 2px;
}
.c-icon-link {
color: $colorBtnFg;
}
}
}
}
.c-image-controls {
// Brightness/contrast
&__controls {
display: flex;
align-items: stretch;
flex-direction: column;
> * + * {
margin-top: $interiorMargin;
}
[class*='c-button'] { flex: 0 0 auto; }
}
&__control,
&__input {
// Sliders and reset element
display: flex;
align-items: center;
width: 100%;
&:before {
color: rgba($colorMenuFg, 0.5);
margin-right: $interiorMarginSm;
}
}
&__input {
// A wrapper is needed to add the type icon to left of each control
input[type='range'] {
//width: 100%; // Do we need this?
}
}
&__zoom {
> * + * { margin-left: $interiorMargin; }
margin-right: $interiorMargin; // Need some extra space due to proximity to close button
}
&__sliders {
@@ -312,6 +246,21 @@
}
}
&__slider-wrapper {
// A wrapper is needed to add the type icon to left of each range input
display: flex;
align-items: center;
&:before {
color: rgba($colorMenuFg, 0.5);
margin-right: $interiorMarginSm;
}
input[type='range'] {
width: 100px;
}
}
&__btn-reset {
flex: 0 0 auto;
}

View File

@@ -1,98 +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 () {
const helperFunctions = {
listenTo: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
const listener = {
object: object,
event: event,
callback: callback,
context: context,
_cb: context ? callback.bind(context) : callback
};
if (object.$watch && event.indexOf('change:') === 0) {
const scopePath = event.replace('change:', '');
listener.unlisten = object.$watch(scopePath, listener._cb, true);
} else if (object.$on) {
listener.unlisten = object.$on(event, listener._cb);
} else if (object.addEventListener) {
object.addEventListener(event, listener._cb);
} else {
object.on(event, listener._cb);
}
this._listeningTo.push(listener);
},
stopListening: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
this._listeningTo.filter(function (listener) {
if (object && object !== listener.object) {
return false;
}
if (event && event !== listener.event) {
return false;
}
if (callback && callback !== listener.callback) {
return false;
}
if (context && context !== listener.context) {
return false;
}
return true;
})
.map(function (listener) {
if (listener.unlisten) {
listener.unlisten();
} else if (listener.object.removeEventListener) {
listener.object.removeEventListener(listener.event, listener._cb);
} else {
listener.object.off(listener.event, listener._cb);
}
return listener;
})
.forEach(function (listener) {
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
}, this);
},
extend: function (object) {
object.listenTo = helperFunctions.listenTo;
object.stopListening = helperFunctions.stopListening;
}
};
return helperFunctions;
});

View File

@@ -483,42 +483,6 @@ describe("The Imagery View Layouts", () => {
});
});
});
xit('should change the image zoom factor when using the zoom buttons', async (done) => {
await Vue.nextTick();
let imageSizeBefore;
let imageSizeAfter;
// test clicking the zoom in button
imageSizeBefore = parent.querySelector('.c-imagery_main-image_background-image').getBoundingClientRect();
parent.querySelector('.t-btn-zoom-in').click();
await Vue.nextTick();
imageSizeAfter = parent.querySelector('.c-imagery_main-image_background-image').getBoundingClientRect();
expect(imageSizeAfter.height).toBeGreaterThan(imageSizeBefore.height);
expect(imageSizeAfter.width).toBeGreaterThan(imageSizeBefore.width);
// test clicking the zoom out button
imageSizeBefore = parent.querySelector('.c-imagery_main-image_background-image').getBoundingClientRect();
parent.querySelector('.t-btn-zoom-out').click();
await Vue.nextTick();
imageSizeAfter = parent.querySelector('.c-imagery_main-image_background-image').getBoundingClientRect();
expect(imageSizeAfter.height).toBeLessThan(imageSizeBefore.height);
expect(imageSizeAfter.width).toBeLessThan(imageSizeBefore.width);
done();
});
xit('should reset the zoom factor on the image when clicking the zoom button', async (done) => {
await Vue.nextTick();
// test clicking the zoom reset button
// zoom in to scale up the image dimensions
parent.querySelector('.t-btn-zoom-in').click();
await Vue.nextTick();
let imageSizeBefore = parent.querySelector('.c-imagery_main-image_background-image').getBoundingClientRect();
await Vue.nextTick();
parent.querySelector('.t-btn-zoom-reset').click();
let imageSizeAfter = parent.querySelector('.c-imagery_main-image_background-image').getBoundingClientRect();
expect(imageSizeAfter.height).toBeLessThan(imageSizeBefore.height);
expect(imageSizeAfter.width).toBeLessThan(imageSizeBefore.width);
done();
});
it('clear data action is installed', () => {
expect(clearDataAction).toBeDefined();
});

View File

@@ -23,51 +23,46 @@
<template>
<div class="c-notebook">
<div class="c-notebook__head">
<Search
class="c-notebook__search"
:value="search"
@input="search = $event"
@clear="resetSearch()"
<Search class="c-notebook__search"
:value="search"
@input="search = $event"
@clear="resetSearch()"
/>
</div>
<SearchResults
v-if="search.length"
ref="searchResults"
:domain-object="domainObject"
:results="searchResults"
@changeSectionPage="changeSelectedSection"
@updateEntries="updateEntries"
<SearchResults v-if="search.length"
ref="searchResults"
:domain-object="domainObject"
:results="searchResults"
@changeSectionPage="changeSelectedSection"
@updateEntries="updateEntries"
/>
<div
v-if="!search.length"
class="c-notebook__body"
<div v-if="!search.length"
class="c-notebook__body"
>
<Sidebar
ref="sidebar"
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
:default-page-id="defaultPageId"
:selected-page-id="getSelectedPageId()"
:default-section-id="defaultSectionId"
:selected-section-id="getSelectedSectionId()"
:domain-object="domainObject"
:page-title="domainObject.configuration.pageTitle"
:section-title="domainObject.configuration.sectionTitle"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
@defaultPageDeleted="cleanupDefaultNotebook"
@defaultSectionDeleted="cleanupDefaultNotebook"
@pagesChanged="pagesChanged"
@selectPage="selectPage"
@sectionsChanged="sectionsChanged"
@selectSection="selectSection"
@toggleNav="toggleNav"
<Sidebar ref="sidebar"
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
:default-page-id="defaultPageId"
:selected-page-id="getSelectedPageId()"
:default-section-id="defaultSectionId"
:selected-section-id="getSelectedSectionId()"
:domain-object="domainObject"
:page-title="domainObject.configuration.pageTitle"
:section-title="domainObject.configuration.sectionTitle"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
@defaultPageDeleted="cleanupDefaultNotebook"
@defaultSectionDeleted="cleanupDefaultNotebook"
@pagesChanged="pagesChanged"
@selectPage="selectPage"
@sectionsChanged="sectionsChanged"
@selectSection="selectSection"
@toggleNav="toggleNav"
/>
<div class="c-notebook__page-view">
<div class="c-notebook__page-view__header">
<button
class="c-notebook__toggle-nav-button c-icon-button c-icon-button--major icon-menu-hamburger"
@click="toggleNav"
<button class="c-notebook__toggle-nav-button c-icon-button c-icon-button--major icon-menu-hamburger"
@click="toggleNav"
></button>
<div class="c-notebook__page-view__path c-path">
<span class="c-notebook__path__section c-path__item">
@@ -78,70 +73,59 @@
</span>
</div>
<div class="c-notebook__page-view__controls">
<select
v-model="showTime"
class="c-notebook__controls__time"
<select v-model="showTime"
class="c-notebook__controls__time"
>
<option
value="0"
:selected="showTime === 0"
<option value="0"
:selected="showTime === 0"
>
Show all
</option>
<option
value="1"
:selected="showTime === 1"
<option value="1"
:selected="showTime === 1"
>Last hour</option>
<option
value="8"
:selected="showTime === 8"
<option value="8"
:selected="showTime === 8"
>Last 8 hours</option>
<option
value="24"
:selected="showTime === 24"
<option value="24"
:selected="showTime === 24"
>Last 24 hours</option>
</select>
<select
v-model="defaultSort"
class="c-notebook__controls__time"
<select v-model="defaultSort"
class="c-notebook__controls__time"
>
<option
value="newest"
:selected="defaultSort === 'newest'"
<option value="newest"
:selected="defaultSort === 'newest'"
>Newest first</option>
<option
value="oldest"
:selected="defaultSort === 'oldest'"
<option value="oldest"
:selected="defaultSort === 'oldest'"
>Oldest first</option>
</select>
</div>
</div>
<div
class="c-notebook__drag-area icon-plus"
@click="newEntry()"
@dragover="dragOver"
@drop.capture="dropCapture"
@drop="dropOnEntry($event)"
<div class="c-notebook__drag-area icon-plus"
@click="newEntry()"
@dragover="dragOver"
@drop.capture="dropCapture"
@drop="dropOnEntry($event)"
>
<span class="c-notebook__drag-area__label">
To start a new entry, click here or drag and drop any object
</span>
</div>
<div
v-if="selectedSection && selectedPage"
ref="notebookEntries"
class="c-notebook__entries"
<div v-if="selectedSection && selectedPage"
ref="notebookEntries"
class="c-notebook__entries"
>
<NotebookEntry
v-for="entry in filteredAndSortedEntries"
:key="entry.id"
:entry="entry"
:domain-object="domainObject"
:selected-page="selectedPage"
:selected-section="selectedSection"
:read-only="false"
@deleteEntry="deleteEntry"
@updateEntry="updateEntry"
<NotebookEntry v-for="entry in filteredAndSortedEntries"
:key="entry.id"
:entry="entry"
:domain-object="domainObject"
:selected-page="selectedPage"
:selected-section="selectedSection"
:read-only="false"
@deleteEntry="deleteEntry"
@updateEntry="updateEntry"
/>
</div>
</div>
@@ -454,8 +438,6 @@ export default {
const classList = document.querySelector('body').classList;
const isPhone = Array.from(classList).includes('phone');
const isTablet = Array.from(classList).includes('tablet');
// address in https://github.com/nasa/openmct/issues/4875
// eslint-disable-next-line compat/compat
const isPortrait = window.screen.orientation.type.includes('portrait');
const isInLayout = Boolean(this.$el.closest('.c-so-view'));
const sidebarCoversEntries = (isPhone || (isTablet && isPortrait) || isInLayout);

View File

@@ -1,24 +1,21 @@
<template>
<div class="c-snapshot c-ne__embed">
<div
v-if="embed.snapshot"
class="c-ne__embed__snap-thumb"
@click="openSnapshot()"
<div v-if="embed.snapshot"
class="c-ne__embed__snap-thumb"
@click="openSnapshot()"
>
<img :src="thumbnailImage">
</div>
<div class="c-ne__embed__info">
<div class="c-ne__embed__name">
<a
class="c-ne__embed__link"
:class="embed.cssClass"
@click="changeLocation"
<a class="c-ne__embed__link"
:class="embed.cssClass"
@click="changeLocation"
>{{ embed.name }}</a>
<PopupMenu :popup-menu-items="popupMenuItems" />
</div>
<div
v-if="embed.snapshot"
class="c-ne__embed__time"
<div v-if="embed.snapshot"
class="c-ne__embed__time"
>
{{ createdOn }}
</div>

View File

@@ -21,11 +21,10 @@
*****************************************************************************/
<template>
<div
class="c-notebook__entry c-ne has-local-controls"
@dragover="changeCursor"
@drop.capture="cancelEditMode"
@drop.prevent="dropOnEntry"
<div class="c-notebook__entry c-ne has-local-controls"
@dragover="changeCursor"
@drop.capture="cancelEditMode"
@drop.prevent="dropOnEntry"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time">
@@ -63,31 +62,27 @@
</div>
</template>
<div class="c-snapshots c-ne__embeds">
<NotebookEmbed
v-for="embed in entry.embeds"
:key="embed.id"
:embed="embed"
@removeEmbed="removeEmbed"
@updateEmbed="updateEmbed"
<NotebookEmbed v-for="embed in entry.embeds"
:key="embed.id"
:embed="embed"
@removeEmbed="removeEmbed"
@updateEmbed="updateEmbed"
/>
</div>
</div>
</div>
<div
v-if="!readOnly"
class="c-ne__local-controls--hidden"
<div v-if="!readOnly"
class="c-ne__local-controls--hidden"
>
<button
class="c-icon-button c-icon-button--major icon-trash"
title="Delete this entry"
tabindex="-1"
@click="deleteEntry"
<button class="c-icon-button c-icon-button--major icon-trash"
title="Delete this entry"
tabindex="-1"
@click="deleteEntry"
>
</button>
</div>
<div
v-if="readOnly"
class="c-ne__section-and-page"
<div v-if="readOnly"
class="c-ne__section-and-page"
>
<a
class="c-click-link"

View File

@@ -8,45 +8,39 @@
<div class="c-object-label__name">
Notebook Snapshots
</div>
<div
v-if="snapshots.length"
class="l-browse-bar__object-details"
<div v-if="snapshots.length"
class="l-browse-bar__object-details"
>{{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
</div>
</div>
<PopupMenu
v-if="snapshots.length > 0"
:popup-menu-items="popupMenuItems"
<PopupMenu v-if="snapshots.length > 0"
:popup-menu-items="popupMenuItems"
/>
</div>
</div>
<div class="l-browse-bar__end">
<button
class="c-click-icon c-click-icon--major icon-x"
@click="close"
<button class="c-click-icon c-click-icon--major icon-x"
@click="close"
></button>
</div>
</div><!-- closes l-browse-bar -->
<div class="c-snapshots">
<span
v-for="snapshot in snapshots"
:key="snapshot.embedObject.id"
draggable="true"
@dragstart="startEmbedDrag(snapshot, $event)"
<span v-for="snapshot in snapshots"
:key="snapshot.embedObject.id"
draggable="true"
@dragstart="startEmbedDrag(snapshot, $event)"
>
<NotebookEmbed
ref="notebookEmbed"
:key="snapshot.embedObject.id"
:embed="snapshot.embedObject"
:is-snapshot-container="true"
:remove-action-string="'Delete Snapshot'"
@removeEmbed="removeSnapshot"
<NotebookEmbed ref="notebookEmbed"
:key="snapshot.embedObject.id"
:embed="snapshot.embedObject"
:is-snapshot-container="true"
:remove-action-string="'Delete Snapshot'"
@removeEmbed="removeSnapshot"
/>
</span>
<div
v-if="!snapshots.length > 0"
class="hint"
<div v-if="!snapshots.length > 0"
class="hint"
>
There are no Notebook Snapshots currently.
</div>

View File

@@ -1,12 +1,11 @@
<template>
<div
class="c-indicator c-indicator--clickable icon-camera"
:class="[
{ 's-status-off': snapshotCount === 0 },
{ 's-status-on': snapshotCount > 0 },
{ 's-status-caution': snapshotCount === snapshotMaxCount },
{ 'has-new-snapshot': flashIndicator }
]"
<div class="c-indicator c-indicator--clickable icon-camera"
:class="[
{ 's-status-off': snapshotCount === 0 },
{ 's-status-on': snapshotCount > 0 },
{ 's-status-caution': snapshotCount === snapshotMaxCount },
{ 'has-new-snapshot': flashIndicator }
]"
>
<span class="label c-indicator__label">
{{ indicatorTitle }}

View File

@@ -1,19 +1,17 @@
<template>
<ul class="c-list">
<li
v-for="page in pages"
<li v-for="page in pages"
:key="page.id"
class="c-list__item-h"
>
<Page
ref="pageComponent"
:default-page-id="defaultPageId"
:selected-page-id="selectedPageId"
:page="page"
:page-title="pageTitle"
@deletePage="deletePage"
@renamePage="updatePage"
@selectPage="selectPage"
<Page ref="pageComponent"
:default-page-id="defaultPageId"
:selected-page-id="selectedPageId"
:page="page"
:page-title="pageTitle"
@deletePage="deletePage"
@renamePage="updatePage"
@selectPage="selectPage"
/>
</li>
</ul>

View File

@@ -1,15 +1,13 @@
<template>
<div
class="c-list__item js-list__item"
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultPageId === page.id) }]"
:data-id="page.id"
@click="selectPage"
<div class="c-list__item js-list__item"
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultPageId === page.id) }]"
:data-id="page.id"
@click="selectPage"
>
<span
class="c-list__item__name js-list__item__name"
:data-id="page.id"
@keydown.enter="updateName"
@blur="updateName"
<span class="c-list__item__name js-list__item__name"
:data-id="page.id"
@keydown.enter="updateName"
@blur="updateName"
>{{ page.name.length ? page.name : `Unnamed ${pageTitle}` }}</span>
<PopupMenu :popup-menu-items="popupMenuItems" />
</div>

View File

@@ -24,17 +24,16 @@
<div class="c-notebook__search-results">
<div class="c-notebook__search-results__header">Search Results ({{ results.length }})</div>
<div class="c-notebook__entries">
<NotebookEntry
v-for="(result, index) in results"
:key="index"
:domain-object="domainObject"
:result="result"
:entry="result.entry"
:read-only="true"
:selected-page="result.page"
:selected-section="result.section"
@changeSectionPage="changeSectionPage"
@updateEntries="updateEntries"
<NotebookEntry v-for="(result, index) in results"
:key="index"
:domain-object="domainObject"
:result="result"
:entry="result.entry"
:read-only="true"
:selected-page="result.page"
:selected-section="result.section"
@changeSectionPage="changeSectionPage"
@updateEntries="updateEntries"
/>
</div>
</div>

View File

@@ -1,19 +1,17 @@
<template>
<ul class="c-list">
<li
v-for="section in sections"
<li v-for="section in sections"
:key="section.id"
class="c-list__item-h"
>
<NotebookSection
ref="sectionComponent"
:default-section-id="defaultSectionId"
:selected-section-id="selectedSectionId"
:section="section"
:section-title="sectionTitle"
@deleteSection="deleteSection"
@renameSection="updateSection"
@selectSection="selectSection"
<NotebookSection ref="sectionComponent"
:default-section-id="defaultSectionId"
:selected-section-id="selectedSectionId"
:section="section"
:section-title="sectionTitle"
@deleteSection="deleteSection"
@renameSection="updateSection"
@selectSection="selectSection"
/>
</li>
</ul>

View File

@@ -1,15 +1,13 @@
<template>
<div
class="c-list__item js-list__item"
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultSectionId === section.id) }]"
:data-id="section.id"
@click="selectSection"
<div class="c-list__item js-list__item"
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultSectionId === section.id) }]"
:data-id="section.id"
@click="selectSection"
>
<span
class="c-list__item__name js-list__item__name"
:data-id="section.id"
@keydown.enter="updateName"
@blur="updateName"
<span class="c-list__item__name js-list__item__name"
:data-id="section.id"
@keydown.enter="updateName"
@blur="updateName"
>{{ section.name.length ? section.name : `Unnamed ${sectionTitle}` }}</span>
<PopupMenu :popup-menu-items="popupMenuItems" />
</div>

View File

@@ -7,23 +7,21 @@
</div>
</div>
<div class="c-sidebar__contents-and-controls">
<button
class="c-list-button"
@click="addSection"
<button class="c-list-button"
@click="addSection"
>
<span class="c-button c-list-button__button icon-plus"></span>
<span class="c-list-button__label">Add {{ sectionTitle }}</span>
</button>
<SectionCollection
class="c-sidebar__contents"
:default-section-id="defaultSectionId"
:selected-section-id="selectedSectionId"
:domain-object="domainObject"
:sections="sections"
:section-title="sectionTitle"
@defaultSectionDeleted="defaultSectionDeleted"
@updateSection="sectionsChanged"
@selectSection="selectSection"
<SectionCollection class="c-sidebar__contents"
:default-section-id="defaultSectionId"
:selected-section-id="selectedSectionId"
:domain-object="domainObject"
:sections="sections"
:section-title="sectionTitle"
@defaultSectionDeleted="defaultSectionDeleted"
@updateSection="sectionsChanged"
@selectSection="selectSection"
/>
</div>
</div>
@@ -32,34 +30,31 @@
<div class="c-sidebar__header">
<span class="c-sidebar__header-label">{{ pageTitle }}</span>
</div>
<button
class="c-click-icon c-click-icon--major icon-x-in-circle"
@click="toggleNav"
<button class="c-click-icon c-click-icon--major icon-x-in-circle"
@click="toggleNav"
></button>
</div>
<div class="c-sidebar__contents-and-controls">
<button
class="c-list-button"
@click="addPage"
<button class="c-list-button"
@click="addPage"
>
<span class="c-button c-list-button__button icon-plus"></span>
<span class="c-list-button__label">Add {{ pageTitle }}</span>
</button>
<PageCollection
ref="pageCollection"
class="c-sidebar__contents"
:default-page-id="defaultPageId"
:selected-page-id="selectedPageId"
:domain-object="domainObject"
:pages="pages"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
:page-title="pageTitle"
@defaultPageDeleted="defaultPageDeleted"
@toggleNav="toggleNav"
@updatePage="pagesChanged"
@selectPage="selectPage"
<PageCollection ref="pageCollection"
class="c-sidebar__contents"
:default-page-id="defaultPageId"
:selected-page-id="selectedPageId"
:domain-object="domainObject"
:pages="pages"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
:page-title="pageTitle"
@defaultPageDeleted="defaultPageDeleted"
@toggleNav="toggleNav"
@updatePage="pagesChanged"
@selectPage="selectPage"
/>
</div>
</div>

View File

@@ -21,9 +21,8 @@
-->
<template>
<div
ref="plan"
class="c-plan c-timeline-holder"
<div ref="plan"
class="c-plan c-timeline-holder"
>
<template v-if="viewBounds && !options.compact">
<swim-lane>
@@ -37,9 +36,8 @@
/>
</swim-lane>
</template>
<div
ref="planHolder"
class="c-plan__contents u-contents"
<div ref="planHolder"
class="c-plan__contents u-contents"
>
</div>
</div>

View File

@@ -21,11 +21,10 @@
-->
<template>
<div class="c-inspector__properties c-inspect-properties">
<plan-activity-view
v-for="activity in activities"
:key="activity.id"
:activity="activity"
:heading="heading"
<plan-activity-view v-for="activity in activities"
:key="activity.id"
:activity="activity"
:heading="heading"
/>
</div>
</template>

View File

@@ -21,21 +21,18 @@
-->
<template>
<div
v-if="timeProperties.length"
class="u-contents"
<div v-if="timeProperties.length"
class="u-contents"
>
<div class="c-inspect-properties__header">
{{ heading }}
</div>
<ul
v-for="timeProperty in timeProperties"
<ul v-for="timeProperty in timeProperties"
:key="timeProperty.id"
class="c-inspect-properties__section"
>
<activity-property
:label="timeProperty.label"
:value="timeProperty.value"
<activity-property :label="timeProperty.label"
:value="timeProperty.value"
/>
</ul>
</div>

View File

@@ -20,158 +20,132 @@
at runtime from the About dialog for additional information.
-->
<template>
<div
v-if="loaded"
class="gl-plot"
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
<div v-if="loaded"
class="gl-plot"
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
>
<plot-legend
:cursor-locked="!!lockHighlightPoint"
:series="seriesModels"
:highlights="highlights"
:legend="legend"
@legendHoverChanged="legendHoverChanged"
<plot-legend :cursor-locked="!!lockHighlightPoint"
:series="seriesModels"
:highlights="highlights"
:legend="legend"
@legendHoverChanged="legendHoverChanged"
/>
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
<y-axis
v-if="seriesModels.length > 0"
:tick-width="tickWidth"
:single-series="seriesModels.length === 1"
:series-model="seriesModels[0]"
:style="{
left: (plotWidth - tickWidth) + 'px'
}"
@yKeyChanged="setYAxisKey"
@tickWidthChanged="onTickWidthChange"
<y-axis v-if="seriesModels.length > 0"
:tick-width="tickWidth"
:single-series="seriesModels.length === 1"
:series-model="seriesModels[0]"
:style="{
left: (plotWidth - tickWidth) + 'px'
}"
@yKeyChanged="setYAxisKey"
@tickWidthChanged="onTickWidthChange"
/>
<div
class="gl-plot-wrapper-display-area-and-x-axis"
:style="{
left: (plotWidth + 20) + 'px'
}"
<div class="gl-plot-wrapper-display-area-and-x-axis"
:style="{
left: (plotWidth + 20) + 'px'
}"
>
<div class="gl-plot-display-area has-local-controls has-cursor-guides">
<div class="l-state-indicators">
<span
class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
></span>
</div>
<mct-ticks
v-show="gridLines && !options.compact"
:axis-type="'xAxis'"
:position="'right'"
@plotTickWidth="onTickWidthChange"
<mct-ticks v-show="gridLines && !options.compact"
:axis-type="'xAxis'"
:position="'right'"
@plotTickWidth="onTickWidthChange"
/>
<mct-ticks
v-show="gridLines"
:axis-type="'yAxis'"
:position="'bottom'"
@plotTickWidth="onTickWidthChange"
<mct-ticks v-show="gridLines"
:axis-type="'yAxis'"
:position="'bottom'"
@plotTickWidth="onTickWidthChange"
/>
<div
ref="chartContainer"
class="gl-plot-chart-wrapper"
:class="[
{ 'alt-pressed': altPressed },
]"
<div ref="chartContainer"
class="gl-plot-chart-wrapper"
>
<mct-chart
:rectangles="rectangles"
:highlights="highlights"
:show-limit-line-labels="showLimitLineLabels"
@plotReinitializeCanvas="initCanvas"
<mct-chart :rectangles="rectangles"
:highlights="highlights"
:show-limit-line-labels="showLimitLineLabels"
@plotReinitializeCanvas="initCanvas"
/>
</div>
<div class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover">
<div
v-if="!options.compact"
class="c-button-set c-button-set--strip-h js-zoom"
<div v-if="!options.compact"
class="c-button-set c-button-set--strip-h js-zoom"
>
<button
class="c-button icon-minus"
title="Zoom out"
@click="zoom('out', 0.2)"
<button class="c-button icon-minus"
title="Zoom out"
@click="zoom('out', 0.2)"
>
</button>
<button
class="c-button icon-plus"
title="Zoom in"
@click="zoom('in', 0.2)"
<button class="c-button icon-plus"
title="Zoom in"
@click="zoom('in', 0.2)"
>
</button>
</div>
<div
v-if="plotHistory.length && !options.compact"
class="c-button-set c-button-set--strip-h js-pan"
<div v-if="plotHistory.length && !options.compact"
class="c-button-set c-button-set--strip-h js-pan"
>
<button
class="c-button icon-arrow-left"
title="Restore previous pan/zoom"
@click="back()"
<button class="c-button icon-arrow-left"
title="Restore previous pan/zoom"
@click="back()"
>
</button>
<button
class="c-button icon-reset"
title="Reset pan/zoom"
@click="clear()"
<button class="c-button icon-reset"
title="Reset pan/zoom"
@click="clear()"
>
</button>
</div>
<div
v-if="isRealTime && !options.compact"
class="c-button-set c-button-set--strip-h js-pause"
<div v-if="isRealTime && !options.compact"
class="c-button-set c-button-set--strip-h js-pause"
>
<button
v-if="!isFrozen"
class="c-button icon-pause"
title="Pause incoming real-time data"
@click="pause()"
<button v-if="!isFrozen"
class="c-button icon-pause"
title="Pause incoming real-time data"
@click="pause()"
>
</button>
<button
v-if="isFrozen"
class="c-button icon-arrow-right pause-play is-paused"
title="Resume displaying real-time data"
@click="play()"
<button v-if="isFrozen"
class="c-button icon-arrow-right pause-play is-paused"
title="Resume displaying real-time data"
@click="play()"
>
</button>
</div>
<div
v-if="isTimeOutOfSync || isFrozen"
class="c-button-set c-button-set--strip-h"
<div v-if="isTimeOutOfSync || isFrozen"
class="c-button-set c-button-set--strip-h"
>
<button
class="c-button icon-clock"
title="Synchronize Time Conductor"
@click="showSynchronizeDialog()"
<button class="c-button icon-clock"
title="Synchronize Time Conductor"
@click="showSynchronizeDialog()"
>
</button>
</div>
</div>
<!--Cursor guides-->
<div
v-show="cursorGuide"
ref="cursorGuideVertical"
class="c-cursor-guide--v js-cursor-guide--v"
<div v-show="cursorGuide"
ref="cursorGuideVertical"
class="c-cursor-guide--v js-cursor-guide--v"
>
</div>
<div
v-show="cursorGuide"
ref="cursorGuideHorizontal"
class="c-cursor-guide--h js-cursor-guide--h"
<div v-show="cursorGuide"
ref="cursorGuideHorizontal"
class="c-cursor-guide--h js-cursor-guide--h"
>
</div>
</div>
<x-axis
v-if="seriesModels.length > 0 && !options.compact"
:series-model="seriesModels[0]"
<x-axis v-if="seriesModels.length > 0 && !options.compact"
:series-model="seriesModels[0]"
/>
</div>
@@ -233,7 +207,6 @@ export default {
},
data() {
return {
altPressed: false,
highlights: [],
lockHighlightPoint: false,
tickWidth: 0,
@@ -272,8 +245,6 @@ export default {
}
},
mounted() {
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('keyup', this.handleKeyUp);
eventHelpers.extend(this);
this.updateRealTime = this.updateRealTime.bind(this);
this.updateDisplayBounds = this.updateDisplayBounds.bind(this);
@@ -305,21 +276,9 @@ export default {
},
beforeDestroy() {
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
this.destroy();
},
methods: {
handleKeyDown(event) {
if (event.key === 'Alt') {
this.altPressed = true;
}
},
handleKeyUp(event) {
if (event.key === 'Alt') {
this.altPressed = false;
}
},
setTimeContext() {
this.stopFollowingTimeContext();
@@ -661,7 +620,7 @@ export default {
this.positionOverElement = {
x: event.clientX - this.chartElementBounds.left,
y: this.chartElementBounds.height
- (event.clientY - this.chartElementBounds.top)
- (event.clientY - this.chartElementBounds.top)
};
this.positionOverPlot = {

View File

@@ -21,60 +21,53 @@
-->
<template>
<div
ref="tickContainer"
class="u-contents js-ticks"
<div ref="tickContainer"
class="u-contents js-ticks"
>
<div
v-if="position === 'left'"
class="gl-plot-tick-wrapper"
<div v-if="position === 'left'"
class="gl-plot-tick-wrapper"
>
<div
v-for="tick in ticks"
:key="tick.value"
class="gl-plot-tick gl-plot-x-tick-label"
:style="{
left: (100 * (tick.value - min) / interval) + '%'
}"
:title="tick.fullText || tick.text"
<div v-for="tick in ticks"
:key="tick.value"
class="gl-plot-tick gl-plot-x-tick-label"
:style="{
left: (100 * (tick.value - min) / interval) + '%'
}"
:title="tick.fullText || tick.text"
>
{{ tick.text }}
</div>
</div>
<div
v-if="position === 'top'"
class="gl-plot-tick-wrapper"
<div v-if="position === 'top'"
class="gl-plot-tick-wrapper"
>
<div
v-for="tick in ticks"
:key="tick.value"
class="gl-plot-tick gl-plot-y-tick-label"
:style="{ top: (100 * (max - tick.value) / interval) + '%' }"
:title="tick.fullText || tick.text"
style="margin-top: -0.50em; direction: ltr;"
<div v-for="tick in ticks"
:key="tick.value"
class="gl-plot-tick gl-plot-y-tick-label"
:style="{ top: (100 * (max - tick.value) / interval) + '%' }"
:title="tick.fullText || tick.text"
style="margin-top: -0.50em; direction: ltr;"
>
<span>{{ tick.text }}</span>
</div>
</div>
<!-- grid lines follow -->
<template v-if="position === 'right'">
<div
v-for="tick in ticks"
:key="tick.value"
class="gl-plot-hash hash-v"
:style="{
right: (100 * (max - tick.value) / interval) + '%',
height: '100%'
}"
<div v-for="tick in ticks"
:key="tick.value"
class="gl-plot-hash hash-v"
:style="{
right: (100 * (max - tick.value) / interval) + '%',
height: '100%'
}"
>
</div>
</template>
<template v-if="position === 'bottom'">
<div
v-for="tick in ticks"
:key="tick.value"
class="gl-plot-hash hash-h"
:style="{ bottom: (100 * (tick.value - min) / interval) + '%', width: '100%' }"
<div v-for="tick in ticks"
:key="tick.value"
class="gl-plot-hash hash-h"
:style="{ bottom: (100 * (tick.value - min) / interval) + '%', width: '100%' }"
>
</div>
</template>
@@ -112,10 +105,6 @@ export default {
mounted() {
eventHelpers.extend(this);
if (!this.axisType) {
throw new Error("axis-type prop expected");
}
this.axis = this.getAxisFromConfig();
this.tickCount = 4;
@@ -130,16 +119,15 @@ export default {
},
methods: {
getAxisFromConfig() {
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
/** @type {import('./configuration/PlotConfigurationModel').default} */
let config = configStore.get(configId);
if (!config) {
throw new Error('config is missing');
if (!this.axisType) {
return;
}
return config[this.axisType];
const configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
let config = configStore.get(configId);
if (config) {
return config[this.axisType];
}
},
/**
* Determine whether ticks should be regenerated for a given range.
@@ -215,8 +203,8 @@ export default {
if (this.shouldRegenerateTicks(range, forceRegeneration)) {
let newTicks = this.getTicks();
this.tickRange = {
min: Math.min(...newTicks),
max: Math.max(...newTicks),
min: Math.min.apply(Math, newTicks),
max: Math.max.apply(Math, newTicks),
step: newTicks[1] - newTicks[0]
};

View File

@@ -20,61 +20,52 @@
at runtime from the About dialog for additional information.
-->
<template>
<div
ref="plotWrapper"
class="c-plot holder holder-plot has-control-bar"
<div ref="plotWrapper"
class="c-plot holder holder-plot has-control-bar"
>
<div
v-if="!options.compact"
class="c-control-bar"
<div v-if="!options.compact"
class="c-control-bar"
>
<span class="c-button-set c-button-set--strip-h">
<button
class="c-button icon-download"
title="Export This View's Data as PNG"
@click="exportPNG()"
<button class="c-button icon-download"
title="Export This View's Data as PNG"
@click="exportPNG()"
>
<span class="c-button__label">PNG</span>
</button>
<button
class="c-button"
title="Export This View's Data as JPG"
@click="exportJPG()"
<button class="c-button"
title="Export This View's Data as JPG"
@click="exportJPG()"
>
<span class="c-button__label">JPG</span>
</button>
</span>
<button
class="c-button icon-crosshair"
:class="{ 'is-active': cursorGuide }"
title="Toggle cursor guides"
@click="toggleCursorGuide"
<button class="c-button icon-crosshair"
:class="{ 'is-active': cursorGuide }"
title="Toggle cursor guides"
@click="toggleCursorGuide"
>
</button>
<button
class="c-button"
:class="{ 'icon-grid-on': gridLines, 'icon-grid-off': !gridLines }"
title="Toggle grid lines"
@click="toggleGridLines"
<button class="c-button"
:class="{ 'icon-grid-on': gridLines, 'icon-grid-off': !gridLines }"
title="Toggle grid lines"
@click="toggleGridLines"
>
</button>
</div>
<div
ref="plotContainer"
class="l-view-section u-style-receiver js-style-receiver"
:class="{'s-status-timeconductor-unsynced': status && status === 'timeconductor-unsynced'}"
<div ref="plotContainer"
class="l-view-section u-style-receiver js-style-receiver"
:class="{'s-status-timeconductor-unsynced': status && status === 'timeconductor-unsynced'}"
>
<div
v-show="!!loading"
class="c-loading--overlay loading"
<div v-show="!!loading"
class="c-loading--overlay loading"
></div>
<mct-plot
:grid-lines="gridLines"
:cursor-guide="cursorGuide"
:options="options"
@loadingUpdated="loadingUpdated"
@statusUpdated="setStatus"
<mct-plot :grid-lines="gridLines"
:cursor-guide="cursorGuide"
:options="options"
@loadingUpdated="loadingUpdated"
@statusUpdated="setStatus"
/>
</div>
</div>

View File

@@ -21,14 +21,12 @@
-->
<template>
<div
v-if="loaded"
class="gl-plot-axis-area gl-plot-x has-local-controls"
<div v-if="loaded"
class="gl-plot-axis-area gl-plot-x has-local-controls"
>
<mct-ticks
:axis-type="'xAxis'"
:position="'left'"
@plotTickWidth="onTickWidthChange"
<mct-ticks :axis-type="'xAxis'"
:position="'left'"
@plotTickWidth="onTickWidthChange"
/>
<div
@@ -44,10 +42,9 @@
class="gl-plot-x-label__select local-controls--hidden"
@change="toggleXKeyOption()"
>
<option
v-for="option in xKeyOptions"
:key="option.key"
:value="option.key"
<option v-for="option in xKeyOptions"
:key="option.key"
:value="option.key"
>{{ option.name }}
</option>
</select>

View File

@@ -20,42 +20,37 @@
at runtime from the About dialog for additional information.
-->
<template>
<div
v-if="loaded"
class="gl-plot-axis-area gl-plot-y has-local-controls"
:style="{
width: (tickWidth + 20) + 'px'
}"
<div v-if="loaded"
class="gl-plot-axis-area gl-plot-y has-local-controls"
:style="{
width: (tickWidth + 20) + 'px'
}"
>
<div
v-if="singleSeries"
class="gl-plot-label gl-plot-y-label"
:class="{'icon-gear': (yKeyOptions.length > 1)}"
<div v-if="singleSeries"
class="gl-plot-label gl-plot-y-label"
:class="{'icon-gear': (yKeyOptions.length > 1)}"
>{{ yAxisLabel }}
</div>
<select
v-if="yKeyOptions.length > 1 && singleSeries"
v-model="yAxisLabel"
class="gl-plot-y-label__select local-controls--hidden"
@change="toggleYAxisLabel"
<select v-if="yKeyOptions.length > 1 && singleSeries"
v-model="yAxisLabel"
class="gl-plot-y-label__select local-controls--hidden"
@change="toggleYAxisLabel"
>
<option
v-for="(option, index) in yKeyOptions"
:key="index"
:value="option.name"
:selected="option.name === yAxisLabel"
<option v-for="(option, index) in yKeyOptions"
:key="index"
:value="option.name"
:selected="option.name === yAxisLabel"
>
{{ option.name }}
</option>
</select>
<mct-ticks
:axis-type="'yAxis'"
class="gl-plot-ticks"
:position="'top'"
@plotTickWidth="onTickWidthChange"
<mct-ticks :axis-type="'yAxis'"
class="gl-plot-ticks"
:position="'top'"
@plotTickWidth="onTickWidthChange"
/>
</div>
</template>

View File

@@ -1,16 +1,14 @@
<template>
<div
class="c-plot-limit"
:style="styleObj"
:class="limitClass"
<div class="c-plot-limit"
:style="styleObj"
:class="limitClass"
>
<div class="c-plot-limit__label">
<span class="c-plot-limit__direction-icon"></span>
<span class="c-plot-limit__severity-icon"></span>
<span class="c-plot-limit__limit-value">{{ limit.value }}</span>
<span
class="c-plot-limit__series-color-swatch"
:style="{ 'background-color': limit.seriesColor }"
<span class="c-plot-limit__series-color-swatch"
:style="{ 'background-color': limit.seriesColor }"
></span>
<span class="c-plot-limit__series-name">{{ limit.name }}</span>
</div>

View File

@@ -1,8 +1,7 @@
<template>
<div
:style="styleObj"
class="c-plot-limit-line js-limit-line"
:class="limitClass"
<div :style="styleObj"
class="c-plot-limit-line js-limit-line"
:class="limitClass"
></div>
</template>

View File

@@ -23,9 +23,6 @@
import eventHelpers from '../lib/eventHelpers';
export default class MCTChartAlarmLineSet {
/**
* @param {Bounds} bounds
*/
constructor(series, chart, offset, bounds) {
this.series = series;
this.chart = chart;
@@ -43,9 +40,6 @@ export default class MCTChartAlarmLineSet {
}
}
/**
* @param {Bounds} bounds
*/
updateBounds(bounds) {
this.bounds = bounds;
this.getLimitPoints(this.series);
@@ -112,7 +106,3 @@ export default class MCTChartAlarmLineSet {
}
}
/**
@typedef {import('@/api/time/TimeContext').Bounds} Bounds
*/

View File

@@ -23,7 +23,7 @@
import MCTChartSeriesElement from './MCTChartSeriesElement';
export default class MCTChartLineLinear extends MCTChartSeriesElement {
addPoint(point, start) {
addPoint(point, start, count) {
this.buffer[start] = point.x;
this.buffer[start + 1] = point.y;
}

View File

@@ -45,7 +45,7 @@ export default class MCTChartLineStepAfter extends MCTChartSeriesElement {
return 2 + ((index - 1) * 4);
}
addPoint(point, start) {
addPoint(point, start, count) {
if (start === 0 && this.count === 0) {
// First point is easy.
this.buffer[start] = point.x;

View File

@@ -24,7 +24,7 @@ import MCTChartSeriesElement from './MCTChartSeriesElement';
// TODO: Is this needed? This is identical to MCTChartLineLinear. Why is it a different class?
export default class MCTChartPointSet extends MCTChartSeriesElement {
addPoint(point, start) {
addPoint(point, start, count) {
this.buffer[start] = point.x;
this.buffer[start + 1] = point.y;
}

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