From d88ead502c10e7afa5b58fd1b285ac4471b933cb Mon Sep 17 00:00:00 2001 From: Shefali Joshi Date: Tue, 31 May 2022 11:15:02 -0700 Subject: [PATCH 01/21] Sprint 2.0.5 (#5272) * Bump d3-selection from 1.3.2 to 3.0.0 Bumps [d3-selection](https://github.com/d3/d3-selection) from 1.3.2 to 3.0.0. - [Release notes](https://github.com/d3/d3-selection/releases) - [Commits](https://github.com/d3/d3-selection/compare/v1.3.2...v3.0.0) --- updated-dependencies: - dependency-name: d3-selection dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Prep for release 2.0.5 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0588500276..c1aac194e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openmct", - "version": "2.0.4-SNAPSHOT", + "version": "2.0.5-SNAPSHOT", "description": "The Open MCT core platform", "devDependencies": { "@babel/eslint-parser": "7.16.3", From 8d761f729bf4d2f7a8967d8a1dfcd117e9461503 Mon Sep 17 00:00:00 2001 From: John Hill Date: Thu, 2 Jun 2022 07:43:40 -0700 Subject: [PATCH 02/21] Add visual test for create menu and display layout icon (#5278) Co-authored-by: unlikelyzero --- e2e/tests/visual/default.visual.spec.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/e2e/tests/visual/default.visual.spec.js b/e2e/tests/visual/default.visual.spec.js index e90e5d38ba..dd88b893ad 100644 --- a/e2e/tests/visual/default.visual.spec.js +++ b/e2e/tests/visual/default.visual.spec.js @@ -194,3 +194,16 @@ test('Visual - Save Successful Banner', async ({ page }) => { await percySnapshot(page, 'Banner message gone'); }); + +test('Visual - Display Layout Icon is correct', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + //Hover on Display Layout option. + await page.locator('text=Display Layout').hover(); + await percySnapshot(page, 'Display Layout Create Menu'); + +}); From dfb726b9248be31e051e70ce5af0d57ba7aef22d Mon Sep 17 00:00:00 2001 From: Jesse Mazzella Date: Thu, 2 Jun 2022 10:27:49 -0700 Subject: [PATCH 03/21] Unpause telemetry table on user bounds change (#5186) * Unpause telemetry table on user bounds change (#5113) * Add tests for table pause and unpause (#5113) * Add test (#5113) - Add test for scenario where table is paused by button but unpaused by user bounds change * Add test (#5113) - Add test for table does not unpause on a bounds change caused by a tick * Add e2e test (#5113) - Add test for scenario where table is paused by button but unpaused by user bounds change * Add test (#5113) - Correctly simulate clock tick - Exclude datum with new bounds and ensure the correct tableRow count * Remove 'wait for save banner' logic from e2e test * Use augmented `test` object in e2e test - Imports `test` object from `fixtures.js` * e2e: Add workarounds for chromium issue * Refactor per code review comments - Simplify `userBoundsChanged()` logic, get rid of duplicate code * Just get rid of the unnecessary method * Respond to code review comments - `destroyed()` --> `beforeDestroy()` - Rename `unpausedByButton` parameter to include user bounds change condition - Remove unused parameter --- .../telemetryTable/telemetryTable.e2e.spec.js | 101 ++++++++++++++ .../telemetryTable/components/table.vue | 26 +++- src/plugins/telemetryTable/pluginSpec.js | 125 ++++++++++++++++++ 3 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 e2e/tests/plugins/telemetryTable/telemetryTable.e2e.spec.js diff --git a/e2e/tests/plugins/telemetryTable/telemetryTable.e2e.spec.js b/e2e/tests/plugins/telemetryTable/telemetryTable.e2e.spec.js new file mode 100644 index 0000000000..ceffa3ae5f --- /dev/null +++ b/e2e/tests/plugins/telemetryTable/telemetryTable.e2e.spec.js @@ -0,0 +1,101 @@ +/***************************************************************************** + * 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 } = require('../../../fixtures'); +const { expect } = require('@playwright/test'); + +test.describe('Telemetry Table', () => { + test('unpauses when paused by button and user changes bounds', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/5113' + }); + + const bannerMessage = '.c-message-banner__message'; + const createButton = 'button:has-text("Create")'; + + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click create button + await page.locator(createButton).click(); + await page.locator('li:has-text("Telemetry Table")').click(); + + // Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184 + await page.click('form[name="mctForm"] a:has-text("My Items")'); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + // Wait for Save Banner to appear + page.waitForSelector(bannerMessage) + ]); + + // Save (exit edit mode) + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(3).click(); + await page.locator('text=Save and Finish Editing').click(); + + // Click create button + await page.locator(createButton).click(); + + // add Sine Wave Generator with defaults + await page.locator('li:has-text("Sine Wave Generator")').click(); + + // Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184 + await page.click('form[name="mctForm"] a:has-text("My Items")'); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + // Wait for Save Banner to appear + page.waitForSelector(bannerMessage) + ]); + + // focus the Telemetry Table + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Telemetry Table').first().click() + ]); + + // Click pause button + const pauseButton = await page.locator('button.c-button.icon-pause'); + await pauseButton.click(); + + const tableWrapper = await page.locator('div.c-table-wrapper'); + await expect(tableWrapper).toHaveClass(/is-paused/); + + // Arbitrarily change end date to some time in the future + const endTimeInput = page.locator('input[type="text"].c-input--datetime').nth(1); + await endTimeInput.click(); + + let endDate = await endTimeInput.inputValue(); + endDate = new Date(endDate); + endDate.setUTCDate(endDate.getUTCDate() + 1); + endDate = endDate.toISOString().replace(/T.*/, ''); + + await endTimeInput.fill(''); + await endTimeInput.fill(endDate); + await page.keyboard.press('Enter'); + + await expect(tableWrapper).not.toHaveClass(/is-paused/); + }); +}); diff --git a/src/plugins/telemetryTable/components/table.vue b/src/plugins/telemetryTable/components/table.vue index 59172615b2..af34cb0221 100644 --- a/src/plugins/telemetryTable/components/table.vue +++ b/src/plugins/telemetryTable/components/table.vue @@ -499,6 +499,8 @@ export default { this.table.tableRows.on('sort', this.updateVisibleRows); this.table.tableRows.on('filter', this.updateVisibleRows); + this.openmct.time.on('bounds', this.boundsChanged); + //Default sort this.sortOptions = this.table.tableRows.sortBy(); this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w'); @@ -513,7 +515,7 @@ export default { this.table.initialize(); }, - destroyed() { + beforeDestroy() { this.table.off('object-added', this.addObject); this.table.off('object-removed', this.removeObject); this.table.off('historical-rows-processed', this.checkForMarkedRows); @@ -527,6 +529,8 @@ export default { this.table.configuration.off('change', this.updateConfiguration); + this.openmct.time.off('bounds', this.boundsChanged); + clearInterval(this.resizePollHandle); this.table.configuration.destroy(); @@ -823,16 +827,16 @@ export default { this.visibleRows = []; this.$nextTick().then(this.updateVisibleRows); }, - pause(pausedByButton) { - if (pausedByButton) { + pause(byButton) { + if (byButton) { this.pausedByButton = true; } this.paused = true; this.table.pause(); }, - unpause(unpausedByButton) { - if (unpausedByButton) { + unpause(byButtonOrUserBoundsChange) { + if (byButtonOrUserBoundsChange) { this.undoMarkedRows(); this.table.unpause(); this.paused = false; @@ -847,6 +851,16 @@ export default { this.isShowingMarkedRowsOnly = false; }, + boundsChanged(_bounds, isTick) { + if (isTick) { + return; + } + + // User bounds change. + if (this.paused) { + this.unpause(true); + } + }, togglePauseByButton() { if (this.paused) { this.unpause(true); @@ -854,7 +868,7 @@ export default { this.pause(true); } }, - undoMarkedRows(unpause) { + undoMarkedRows() { this.markedRows.forEach(r => r.marked = false); this.markedRows = []; }, diff --git a/src/plugins/telemetryTable/pluginSpec.js b/src/plugins/telemetryTable/pluginSpec.js index 1d29c2e683..9d8892f3c2 100644 --- a/src/plugins/telemetryTable/pluginSpec.js +++ b/src/plugins/telemetryTable/pluginSpec.js @@ -133,6 +133,7 @@ describe("the plugin", () => { let tableViewProvider; let tableView; let tableInstance; + let mockClock; beforeEach(() => { openmct.time.timeSystem('utc', { @@ -140,6 +141,20 @@ describe("the plugin", () => { end: 4 }); + mockClock = jasmine.createSpyObj("clock", [ + "on", + "off", + "currentValue" + ]); + mockClock.key = 'mockClock'; + mockClock.currentValue.and.returnValue(1); + + openmct.time.addClock(mockClock); + openmct.time.clock('mockClock', { + start: 0, + end: 4 + }); + testTelemetryObject = { identifier: { namespace: "", @@ -360,5 +375,115 @@ describe("the plugin", () => { tableRowCells = element.querySelectorAll('table.c-telemetry-table__body > tbody > tr:first-child td'); expect(tableRowCells.length).toEqual(4); }); + + it("Pauses the table when a row is marked", async () => { + let firstRow = element.querySelector('table.c-telemetry-table__body > tbody > tr'); + let clickEvent = createMouseEvent('click'); + + // Mark a row + firstRow.dispatchEvent(clickEvent); + + await Vue.nextTick(); + + // Verify table is paused + expect(element.querySelector('div.c-table.is-paused')).not.toBeNull(); + }); + + it("Unpauses the table on user bounds change", async () => { + let firstRow = element.querySelector('table.c-telemetry-table__body > tbody > tr'); + let clickEvent = createMouseEvent('click'); + + // Mark a row + firstRow.dispatchEvent(clickEvent); + + await Vue.nextTick(); + + // Verify table is paused + expect(element.querySelector('div.c-table.is-paused')).not.toBeNull(); + + const currentBounds = openmct.time.bounds(); + + const newBounds = { + start: currentBounds.start, + end: currentBounds.end - 3 + }; + + // Manually change the time bounds + openmct.time.bounds(newBounds); + + await Vue.nextTick(); + + // Verify table is no longer paused + expect(element.querySelector('div.c-table.is-paused')).toBeNull(); + + await Vue.nextTick(); + + // Verify table displays the correct number of rows within the new bounds + const tableRows = element.querySelectorAll('table.c-telemetry-table__body > tbody > tr'); + expect(tableRows.length).toEqual(2); + }); + + it("Unpauses the table on user bounds change if paused by button", async () => { + const viewContext = tableView.getViewContext(); + + // Pause by button + viewContext.togglePauseByButton(); + + await Vue.nextTick(); + + // Verify table is paused + expect(element.querySelector('div.c-table.is-paused')).not.toBeNull(); + + const currentBounds = openmct.time.bounds(); + + const newBounds = { + start: currentBounds.start, + end: currentBounds.end - 3 + }; + + // Manually change the time bounds + openmct.time.bounds(newBounds); + + await Vue.nextTick(); + + // Verify table is no longer paused + expect(element.querySelector('div.c-table.is-paused')).toBeNull(); + + await Vue.nextTick(); + + // Verify table displays the correct number of rows within the new bounds + const tableRows = element.querySelectorAll('table.c-telemetry-table__body > tbody > tr'); + expect(tableRows.length).toEqual(2); + }); + + it("Does not unpause the table on tick", async () => { + const viewContext = tableView.getViewContext(); + + // Pause by button + viewContext.togglePauseByButton(); + + await Vue.nextTick(); + + // Verify table displays the correct number of rows + let tableRows = element.querySelectorAll('table.c-telemetry-table__body > tbody > tr'); + expect(tableRows.length).toEqual(3); + + // Verify table is paused + expect(element.querySelector('div.c-table.is-paused')).not.toBeNull(); + + // Tick the clock + openmct.time.tick(1); + + await Vue.nextTick(); + + // Verify table is still paused + expect(element.querySelector('div.c-table.is-paused')).not.toBeNull(); + + await Vue.nextTick(); + + // Verify table displays the correct number of rows + tableRows = element.querySelectorAll('table.c-telemetry-table__body > tbody > tr'); + expect(tableRows.length).toEqual(3); + }); }); }); From 50b642fabe2f0f76512e272b5b6306f54da8b46a Mon Sep 17 00:00:00 2001 From: Michael Rogers Date: Thu, 2 Jun 2022 13:42:11 -0500 Subject: [PATCH 04/21] Updated the dependency injection syntax to use v4 instead of default (#5279) --- example/generator/WorkerInterface.js | 2 +- src/plugins/objectMigration/Migrations.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/generator/WorkerInterface.js b/example/generator/WorkerInterface.js index 2ddb3ee181..1573800fff 100644 --- a/example/generator/WorkerInterface.js +++ b/example/generator/WorkerInterface.js @@ -23,7 +23,7 @@ define([ 'uuid' ], function ( - uuid + { v4: uuid } ) { function WorkerInterface(openmct) { // eslint-disable-next-line no-undef diff --git a/src/plugins/objectMigration/Migrations.js b/src/plugins/objectMigration/Migrations.js index 8bf5d445af..46bd8f3831 100644 --- a/src/plugins/objectMigration/Migrations.js +++ b/src/plugins/objectMigration/Migrations.js @@ -23,7 +23,7 @@ define([ 'uuid' ], function ( - uuid + { v4: uuid } ) { return function Migrations(openmct) { function getColumnNameKeyMap(domainObject) { From f5796c984ef84f918c0a7448440013f6f2469e0a Mon Sep 17 00:00:00 2001 From: Charles Hacskaylo Date: Thu, 2 Jun 2022 13:46:13 -0700 Subject: [PATCH 05/21] Operator status (#5179) * Added click event to simple indicator * Moved operator status plugin to Open * Implementing user role status API * Support adding indicators asynchronously * Adding user status API * Updated example user provider * Update icon with status * Adding admin indicator * Apply config options * Set status class on indicator. Clear all statuses * Show poll question in op stat indicator * Implementing status summary * Get statuses from providers. Reset statuses when poll question set * Styling for operator status - New icon glyph - IMPORTANT: OVERRIDE ANY MERGE CONFLICTS USING THIS COMMIT! - Fixed erroneous font glyph mapping; - Added default color for indicator icon; - Changed user indicator to display response when set to other than "NO_STATUS". - Standardized icon display. * Cherrypick symbols font updates from restricted-notebook branch. This is the most full and complete version of the symbols font - OVERRIDE ANY MERGE CONFLICTS WITH THIS COMMIT! * Fix positioning of popups * Also fix positioning of status indicator * Get roles by status instead of users * Refactor how status summary is determined to simplify API * Re-fetch status summary on status change * Implemented status reset * Move status into separate API * Refactor user status to its own sub-API * Create RAF utility class * Error handling * Add copyright notices * Fix test issues * Added jsdocs * Additional tests for raf utility function * Move status style configuration into Open * Move styling from the API into the view * Added some docs * Added some unit tests and fixed a bug found in the process. Tests work\! Co-authored-by: Andrew Henry --- example/exampleUser/ExampleUserProvider.js | 105 +++- example/exampleUser/plugin.js | 15 +- example/exampleUser/pluginSpec.js | 7 +- src/api/api.js | 2 +- src/api/indicators/IndicatorAPI.js | 34 +- src/api/indicators/SimpleIndicator.js | 171 ++++--- src/api/telemetry/TelemetryMetadataManager.js | 2 +- src/api/user/StatusAPI.js | 295 +++++++++++ src/api/user/StatusUserProvider.js | 81 +++ src/api/user/UserAPI.js | 42 +- src/api/user/UserProvider.js | 36 ++ src/api/user/UserStatusAPISpec.js | 103 ++++ .../components/TelemetryView.vue | 2 +- src/plugins/objectMigration/Migrations.js | 2 +- .../operatorStatus/AbstractStatusIndicator.js | 106 ++++ .../operatorStatus/operator-status.scss | 142 ++++++ .../operatorStatus/OperatorStatus.vue | 187 +++++++ .../operatorStatus/OperatorStatusIndicator.js | 63 +++ src/plugins/operatorStatus/plugin.js | 50 ++ .../pollQuestion/PollQuestion.vue | 184 +++++++ .../pollQuestion/PollQuestionIndicator.js | 63 +++ src/plugins/plugins.js | 3 + src/styles/_constants.scss | 9 + src/styles/_glyphs.scss | 9 + src/styles/fonts/Open MCT Symbols 16px.json | 473 +++++++++++++----- src/styles/fonts/Open-MCT-Symbols-16px.svg | 328 ++++++------ src/styles/fonts/Open-MCT-Symbols-16px.ttf | Bin 24692 -> 26020 bytes src/styles/fonts/Open-MCT-Symbols-16px.woff | Bin 24768 -> 26096 bytes src/styles/vue-styles.scss | 1 + src/ui/layout/status-bar/Indicators.vue | 13 +- src/utils/raf.js | 14 + src/utils/rafSpec.js | 61 +++ 32 files changed, 2216 insertions(+), 387 deletions(-) create mode 100644 src/api/user/StatusAPI.js create mode 100644 src/api/user/StatusUserProvider.js create mode 100644 src/api/user/UserProvider.js create mode 100644 src/api/user/UserStatusAPISpec.js create mode 100644 src/plugins/operatorStatus/AbstractStatusIndicator.js create mode 100644 src/plugins/operatorStatus/operator-status.scss create mode 100644 src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue create mode 100644 src/plugins/operatorStatus/operatorStatus/OperatorStatusIndicator.js create mode 100644 src/plugins/operatorStatus/plugin.js create mode 100644 src/plugins/operatorStatus/pollQuestion/PollQuestion.vue create mode 100644 src/plugins/operatorStatus/pollQuestion/PollQuestionIndicator.js create mode 100644 src/utils/raf.js create mode 100644 src/utils/rafSpec.js diff --git a/example/exampleUser/ExampleUserProvider.js b/example/exampleUser/ExampleUserProvider.js index 7e17de98ef..2926f0ba92 100644 --- a/example/exampleUser/ExampleUserProvider.js +++ b/example/exampleUser/ExampleUserProvider.js @@ -24,16 +24,53 @@ import EventEmitter from 'EventEmitter'; import { v4 as uuid } from 'uuid'; import createExampleUser from './exampleUserCreator'; +const STATUSES = [{ + key: "NO_STATUS", + label: "Not set", + iconClass: "icon-question-mark", + iconClassPoll: "icon-status-poll-question-mark" +}, { + key: "GO", + label: "GO", + iconClass: "icon-check", + iconClassPoll: "icon-status-poll-question-mark", + statusClass: "s-status-ok", + statusBgColor: "#33cc33", + statusFgColor: "#000" +}, { + key: "MAYBE", + label: "MAYBE", + iconClass: "icon-alert-triangle", + iconClassPoll: "icon-status-poll-question-mark", + statusClass: "s-status-warning", + statusBgColor: "#ffb66c", + statusFgColor: "#000" +}, { + key: "NO_GO", + label: "NO GO", + iconClass: "icon-circle-slash", + iconClassPoll: "icon-status-poll-question-mark", + statusClass: "s-status-error", + statusBgColor: "#9900cc", + statusFgColor: "#fff" +}]; +/** + * @implements {StatusUserProvider} + */ export default class ExampleUserProvider extends EventEmitter { - constructor(openmct) { + constructor(openmct, {defaultStatusRole} = {defaultStatusRole: undefined}) { super(); this.openmct = openmct; this.user = undefined; this.loggedIn = false; this.autoLoginUser = undefined; + this.status = STATUSES[1]; + this.pollQuestion = undefined; + this.defaultStatusRole = defaultStatusRole; this.ExampleUser = createExampleUser(this.openmct.user.User); + this.loginPromise = undefined; } isLoggedIn() { @@ -45,11 +82,19 @@ export default class ExampleUserProvider extends EventEmitter { } getCurrentUser() { - if (this.loggedIn) { - return Promise.resolve(this.user); + if (!this.loginPromise) { + this.loginPromise = this._login().then(() => this.user); } - return this._login().then(() => this.user); + return this.loginPromise; + } + + canProvideStatusForRole() { + return Promise.resolve(true); + } + + canSetPollQuestion() { + return Promise.resolve(true); } hasRole(roleId) { @@ -60,6 +105,55 @@ export default class ExampleUserProvider extends EventEmitter { return Promise.resolve(this.user.getRoles().includes(roleId)); } + getStatusRoleForCurrentUser() { + return Promise.resolve(this.defaultStatusRole); + } + + getAllStatusRoles() { + return Promise.resolve([this.defaultStatusRole]); + } + + getStatusForRole(role) { + return Promise.resolve(this.status); + } + + async getDefaultStatusForRole(role) { + const allRoles = await this.getPossibleStatuses(); + + return allRoles?.[0]; + } + + setStatusForRole(role, status) { + this.status = status; + this.emit('statusChange', { + role, + status + }); + + return true; + } + + getPollQuestion() { + return Promise.resolve({ + question: 'Set "GO" if your position is ready for a boarding action on the Klingon cruiser', + timestamp: Date.now() + }); + } + + setPollQuestion(pollQuestion) { + this.pollQuestion = { + question: pollQuestion, + timestamp: Date.now() + }; + this.emit("pollQuestionChange", this.pollQuestion); + + return true; + } + + getPossibleStatuses() { + return Promise.resolve(STATUSES); + } + _login() { const id = uuid(); @@ -108,3 +202,6 @@ export default class ExampleUserProvider extends EventEmitter { ); } } +/** + * @typedef {import('@/api/user/StatusUserProvider').default} StatusUserProvider + */ diff --git a/example/exampleUser/plugin.js b/example/exampleUser/plugin.js index f7094131e6..af533f098b 100644 --- a/example/exampleUser/plugin.js +++ b/example/exampleUser/plugin.js @@ -22,8 +22,19 @@ import ExampleUserProvider from './ExampleUserProvider'; -export default function ExampleUserPlugin() { +export default function ExampleUserPlugin({autoLoginUser, defaultStatusRole} = { + autoLoginUser: 'guest', + defaultStatusRole: 'test-role' +}) { return function install(openmct) { - openmct.user.setProvider(new ExampleUserProvider(openmct)); + const userProvider = new ExampleUserProvider(openmct, { + defaultStatusRole + }); + + if (autoLoginUser !== undefined) { + userProvider.autoLogin(autoLoginUser); + } + + openmct.user.setProvider(userProvider); }; } diff --git a/example/exampleUser/pluginSpec.js b/example/exampleUser/pluginSpec.js index dd8ea6bba5..02719d99d5 100644 --- a/example/exampleUser/pluginSpec.js +++ b/example/exampleUser/pluginSpec.js @@ -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(() => { @@ -47,9 +47,4 @@ xdescribe("The Example User Plugin", () => { }); openmct.install(openmct.plugins.example.ExampleUser()); }); - - // The rest of the functionality of the ExampleUser Plugin is - // tested in both the UserAPISpec.js and in the UserIndicatorPlugin spec. - // If that changes, those tests can be moved here. - }); diff --git a/src/api/api.js b/src/api/api.js index 1a0174d574..7e31bec7aa 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -56,7 +56,7 @@ define([ CompositionAPI: CompositionAPI, EditorAPI: EditorAPI, FormsAPI: FormsAPI, - IndicatorAPI: IndicatorAPI, + IndicatorAPI: IndicatorAPI.default, MenuAPI: MenuAPI.default, NotificationAPI: NotificationAPI.default, ObjectAPI: ObjectAPI, diff --git a/src/api/indicators/IndicatorAPI.js b/src/api/indicators/IndicatorAPI.js index ef81f67884..98d78112ca 100644 --- a/src/api/indicators/IndicatorAPI.js +++ b/src/api/indicators/IndicatorAPI.js @@ -19,27 +19,27 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -define([ - './SimpleIndicator', - 'lodash' -], function ( - SimpleIndicator, - _ -) { - function IndicatorAPI(openmct) { + +import EventEmitter from "EventEmitter"; +import SimpleIndicator from "./SimpleIndicator"; + +class IndicatorAPI extends EventEmitter { + constructor(openmct) { + super(); + this.openmct = openmct; this.indicatorObjects = []; } - IndicatorAPI.prototype.getIndicatorObjectsByPriority = function () { + getIndicatorObjectsByPriority() { const sortedIndicators = this.indicatorObjects.sort((a, b) => b.priority - a.priority); return sortedIndicators; - }; + } - IndicatorAPI.prototype.simpleIndicator = function () { + simpleIndicator() { return new SimpleIndicator(this.openmct); - }; + } /** * Accepts an indicator object, which is a simple object @@ -62,14 +62,16 @@ define([ * myIndicator.iconClass("icon-info"); * */ - IndicatorAPI.prototype.add = function (indicator) { + add(indicator) { if (!indicator.priority) { indicator.priority = this.openmct.priority.DEFAULT; } this.indicatorObjects.push(indicator); - }; - return IndicatorAPI; + this.emit('addIndicator', indicator); + } -}); +} + +export default IndicatorAPI; diff --git a/src/api/indicators/SimpleIndicator.js b/src/api/indicators/SimpleIndicator.js index 7556dd512e..1ef99e6888 100644 --- a/src/api/indicators/SimpleIndicator.js +++ b/src/api/indicators/SimpleIndicator.js @@ -20,82 +20,107 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define(['zepto', './res/indicator-template.html'], - function ($, indicatorTemplate) { - const DEFAULT_ICON_CLASS = 'icon-info'; +import EventEmitter from 'EventEmitter'; +import indicatorTemplate from './res/indicator-template.html'; - function SimpleIndicator(openmct) { - this.openmct = openmct; - this.element = $(indicatorTemplate)[0]; - this.priority = openmct.priority.DEFAULT; +const DEFAULT_ICON_CLASS = 'icon-info'; - this.textElement = this.element.querySelector('.js-indicator-text'); +class SimpleIndicator extends EventEmitter { + constructor(openmct) { + super(); - //Set defaults - this.text('New Indicator'); - this.description(''); - this.iconClass(DEFAULT_ICON_CLASS); - this.statusClass(''); + this.openmct = openmct; + this.element = compileTemplate(indicatorTemplate)[0]; + this.priority = openmct.priority.DEFAULT; + + this.textElement = this.element.querySelector('.js-indicator-text'); + + //Set defaults + this.text('New Indicator'); + this.description(''); + this.iconClass(DEFAULT_ICON_CLASS); + + this.click = this.click.bind(this); + + this.element.addEventListener('click', this.click); + openmct.once('destroy', () => { + this.removeAllListeners(); + this.element.removeEventListener('click', this.click); + }); + } + + text(text) { + if (text !== undefined && text !== this.textValue) { + this.textValue = text; + this.textElement.innerText = text; + + if (!text) { + this.element.classList.add('hidden'); + } else { + this.element.classList.remove('hidden'); + } } - SimpleIndicator.prototype.text = function (text) { - if (text !== undefined && text !== this.textValue) { - this.textValue = text; - this.textElement.innerText = text; - - if (!text) { - this.element.classList.add('hidden'); - } else { - this.element.classList.remove('hidden'); - } - } - - return this.textValue; - }; - - SimpleIndicator.prototype.description = function (description) { - if (description !== undefined && description !== this.descriptionValue) { - this.descriptionValue = description; - this.element.title = description; - } - - return this.descriptionValue; - }; - - SimpleIndicator.prototype.iconClass = function (iconClass) { - if (iconClass !== undefined && iconClass !== this.iconClassValue) { - // element.classList is precious and throws errors if you try and add - // or remove empty strings - if (this.iconClassValue) { - this.element.classList.remove(this.iconClassValue); - } - - if (iconClass) { - this.element.classList.add(iconClass); - } - - this.iconClassValue = iconClass; - } - - return this.iconClassValue; - }; - - SimpleIndicator.prototype.statusClass = function (statusClass) { - if (statusClass !== undefined && statusClass !== this.statusClassValue) { - if (this.statusClassValue) { - this.element.classList.remove(this.statusClassValue); - } - - if (statusClass) { - this.element.classList.add(statusClass); - } - - this.statusClassValue = statusClass; - } - - return this.statusClassValue; - }; - - return SimpleIndicator; + return this.textValue; } -); + + description(description) { + if (description !== undefined && description !== this.descriptionValue) { + this.descriptionValue = description; + this.element.title = description; + } + + return this.descriptionValue; + } + + iconClass(iconClass) { + if (iconClass !== undefined && iconClass !== this.iconClassValue) { + // element.classList is precious and throws errors if you try and add + // or remove empty strings + if (this.iconClassValue) { + this.element.classList.remove(this.iconClassValue); + } + + if (iconClass) { + this.element.classList.add(iconClass); + } + + this.iconClassValue = iconClass; + } + + return this.iconClassValue; + } + + statusClass(statusClass) { + if (arguments.length === 1 && statusClass !== this.statusClassValue) { + if (this.statusClassValue) { + this.element.classList.remove(this.statusClassValue); + } + + if (statusClass !== undefined) { + this.element.classList.add(statusClass); + } + + this.statusClassValue = statusClass; + } + + return this.statusClassValue; + } + + click(event) { + this.emit('click', event); + } + + getElement() { + return this.element; + } +} + +function compileTemplate(htmlTemplate) { + const templateNode = document.createElement('template'); + templateNode.innerHTML = htmlTemplate; + + return templateNode.content.cloneNode(true).children; +} + +export default SimpleIndicator; diff --git a/src/api/telemetry/TelemetryMetadataManager.js b/src/api/telemetry/TelemetryMetadataManager.js index 1f55f5829d..0e21ad0797 100644 --- a/src/api/telemetry/TelemetryMetadataManager.js +++ b/src/api/telemetry/TelemetryMetadataManager.js @@ -138,7 +138,7 @@ define([ valueMetadata = this.values()[0]; } - return valueMetadata.key; + return valueMetadata; }; return TelemetryMetadataManager; diff --git a/src/api/user/StatusAPI.js b/src/api/user/StatusAPI.js new file mode 100644 index 0000000000..4e53d96143 --- /dev/null +++ b/src/api/user/StatusAPI.js @@ -0,0 +1,295 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ +import EventEmitter from "EventEmitter"; + +export default class StatusAPI extends EventEmitter { + #userAPI; + #openmct; + + constructor(userAPI, openmct) { + super(); + this.#userAPI = userAPI; + this.#openmct = openmct; + + this.onProviderStatusChange = this.onProviderStatusChange.bind(this); + this.onProviderPollQuestionChange = this.onProviderPollQuestionChange.bind(this); + this.listenToStatusEvents = this.listenToStatusEvents.bind(this); + + this.#openmct.once('destroy', () => { + const provider = this.#userAPI.getProvider(); + + if (typeof provider?.off === 'function') { + provider.off('statusChange', this.onProviderStatusChange); + provider.off('pollQuestionChange', this.onProviderPollQuestionChange); + } + }); + + this.#userAPI.on('providerAdded', this.listenToStatusEvents); + } + + /** + * Fetch the currently defined operator status poll question. When presented with a status poll question, all operators will reply with their current status. + * @returns {Promise} + */ + getPollQuestion() { + const provider = this.#userAPI.getProvider(); + + if (provider.getPollQuestion) { + return provider.getPollQuestion(); + } else { + this.#userAPI.error("User provider does not support polling questions"); + } + } + + /** + * Set a poll question for operators to respond to. When presented with a status poll question, all operators will reply with their current status. + * @param {String} questionText - The text of the question + * @returns {Promise} true if operation was successful, otherwise false. + */ + async setPollQuestion(questionText) { + const canSetPollQuestion = await this.canSetPollQuestion(); + + if (canSetPollQuestion) { + const provider = this.#userAPI.getProvider(); + + const result = await provider.setPollQuestion(questionText); + + try { + await this.resetAllStatuses(); + } catch (error) { + console.warn("Poll question set but unable to clear operator statuses."); + console.error(error); + } + + return result; + } else { + this.#userAPI.error("User provider does not support setting polling question"); + } + } + + /** + * Can the currently logged in user set the operator status poll question. + * @returns {Promise} + */ + canSetPollQuestion() { + const provider = this.#userAPI.getProvider(); + + if (provider.canSetPollQuestion) { + return provider.canSetPollQuestion(); + } else { + return Promise.resolve(false); + } + } + + /** + * @returns {Promise>} the complete list of possible states that an operator can reply to a poll question with. + */ + async getPossibleStatuses() { + const provider = this.#userAPI.getProvider(); + + if (provider.getPossibleStatuses) { + const possibleStatuses = await provider.getPossibleStatuses() || []; + + return possibleStatuses.map(status => status); + } else { + this.#userAPI.error("User provider cannot provide statuses"); + } + } + + /** + * @param {import("./UserAPI").Role} role The role to fetch the current status for. + * @returns {Promise} the current status of the provided role + */ + async getStatusForRole(role) { + const provider = this.#userAPI.getProvider(); + + if (provider.getStatusForRole) { + const status = await provider.getStatusForRole(role); + + return status; + } else { + this.#userAPI.error("User provider does not support role status"); + } + } + + /** + * @param {import("./UserAPI").Role} role + * @returns {Promise} true if the configured UserProvider can provide status for the given role + * @see StatusUserProvider + */ + canProvideStatusForRole(role) { + const provider = this.#userAPI.getProvider(); + + if (provider.canProvideStatusForRole) { + return provider.canProvideStatusForRole(role); + } else { + return false; + } + } + + /** + * @param {import("./UserAPI").Role} role The role to set the status for. + * @param {Status} status The status to set for the provided role + * @returns {Promise} true if operation was successful, otherwise false. + */ + setStatusForRole(role, status) { + const provider = this.#userAPI.getProvider(); + + if (provider.setStatusForRole) { + return provider.setStatusForRole(role, status); + } else { + this.#userAPI.error("User provider does not support setting role status"); + } + } + + /** + * Resets the status of the provided role back to its default status. + * @param {import("./UserAPI").Role} role The role to set the status for. + * @returns {Promise} true if operation was successful, otherwise false. + */ + async resetStatusForRole(role) { + const provider = this.#userAPI.getProvider(); + const defaultStatus = await this.getDefaultStatus(); + + if (provider.setStatusForRole) { + return provider.setStatusForRole(role, defaultStatus); + } else { + this.#userAPI.error("User provider does not support resetting role status"); + } + } + + /** + * Resets the status of all operators to their default status + * @returns {Promise} true if operation was successful, otherwise false. + */ + async resetAllStatuses() { + const allStatusRoles = await this.getAllStatusRoles(); + + return Promise.all(allStatusRoles.map(role => this.resetStatusForRole(role))); + } + + /** + * The default status. This is the status that will be used before the user has selected any status. + * @param {import("./UserAPI").Role} role + * @returns {Promise} the default operator status if no other has been set. + */ + async getDefaultStatusForRole(role) { + const provider = this.#userAPI.getProvider(); + const defaultStatus = await provider.getDefaultStatusForRole(role); + + return defaultStatus; + } + + /** + * All possible status roles. A status role is a user role that can provide status. In some systems + * this may be all user roles, but there may be cases where some users are not are not polled + * for status if they do not have a real-time operational role. + * + * @returns {Promise>} the default operator status if no other has been set. + */ + getAllStatusRoles() { + const provider = this.#userAPI.getProvider(); + + if (provider.getAllStatusRoles) { + return provider.getAllStatusRoles(); + } else { + this.#userAPI.error("User provider cannot provide all status roles"); + } + } + + /** + * The status role of the current user. A user may have multiple roles, but will only have one role + * that provides status at any time. + * @returns {Promise} the role for which the current user can provide status. + */ + getStatusRoleForCurrentUser() { + const provider = this.#userAPI.getProvider(); + + if (provider.getStatusRoleForCurrentUser) { + return provider.getStatusRoleForCurrentUser(); + } else { + this.#userAPI.error("User provider cannot provide role status for this user"); + } + } + + /** + * @returns {Promise} true if the configured UserProvider can provide status for the currently logged in user, false otherwise. + * @see StatusUserProvider + */ + async canProvideStatusForCurrentUser() { + const provider = this.#userAPI.getProvider(); + + if (provider.getStatusRoleForCurrentUser) { + const activeStatusRole = await this.#userAPI.getProvider().getStatusRoleForCurrentUser(); + const canProvideStatus = await this.canProvideStatusForRole(activeStatusRole); + + return canProvideStatus; + } else { + return false; + } + } + + /** + * Private internal function that cannot be made #private because it needs to be registered as a callback to the user provider + * @private + */ + listenToStatusEvents(provider) { + if (typeof provider.on === 'function') { + provider.on('statusChange', this.onProviderStatusChange); + provider.on('pollQuestionChange', this.onProviderPollQuestionChange); + } + } + + /** + * @private + */ + onProviderStatusChange(newStatus) { + this.emit('statusChange', newStatus); + } + + /** + * @private + */ + onProviderPollQuestionChange(pollQuestion) { + this.emit('pollQuestionChange', pollQuestion); + } +} + +/** + * @typedef {import('./UserProvider')} UserProvider + */ +/** + * @typedef {import('./StatusUserProvider')} StatusUserProvider + */ +/** + * The PollQuestion type + * @typedef {Object} PollQuestion + * @property {String} question - The question to be presented to users + * @property {Number} timestamp - The time that the poll question was set. + */ + +/** + * The Status type + * @typedef {Object} Status + * @property {String} key - A unique identifier for this status + * @property {Number} label - A human readable label for this status + */ diff --git a/src/api/user/StatusUserProvider.js b/src/api/user/StatusUserProvider.js new file mode 100644 index 0000000000..b474fdbedb --- /dev/null +++ b/src/api/user/StatusUserProvider.js @@ -0,0 +1,81 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ +import UserProvider from "./UserProvider"; + +export default class StatusUserProvider extends UserProvider { + /** + * @param {('statusChange'|'pollQuestionChange')} event the name of the event to listen to + * @param {Function} callback a function to invoke when this event occurs + */ + on(event, callback) {} + /** + * @param {('statusChange'|'pollQuestionChange')} event the name of the event to stop listen to + * @param {Function} callback the callback function used to register the listener + */ + off(event, callback) {} + /** + * @returns {import("./StatusAPI").PollQuestion} the current status poll question + */ + async getPollQuestion() {} + /** + * @param {import("./StatusAPI").PollQuestion} pollQuestion a new poll question to set + * @returns {Promise} true if operation was successful, otherwise false + */ + async setPollQuestion(pollQuestion) {} + /** + * @returns {Promise} true if the current user can set the poll question, otherwise false + */ + async canSetPollQuestion() {} + /** + * @returns {Promise>} a list of the possible statuses that an operator can be in + */ + async getPossibleStatuses() {} + /** + * @param {import("./UserAPI").Role} role + * @returns {Promise} true if operation was successful, otherwise false. + */ + async setStatusForRole(role, status) {} + /** + * @param {import("./UserAPI").Role} role + * @returns {Promise>} a list of all available status roles, if user permissions allow it. + */ + async getAllStatusRoles() {} + /** + * @returns {Promise} the active status role for the currently logged in user + */ + async getStatusRoleForCurrentUser() {} +} diff --git a/src/api/user/UserAPI.js b/src/api/user/UserAPI.js index 1948021734..0ab2d91569 100644 --- a/src/api/user/UserAPI.js +++ b/src/api/user/UserAPI.js @@ -25,16 +25,22 @@ import { MULTIPLE_PROVIDER_ERROR, NO_PROVIDER_ERROR } from './constants'; +import StatusAPI from './StatusAPI'; import User from './User'; class UserAPI extends EventEmitter { - constructor(openmct) { + /** + * @param {OpenMCT} openmct + * @param {UserAPIConfiguration} config + */ + constructor(openmct, config) { super(); this._openmct = openmct; this._provider = undefined; this.User = User; + this.status = new StatusAPI(this, openmct, config); } /** @@ -47,14 +53,17 @@ class UserAPI extends EventEmitter { */ setProvider(provider) { if (this.hasProvider()) { - this._error(MULTIPLE_PROVIDER_ERROR); + this.error(MULTIPLE_PROVIDER_ERROR); } this._provider = provider; - this.emit('providerAdded', this._provider); } + getProvider() { + return this._provider; + } + /** * Return true if the user provider has been set. * @@ -74,7 +83,7 @@ class UserAPI extends EventEmitter { * @throws Will throw an error if no user provider is set */ getCurrentUser() { - this._noProviderCheck(); + this.noProviderCheck(); return this._provider.getCurrentUser(); } @@ -105,7 +114,7 @@ class UserAPI extends EventEmitter { * @throws Will throw an error if no user provider is set */ hasRole(roleId) { - this._noProviderCheck(); + this.noProviderCheck(); return this._provider.hasRole(roleId); } @@ -116,9 +125,9 @@ class UserAPI extends EventEmitter { * @private * @throws Will throw an error if no user provider is set */ - _noProviderCheck() { + noProviderCheck() { if (!this.hasProvider()) { - this._error(NO_PROVIDER_ERROR); + this.error(NO_PROVIDER_ERROR); } } @@ -129,9 +138,26 @@ class UserAPI extends EventEmitter { * @param {string} error description of error * @throws Will throw error passed in */ - _error(error) { + error(error) { throw new Error(error); } } export default UserAPI; +/** + * @typedef {String} Role + */ +/** + * @typedef {Object} OpenMCT + */ +/** + * @typedef {{statusStyles: Object.}} UserAPIConfiguration + */ +/** + * @typedef {Object} StatusStyleDefinition + * @property {String} iconClass The icon class to apply to the status indicator when this status is active "icon-circle-slash", + * @property {String} iconClassPoll The icon class to apply to the poll question indicator when this style is active eg. "icon-status-poll-question-mark" + * @property {String} statusClass The class to apply to the indicator when this status is active eg. "s-status-error" + * @property {String} statusBgColor The background color to apply in the status summary section of the poll question popup for this status eg."#9900cc" + * @property {String} statusFgColor The foreground color to apply in the status summary section of the poll question popup for this status eg. "#fff" + */ diff --git a/src/api/user/UserProvider.js b/src/api/user/UserProvider.js new file mode 100644 index 0000000000..8502dd54e9 --- /dev/null +++ b/src/api/user/UserProvider.js @@ -0,0 +1,36 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ +export default class UserProvider { + /** + * @returns {Promise} A promise that resolves with the currently logged in user + */ + getCurrentUser() {} + /** + * @returns {Boolean} true if a user is currently logged in, otherwise false + */ + isLoggedIn() {} + /** + * @param {String} role + * @returns {Promise} true if the current user has the given role + */ + hasRole(role) {} +} diff --git a/src/api/user/UserStatusAPISpec.js b/src/api/user/UserStatusAPISpec.js new file mode 100644 index 0000000000..30df2820ce --- /dev/null +++ b/src/api/user/UserStatusAPISpec.js @@ -0,0 +1,103 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + +import { + createOpenMct, + resetApplicationState +} from '../../utils/testing'; + +describe("The User Status API", () => { + let openmct; + let userProvider; + let mockUser; + + beforeEach(() => { + userProvider = jasmine.createSpyObj("userProvider", [ + "setPollQuestion", + "getPollQuestion", + "getCurrentUser", + "getPossibleStatuses", + "getAllStatusRoles", + "canSetPollQuestion", + "isLoggedIn", + "on" + ]); + openmct = createOpenMct(); + mockUser = new openmct.user.User("test-user", "A test user"); + userProvider.getCurrentUser.and.returnValue(Promise.resolve(mockUser)); + userProvider.getPossibleStatuses.and.returnValue(Promise.resolve([])); + userProvider.getAllStatusRoles.and.returnValue(Promise.resolve([])); + userProvider.canSetPollQuestion.and.returnValue(Promise.resolve(false)); + userProvider.isLoggedIn.and.returnValue(true); + }); + + afterEach(() => { + return resetApplicationState(openmct); + }); + + describe("the poll question", () => { + it('can be set via a user status provider if supported', () => { + openmct.user.setProvider(userProvider); + userProvider.canSetPollQuestion.and.returnValue(Promise.resolve(true)); + + return openmct.user.status.setPollQuestion('This is a poll question').then(() => { + expect(userProvider.setPollQuestion).toHaveBeenCalledWith('This is a poll question'); + }); + }); + // fit('emits an event when the poll question changes', () => { + // const pollQuestionChangeCallback = jasmine.createSpy('pollQuestionChangeCallback'); + // let pollQuestionListener; + + // userProvider.canSetPollQuestion.and.returnValue(Promise.resolve(true)); + // userProvider.on.and.callFake((eventName, listener) => { + // if (eventName === 'pollQuestionChange') { + // pollQuestionListener = listener; + // } + // }); + + // openmct.user.on('pollQuestionChange', pollQuestionChangeCallback); + + // openmct.user.setProvider(userProvider); + + // return openmct.user.status.setPollQuestion('This is a poll question').then(() => { + // expect(pollQuestionListener).toBeDefined(); + // pollQuestionListener(); + // expect(pollQuestionChangeCallback).toHaveBeenCalled(); + + // const pollQuestion = pollQuestionChangeCallback.calls.mostRecent().args[0]; + // expect(pollQuestion.question).toBe('This is a poll question'); + + // openmct.user.off('pollQuestionChange', pollQuestionChangeCallback); + // }); + // }); + it('cannot be set if the user is not permitted', () => { + openmct.user.setProvider(userProvider); + userProvider.canSetPollQuestion.and.returnValue(Promise.resolve(false)); + + return openmct.user.status.setPollQuestion('This is a poll question').catch((error) => { + expect(error).toBeInstanceOf(Error); + }).finally(() => { + expect(userProvider.setPollQuestion).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/src/plugins/displayLayout/components/TelemetryView.vue b/src/plugins/displayLayout/components/TelemetryView.vue index 3a759db9df..6886baa52d 100644 --- a/src/plugins/displayLayout/components/TelemetryView.vue +++ b/src/plugins/displayLayout/components/TelemetryView.vue @@ -91,7 +91,7 @@ export default { width: DEFAULT_TELEMETRY_DIMENSIONS[0], height: DEFAULT_TELEMETRY_DIMENSIONS[1], displayMode: 'all', - value: metadata.getDefaultDisplayValue(), + value: metadata.getDefaultDisplayValue()?.key, stroke: "", fill: "", color: "", diff --git a/src/plugins/objectMigration/Migrations.js b/src/plugins/objectMigration/Migrations.js index 46bd8f3831..493845e1fc 100644 --- a/src/plugins/objectMigration/Migrations.js +++ b/src/plugins/objectMigration/Migrations.js @@ -145,7 +145,7 @@ define([ item.size = element.size || DEFAULT_SIZE; item.identifier = telemetryObjects[element.id].identifier; item.displayMode = element.titled ? 'all' : 'value'; - item.value = openmct.telemetry.getMetadata(telemetryObjects[element.id]).getDefaultDisplayValue(); + item.value = openmct.telemetry.getMetadata(telemetryObjects[element.id]).getDefaultDisplayValue()?.key; } else if (element.type === 'fixed.box') { item.type = "box-view"; item.stroke = element.stroke || DEFAULT_STROKE; diff --git a/src/plugins/operatorStatus/AbstractStatusIndicator.js b/src/plugins/operatorStatus/AbstractStatusIndicator.js new file mode 100644 index 0000000000..7d2a012938 --- /dev/null +++ b/src/plugins/operatorStatus/AbstractStatusIndicator.js @@ -0,0 +1,106 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ +import raf from '@/utils/raf'; + +export default class AbstractStatusIndicator { + #popupComponent; + #indicator; + #configuration; + + /** + * @param {*} openmct the Open MCT API (proper jsdoc to come) + * @param {import('@/api/user/UserAPI').UserAPIConfiguration} configuration Per-deployment status styling. See the type definition in UserAPI + */ + constructor(openmct, configuration) { + this.openmct = openmct; + this.#configuration = configuration; + + this.showPopup = this.showPopup.bind(this); + this.clearPopup = this.clearPopup.bind(this); + this.positionBox = this.positionBox.bind(this); + this.positionBox = raf(this.positionBox); + + this.#indicator = this.createIndicator(); + this.#popupComponent = this.createPopupComponent(); + } + + install() { + this.openmct.indicators.add(this.#indicator); + } + + showPopup() { + const popupElement = this.getPopupElement(); + + document.body.appendChild(popupElement.$el); + //Use capture so we don't trigger immediately on the same iteration of the event loop + document.addEventListener('click', this.clearPopup, { + capture: true + }); + + this.positionBox(); + + window.addEventListener('resize', this.positionBox); + } + + positionBox() { + const popupElement = this.getPopupElement(); + const indicator = this.getIndicator(); + + let indicatorBox = indicator.element.getBoundingClientRect(); + popupElement.positionX = indicatorBox.left; + popupElement.positionY = indicatorBox.bottom; + + const popupRight = popupElement.positionX + popupElement.$el.clientWidth; + const offsetLeft = Math.min(window.innerWidth - popupRight, 0); + popupElement.positionX = popupElement.positionX + offsetLeft; + } + + clearPopup(clickAwayEvent) { + const popupElement = this.getPopupElement(); + + if (!popupElement.$el.contains(clickAwayEvent.target)) { + popupElement.$el.remove(); + document.removeEventListener('click', this.clearPopup); + window.removeEventListener('resize', this.positionBox); + } + } + + createPopupComponent() { + throw new Error('Must override createPopupElement method'); + } + + getPopupElement() { + return this.#popupComponent; + } + + createIndicator() { + throw new Error('Must override createIndicator method'); + } + + getIndicator() { + return this.#indicator; + } + + getConfiguration() { + return this.#configuration; + } +} diff --git a/src/plugins/operatorStatus/operator-status.scss b/src/plugins/operatorStatus/operator-status.scss new file mode 100644 index 0000000000..144ffff402 --- /dev/null +++ b/src/plugins/operatorStatus/operator-status.scss @@ -0,0 +1,142 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + + $statusCountWidth: 30px; + +.c-status-poll-panel { + @include menuOuter(); + display: flex; + flex-direction: column; + padding: $interiorMarginLg; + min-width: 350px; + max-width: 35%; + + > * + * { + margin-top: $interiorMarginLg; + } + + *:before { + font-size: 0.8em; + margin-right: $interiorMarginSm; + } + + &__section { + display: flex; + align-items: center; + flex-direction: row; + + > * + * { + margin-left: $interiorMarginLg; + } + } + + &__top { + text-transform: uppercase; + } + + &__user-role, + &__updated { + opacity: 50%; + } + + &__updated { + flex: 1 1 auto; + text-align: right; + } + + &__poll-question { + background: $colorBodyFg; + color: $colorBodyBg; + border-radius: $controlCr; + font-weight: bold; + padding: $interiorMarginSm $interiorMargin; + + .c-status-poll-panel--admin & { + background: rgba($colorBodyFg, 0.1); + color: $colorBodyFg; + } + } + + /****** Admin interface */ + &__content { + $m: $interiorMargin; + display: grid; + grid-template-columns: min-content 1fr; + grid-column-gap: $m; + grid-row-gap: $m; + + [class*='__label'] { + padding: 3px 0; + } + + [class*='new-question'] { + align-items: center; + display: flex; + flex-direction: row; + > * + * { margin-left: $interiorMargin; } + + input { + flex: 1 1 auto; + height: $btnStdH; + } + + button { flex: 0 0 auto; } + } + } +} + +.c-status-poll-report { + display: flex; + flex-direction: row; + > * + * { margin-left: $interiorMargin; } + + &__count { + background: rgba($colorBodyFg, 0.2); + border-radius: $controlCr; + display: flex; + flex-direction: row; + font-size: 1.25em; + align-items: center; + padding: $interiorMarginSm $interiorMarginLg; + + &-type { + line-height: 1em; + opacity: 0.6; + } + } +} + +.c-indicator { + &:before { + // Indicator icon + color: $colorKey; + } + + &--operator-status { + cursor: pointer; + max-width: 150px; + + @include hover() { + background: $colorIndicatorBgHov; + } + } +} diff --git a/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue b/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue new file mode 100644 index 0000000000..e51f3d08d9 --- /dev/null +++ b/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue @@ -0,0 +1,187 @@ + + + + diff --git a/src/plugins/operatorStatus/operatorStatus/OperatorStatusIndicator.js b/src/plugins/operatorStatus/operatorStatus/OperatorStatusIndicator.js new file mode 100644 index 0000000000..9eb96e938c --- /dev/null +++ b/src/plugins/operatorStatus/operatorStatus/OperatorStatusIndicator.js @@ -0,0 +1,63 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ +import Vue from 'vue'; + +import AbstractStatusIndicator from '../AbstractStatusIndicator'; +import OperatorStatusComponent from './OperatorStatus.vue'; + +export default class OperatorStatusIndicator extends AbstractStatusIndicator { + createPopupComponent() { + const indicator = this.getIndicator(); + const popupElement = new Vue({ + components: { + OperatorStatus: OperatorStatusComponent + }, + provide: { + openmct: this.openmct, + indicator: indicator, + configuration: this.getConfiguration() + }, + data() { + return { + positionX: 0, + positionY: 0 + }; + }, + template: '' + }).$mount(); + + return popupElement; + } + + createIndicator() { + const operatorIndicator = this.openmct.indicators.simpleIndicator(); + + operatorIndicator.text("My Operator Status"); + operatorIndicator.description("Set my operator status"); + operatorIndicator.iconClass('icon-status-poll-question-mark'); + operatorIndicator.element.classList.add("c-indicator--operator-status"); + operatorIndicator.element.classList.add("no-minify"); + operatorIndicator.on('click', this.showPopup); + + return operatorIndicator; + } +} diff --git a/src/plugins/operatorStatus/plugin.js b/src/plugins/operatorStatus/plugin.js new file mode 100644 index 0000000000..3d449d1ebd --- /dev/null +++ b/src/plugins/operatorStatus/plugin.js @@ -0,0 +1,50 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ +import OperatorStatusIndicator from './operatorStatus/OperatorStatusIndicator'; +import PollQuestionIndicator from './pollQuestion/PollQuestionIndicator'; + +/** + * @param {import('@/api/user/UserAPI').UserAPIConfiguration} configuration + * @returns {function} The plugin install function + */ +export default function operatorStatusPlugin(configuration) { + return function install(openmct) { + + if (openmct.user.hasProvider()) { + openmct.user.status.canProvideStatusForCurrentUser().then(canProvideStatus => { + if (canProvideStatus) { + const operatorStatusIndicator = new OperatorStatusIndicator(openmct, configuration); + + operatorStatusIndicator.install(); + } + }); + + openmct.user.status.canSetPollQuestion().then(canSetPollQuestion => { + if (canSetPollQuestion) { + const pollQuestionIndicator = new PollQuestionIndicator(openmct, configuration); + + pollQuestionIndicator.install(); + } + }); + } + }; +} diff --git a/src/plugins/operatorStatus/pollQuestion/PollQuestion.vue b/src/plugins/operatorStatus/pollQuestion/PollQuestion.vue new file mode 100644 index 0000000000..f279e57975 --- /dev/null +++ b/src/plugins/operatorStatus/pollQuestion/PollQuestion.vue @@ -0,0 +1,184 @@ + + + + diff --git a/src/plugins/operatorStatus/pollQuestion/PollQuestionIndicator.js b/src/plugins/operatorStatus/pollQuestion/PollQuestionIndicator.js new file mode 100644 index 0000000000..ea85d5905d --- /dev/null +++ b/src/plugins/operatorStatus/pollQuestion/PollQuestionIndicator.js @@ -0,0 +1,63 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ +import Vue from 'vue'; + +import AbstractStatusIndicator from '../AbstractStatusIndicator'; +import PollQuestionComponent from './PollQuestion.vue'; + +export default class PollQuestionIndicator extends AbstractStatusIndicator { + createPopupComponent() { + const indicator = this.getIndicator(); + const pollQuestionElement = new Vue({ + components: { + PollQuestion: PollQuestionComponent + }, + provide: { + openmct: this.openmct, + indicator: indicator, + configuration: this.getConfiguration() + }, + data() { + return { + positionX: 0, + positionY: 0 + }; + }, + template: '' + }).$mount(); + + return pollQuestionElement; + } + + createIndicator() { + const pollQuestionIndicator = this.openmct.indicators.simpleIndicator(); + + pollQuestionIndicator.text("Poll Question"); + pollQuestionIndicator.description("Set the current poll question"); + pollQuestionIndicator.iconClass('icon-status-poll-edit'); + pollQuestionIndicator.element.classList.add("c-indicator--operator-status"); + pollQuestionIndicator.element.classList.add("no-minify"); + pollQuestionIndicator.on('click', this.showPopup); + + return pollQuestionIndicator; + } +} diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index 7beccebd54..e53ac68433 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -78,6 +78,7 @@ define([ './userIndicator/plugin', '../../example/exampleUser/plugin', './localStorage/plugin', + './operatorStatus/plugin', './gauge/GaugePlugin', './timelist/plugin' ], function ( @@ -138,6 +139,7 @@ define([ UserIndicator, ExampleUser, LocalStorage, + OperatorStatus, GaugePlugin, TimeList ) { @@ -217,6 +219,7 @@ define([ plugins.DeviceClassifier = DeviceClassifier.default; plugins.UserIndicator = UserIndicator.default; plugins.LocalStorage = LocalStorage.default; + plugins.OperatorStatus = OperatorStatus.default; plugins.Gauge = GaugePlugin.default; plugins.Timelist = TimeList.default; diff --git a/src/styles/_constants.scss b/src/styles/_constants.scss index 8d2d34179e..8b4994756e 100755 --- a/src/styles/_constants.scss +++ b/src/styles/_constants.scss @@ -156,6 +156,13 @@ $glyph-icon-notebook-page: '\e92c'; $glyph-icon-unlocked: '\e92d'; $glyph-icon-circle: '\e92e'; $glyph-icon-draft: '\e92f'; +$glyph-icon-circle-slash: '\e930'; +$glyph-icon-question-mark: '\e931'; +$glyph-icon-status-poll-check: '\e932'; +$glyph-icon-status-poll-caution: '\e933'; +$glyph-icon-status-poll-circle-slash: '\e934'; +$glyph-icon-status-poll-question-mark: '\e935'; +$glyph-icon-status-poll-edit: '\e936'; $glyph-icon-arrows-right-left: '\ea00'; $glyph-icon-arrows-up-down: '\ea01'; $glyph-icon-bullet: '\ea02'; @@ -264,6 +271,7 @@ $glyph-icon-bar-chart: '\eb2c'; $glyph-icon-map: '\eb2d'; $glyph-icon-plan: '\eb2e'; $glyph-icon-timelist: '\eb2f'; +$glyph-icon-notebook-shift-log: '\eb31'; $glyph-icon-plot-scatter: '\eb30'; /************************** GLYPHS AS DATA URI */ @@ -317,4 +325,5 @@ $bg-icon-bar-chart: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://w $bg-icon-map: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M448 32.7 384 64v448l64-31.3c35.2-17.21 64-60.1 64-95.3v-320c0-35.2-28.8-49.91-64-32.7ZM160 456l193.6 48.4v-448L160 8v448zM129.6.4 128 0 64 31.3C28.8 48.51 0 91.4 0 126.6v320c0 35.2 28.8 49.91 64 32.7l64-31.3 1.6.4Z'/%3e%3c/svg%3e"); $bg-icon-plan: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cg data-name='Layer 1'%3e%3cpath fill='%23000000' d='M128 96V64a64.19 64.19 0 0 1 64-64h128a64.19 64.19 0 0 1 64 64v32Z'/%3e%3cpath fill='%23000000' d='M416 64v64H96V64c-52.8 0-96 43.2-96 96v256c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V160c0-52.8-43.2-96-96-96ZM64 288v-64h128v64Zm256 128H128v-64h192Zm128 0h-64v-64h64Zm0-128H256v-64h192Z'/%3e%3c/g%3e%3c/g%3e%3c/svg%3e"); $bg-icon-timelist: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cpath d='M448 0H64A64.19 64.19 0 0 0 0 64v384a64.19 64.19 0 0 0 64 64h384a64.19 64.19 0 0 0 64-64V64a64.19 64.19 0 0 0-64-64ZM213.47 266.73a24 24 0 0 1-32.2 10.74L104 238.83V128a24 24 0 0 1 48 0v81.17l50.73 25.36a24 24 0 0 1 10.74 32.2ZM448 448H288v-64h160Zm0-96H288v-64h160Zm0-96H288v-64h160Zm0-96H288V96h160Z' data-name='Layer 1'/%3e%3c/g%3e%3c/svg%3e"); +$bg-icon-notebook-shift-log: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M448 55.36c0-39.95-27.69-63.66-61.54-52.68L0 128h448V55.36ZM448 160H0v288c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64ZM128 416H64v-64h64v64Zm0-96H64v-64h64v64Zm320 96H192v-64h256v64Zm0-96H192v-64h256v64Z'/%3e%3c/svg%3e"); $bg-icon-plot-scatter: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cg data-name='Layer 2'%3e%3cpath d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96ZM64 176a48 48 0 1 1 48 48 48 48 0 0 1-48-48Zm80 240a48 48 0 1 1 48-48 48 48 0 0 1-48 48Zm128-96a48 48 0 1 1 48-48 48 48 0 0 1-48 48Zm0-160a48 48 0 1 1 48-48 48 48 0 0 1-48 48Zm128 256a48 48 0 1 1 48-48 48 48 0 0 1-48 48Z' data-name='Layer 1'/%3e%3c/g%3e%3c/svg%3e"); diff --git a/src/styles/_glyphs.scss b/src/styles/_glyphs.scss index ac82d60060..80b43eb3de 100755 --- a/src/styles/_glyphs.scss +++ b/src/styles/_glyphs.scss @@ -87,6 +87,13 @@ .icon-unlocked { @include glyphBefore($glyph-icon-unlocked); } .icon-circle { @include glyphBefore($glyph-icon-circle); } .icon-draft { @include glyphBefore($glyph-icon-draft); } +.icon-question-mark { @include glyphBefore($glyph-icon-question-mark); } +.icon-circle-slash { @include glyphBefore($glyph-icon-circle-slash); } +.icon-status-poll-check { @include glyphBefore($glyph-icon-status-poll-check); } +.icon-status-poll-caution { @include glyphBefore($glyph-icon-status-poll-caution); } +.icon-status-poll-circle-slash { @include glyphBefore($glyph-icon-status-poll-circle-slash); } +.icon-status-poll-question-mark { @include glyphBefore($glyph-icon-status-poll-question-mark); } +.icon-status-poll-edit { @include glyphBefore($glyph-icon-status-poll-edit); } .icon-arrows-right-left { @include glyphBefore($glyph-icon-arrows-right-left); } .icon-arrows-up-down { @include glyphBefore($glyph-icon-arrows-up-down); } .icon-bullet { @include glyphBefore($glyph-icon-bullet); } @@ -195,6 +202,7 @@ .icon-map { @include glyphBefore($glyph-icon-map); } .icon-plan { @include glyphBefore($glyph-icon-plan); } .icon-timelist { @include glyphBefore($glyph-icon-timelist); } +.icon-notebook-shift-log { @include glyphBefore($glyph-icon-notebook-shift-log); } .icon-plot-scatter { @include glyphBefore($glyph-icon-plot-scatter); } /************************** 12 PX CLASSES */ @@ -256,4 +264,5 @@ .bg-icon-map { @include glyphBg($bg-icon-map); } .bg-icon-plan { @include glyphBg($bg-icon-plan); } .bg-icon-timelist { @include glyphBg($bg-icon-timelist); } +.bg-icon-notebook-shift-log { @include glyphBg($bg-icon-notebook-shift-log); } .bg-icon-plot-scatter { @include glyphBg($bg-icon-plot-scatter); } diff --git a/src/styles/fonts/Open MCT Symbols 16px.json b/src/styles/fonts/Open MCT Symbols 16px.json index 7e5d8a02a9..11cc387378 100644 --- a/src/styles/fonts/Open MCT Symbols 16px.json +++ b/src/styles/fonts/Open MCT Symbols 16px.json @@ -2,7 +2,7 @@ "metadata": { "name": "Open MCT Symbols 16px", "lastOpened": 0, - "created": 1650916650636 + "created": 1651949568729 }, "iconSets": [ { @@ -391,13 +391,69 @@ "code": 59695, "tempChar": "" }, + { + "order": 212, + "id": 183, + "name": "icon-circle-slash", + "prevSize": 16, + "code": 59696, + "tempChar": "" + }, + { + "order": 213, + "id": 182, + "name": "icon-question-mark", + "prevSize": 16, + "code": 59697, + "tempChar": "" + }, + { + "order": 206, + "id": 179, + "name": "icon-status-poll-check", + "prevSize": 16, + "code": 59698, + "tempChar": "" + }, + { + "order": 207, + "id": 178, + "name": "icon-status-poll-caution", + "prevSize": 16, + "code": 59699, + "tempChar": "" + }, + { + "order": 210, + "id": 180, + "name": "icon-status-poll-circle-slash", + "prevSize": 16, + "code": 59700, + "tempChar": "" + }, + { + "order": 211, + "id": 181, + "name": "icon-status-poll-question-mark", + "prevSize": 16, + "code": 59701, + "tempChar": "" + }, + { + "order": 209, + "id": 176, + "name": "icon-status-poll-edit", + "prevSize": 16, + "code": 59702, + "tempChar": "" + }, { "order": 27, "id": 105, "name": "icon-arrows-right-left", "prevSize": 16, "code": 59904, - "tempChar": "" + "tempChar": "" }, { "order": 26, @@ -405,7 +461,7 @@ "name": "icon-arrows-up-down", "prevSize": 16, "code": 59905, - "tempChar": "" + "tempChar": "" }, { "order": 68, @@ -413,7 +469,7 @@ "name": "icon-bullet", "prevSize": 16, "code": 59906, - "tempChar": "" + "tempChar": "" }, { "order": 150, @@ -421,7 +477,7 @@ "prevSize": 16, "code": 59907, "name": "icon-calendar", - "tempChar": "" + "tempChar": "" }, { "order": 45, @@ -429,7 +485,7 @@ "name": "icon-chain-links", "prevSize": 16, "code": 59908, - "tempChar": "" + "tempChar": "" }, { "order": 73, @@ -437,7 +493,7 @@ "name": "icon-download", "prevSize": 16, "code": 59909, - "tempChar": "" + "tempChar": "" }, { "order": 39, @@ -445,7 +501,7 @@ "name": "icon-duplicate", "prevSize": 16, "code": 59910, - "tempChar": "" + "tempChar": "" }, { "order": 50, @@ -453,7 +509,7 @@ "name": "icon-folder-new", "prevSize": 16, "code": 59911, - "tempChar": "" + "tempChar": "" }, { "order": 138, @@ -461,7 +517,7 @@ "name": "icon-fullscreen-collapse", "prevSize": 16, "code": 59912, - "tempChar": "" + "tempChar": "" }, { "order": 139, @@ -469,7 +525,7 @@ "name": "icon-fullscreen-expand", "prevSize": 16, "code": 59913, - "tempChar": "" + "tempChar": "" }, { "order": 122, @@ -477,7 +533,7 @@ "name": "icon-layers", "prevSize": 16, "code": 59914, - "tempChar": "" + "tempChar": "" }, { "order": 151, @@ -485,7 +541,7 @@ "name": "icon-line-horz", "prevSize": 16, "code": 59915, - "tempChar": "" + "tempChar": "" }, { "order": 100, @@ -493,7 +549,7 @@ "name": "icon-magnify", "prevSize": 16, "code": 59916, - "tempChar": "" + "tempChar": "" }, { "order": 99, @@ -501,7 +557,7 @@ "name": "icon-magnify-in", "prevSize": 16, "code": 59917, - "tempChar": "" + "tempChar": "" }, { "order": 101, @@ -509,7 +565,7 @@ "name": "icon-magnify-out-v2", "prevSize": 16, "code": 59918, - "tempChar": "" + "tempChar": "" }, { "order": 103, @@ -517,7 +573,7 @@ "name": "icon-menu", "prevSize": 16, "code": 59919, - "tempChar": "" + "tempChar": "" }, { "order": 124, @@ -525,7 +581,7 @@ "name": "icon-move", "prevSize": 16, "code": 59920, - "tempChar": "" + "tempChar": "" }, { "order": 7, @@ -533,7 +589,7 @@ "name": "icon-new-window", "prevSize": 16, "code": 59921, - "tempChar": "" + "tempChar": "" }, { "order": 63, @@ -541,7 +597,7 @@ "name": "icon-paint-bucket-v2", "prevSize": 16, "code": 59922, - "tempChar": "" + "tempChar": "" }, { "order": 15, @@ -549,7 +605,7 @@ "name": "icon-pencil", "prevSize": 16, "code": 59923, - "tempChar": "" + "tempChar": "" }, { "order": 54, @@ -557,7 +613,7 @@ "name": "icon-pencil-edit-in-place", "prevSize": 16, "code": 59924, - "tempChar": "" + "tempChar": "" }, { "order": 40, @@ -565,7 +621,7 @@ "name": "icon-play", "prevSize": 16, "code": 59925, - "tempChar": "" + "tempChar": "" }, { "order": 125, @@ -573,7 +629,7 @@ "name": "icon-pause", "prevSize": 16, "code": 59926, - "tempChar": "" + "tempChar": "" }, { "order": 119, @@ -581,7 +637,7 @@ "name": "icon-plot-resource", "prevSize": 16, "code": 59927, - "tempChar": "" + "tempChar": "" }, { "order": 48, @@ -589,7 +645,7 @@ "name": "icon-pointer-left", "prevSize": 16, "code": 59928, - "tempChar": "" + "tempChar": "" }, { "order": 47, @@ -597,7 +653,7 @@ "name": "icon-pointer-right", "prevSize": 16, "code": 59929, - "tempChar": "" + "tempChar": "" }, { "order": 85, @@ -605,7 +661,7 @@ "name": "icon-refresh", "prevSize": 16, "code": 59930, - "tempChar": "" + "tempChar": "" }, { "order": 55, @@ -613,7 +669,7 @@ "name": "icon-save", "prevSize": 16, "code": 59931, - "tempChar": "" + "tempChar": "" }, { "order": 56, @@ -621,7 +677,7 @@ "name": "icon-save-as", "prevSize": 16, "code": 59932, - "tempChar": "" + "tempChar": "" }, { "order": 58, @@ -629,7 +685,7 @@ "name": "icon-sine", "prevSize": 16, "code": 59933, - "tempChar": "" + "tempChar": "" }, { "order": 113, @@ -637,7 +693,7 @@ "name": "icon-font", "prevSize": 16, "code": 59934, - "tempChar": "" + "tempChar": "" }, { "order": 41, @@ -645,7 +701,7 @@ "name": "icon-thumbs-strip", "prevSize": 16, "code": 59935, - "tempChar": "" + "tempChar": "" }, { "order": 146, @@ -653,7 +709,7 @@ "name": "icon-two-parts-both", "prevSize": 16, "code": 59936, - "tempChar": "" + "tempChar": "" }, { "order": 145, @@ -661,7 +717,7 @@ "name": "icon-two-parts-one-only", "prevSize": 16, "code": 59937, - "tempChar": "" + "tempChar": "" }, { "order": 82, @@ -669,7 +725,7 @@ "name": "icon-resync", "prevSize": 16, "code": 59938, - "tempChar": "" + "tempChar": "" }, { "order": 86, @@ -677,7 +733,7 @@ "name": "icon-reset", "prevSize": 16, "code": 59939, - "tempChar": "" + "tempChar": "" }, { "order": 61, @@ -685,7 +741,7 @@ "name": "icon-x-in-circle", "prevSize": 16, "code": 59940, - "tempChar": "" + "tempChar": "" }, { "order": 84, @@ -693,7 +749,7 @@ "name": "icon-brightness", "prevSize": 16, "code": 59941, - "tempChar": "" + "tempChar": "" }, { "order": 83, @@ -701,7 +757,7 @@ "name": "icon-contrast", "prevSize": 16, "code": 59942, - "tempChar": "" + "tempChar": "" }, { "order": 87, @@ -709,7 +765,7 @@ "name": "icon-expand", "prevSize": 16, "code": 59943, - "tempChar": "" + "tempChar": "" }, { "order": 89, @@ -717,7 +773,7 @@ "name": "icon-list-view", "prevSize": 16, "code": 59944, - "tempChar": "" + "tempChar": "" }, { "order": 133, @@ -725,7 +781,7 @@ "name": "icon-grid-snap-to", "prevSize": 16, "code": 59945, - "tempChar": "" + "tempChar": "" }, { "order": 132, @@ -733,7 +789,7 @@ "name": "icon-grid-snap-no", "prevSize": 16, "code": 59946, - "tempChar": "" + "tempChar": "" }, { "order": 94, @@ -741,7 +797,7 @@ "name": "icon-frame-show", "prevSize": 16, "code": 59947, - "tempChar": "" + "tempChar": "" }, { "order": 95, @@ -749,7 +805,7 @@ "name": "icon-frame-hide", "prevSize": 16, "code": 59948, - "tempChar": "" + "tempChar": "" }, { "order": 97, @@ -757,7 +813,7 @@ "name": "icon-import", "prevSize": 16, "code": 59949, - "tempChar": "" + "tempChar": "" }, { "order": 96, @@ -765,7 +821,7 @@ "name": "icon-export", "prevSize": 16, "code": 59950, - "tempChar": "" + "tempChar": "" }, { "order": 194, @@ -773,7 +829,7 @@ "name": "icon-font-size", "prevSize": 16, "code": 59951, - "tempChar": "" + "tempChar": "" }, { "order": 163, @@ -781,7 +837,7 @@ "name": "icon-clear-data", "prevSize": 16, "code": 59952, - "tempChar": "" + "tempChar": "" }, { "order": 173, @@ -789,7 +845,7 @@ "name": "icon-history", "prevSize": 16, "code": 59953, - "tempChar": "" + "tempChar": "" }, { "order": 181, @@ -797,7 +853,7 @@ "name": "icon-arrow-up-to-parent", "prevSize": 16, "code": 59954, - "tempChar": "" + "tempChar": "" }, { "order": 184, @@ -805,7 +861,7 @@ "name": "icon-crosshair-in-circle", "prevSize": 16, "code": 59955, - "tempChar": "" + "tempChar": "" }, { "order": 185, @@ -813,7 +869,7 @@ "name": "icon-target", "prevSize": 16, "code": 59956, - "tempChar": "" + "tempChar": "" }, { "order": 187, @@ -821,7 +877,7 @@ "name": "icon-items-collapse", "prevSize": 16, "code": 59957, - "tempChar": "" + "tempChar": "" }, { "order": 188, @@ -829,7 +885,7 @@ "name": "icon-items-expand", "prevSize": 16, "code": 59958, - "tempChar": "" + "tempChar": "" }, { "order": 190, @@ -837,7 +893,7 @@ "name": "icon-3-dots", "prevSize": 16, "code": 59959, - "tempChar": "" + "tempChar": "" }, { "order": 193, @@ -845,7 +901,7 @@ "name": "icon-grid-on", "prevSize": 16, "code": 59960, - "tempChar": "" + "tempChar": "" }, { "order": 192, @@ -853,7 +909,7 @@ "name": "icon-grid-off", "prevSize": 16, "code": 59961, - "tempChar": "" + "tempChar": "" }, { "order": 191, @@ -861,7 +917,7 @@ "name": "icon-camera", "prevSize": 16, "code": 59962, - "tempChar": "" + "tempChar": "" }, { "order": 196, @@ -869,7 +925,7 @@ "name": "icon-folders-collapse", "prevSize": 16, "code": 59963, - "tempChar": "" + "tempChar": "" }, { "order": 144, @@ -877,7 +933,7 @@ "name": "icon-activity", "prevSize": 16, "code": 60160, - "tempChar": "" + "tempChar": "" }, { "order": 104, @@ -885,7 +941,7 @@ "name": "icon-activity-mode", "prevSize": 16, "code": 60161, - "tempChar": "" + "tempChar": "" }, { "order": 137, @@ -893,7 +949,7 @@ "name": "icon-autoflow-tabular", "prevSize": 16, "code": 60162, - "tempChar": "" + "tempChar": "" }, { "order": 115, @@ -901,7 +957,7 @@ "name": "icon-clock", "prevSize": 16, "code": 60163, - "tempChar": "" + "tempChar": "" }, { "order": 2, @@ -909,7 +965,7 @@ "name": "icon-database", "prevSize": 16, "code": 60164, - "tempChar": "" + "tempChar": "" }, { "order": 3, @@ -917,7 +973,7 @@ "name": "icon-database-query", "prevSize": 16, "code": 60165, - "tempChar": "" + "tempChar": "" }, { "order": 67, @@ -925,7 +981,7 @@ "name": "icon-dataset", "prevSize": 16, "code": 60166, - "tempChar": "" + "tempChar": "" }, { "order": 59, @@ -933,7 +989,7 @@ "name": "icon-datatable", "prevSize": 16, "code": 60167, - "tempChar": "" + "tempChar": "" }, { "order": 136, @@ -941,7 +997,7 @@ "name": "icon-dictionary", "prevSize": 16, "code": 60168, - "tempChar": "" + "tempChar": "" }, { "order": 51, @@ -949,7 +1005,7 @@ "name": "icon-folder", "prevSize": 16, "code": 60169, - "tempChar": "" + "tempChar": "" }, { "order": 147, @@ -957,7 +1013,7 @@ "name": "icon-image", "prevSize": 16, "code": 60170, - "tempChar": "" + "tempChar": "" }, { "order": 4, @@ -965,7 +1021,7 @@ "name": "icon-layout", "prevSize": 16, "code": 60171, - "tempChar": "" + "tempChar": "" }, { "order": 24, @@ -973,7 +1029,7 @@ "name": "icon-object", "prevSize": 16, "code": 60172, - "tempChar": "" + "tempChar": "" }, { "order": 52, @@ -981,7 +1037,7 @@ "name": "icon-object-unknown", "prevSize": 16, "code": 60173, - "tempChar": "" + "tempChar": "" }, { "order": 105, @@ -989,7 +1045,7 @@ "name": "icon-packet", "prevSize": 16, "code": 60174, - "tempChar": "" + "tempChar": "" }, { "order": 126, @@ -997,7 +1053,7 @@ "name": "icon-page", "prevSize": 16, "code": 60175, - "tempChar": "" + "tempChar": "" }, { "order": 130, @@ -1005,7 +1061,7 @@ "name": "icon-plot-overlay", "prevSize": 16, "code": 60176, - "tempChar": "" + "tempChar": "" }, { "order": 80, @@ -1013,7 +1069,7 @@ "name": "icon-plot-stacked", "prevSize": 16, "code": 60177, - "tempChar": "" + "tempChar": "" }, { "order": 134, @@ -1021,7 +1077,7 @@ "name": "icon-session", "prevSize": 16, "code": 60178, - "tempChar": "" + "tempChar": "" }, { "order": 109, @@ -1029,7 +1085,7 @@ "name": "icon-tabular", "prevSize": 16, "code": 60179, - "tempChar": "" + "tempChar": "" }, { "order": 107, @@ -1037,7 +1093,7 @@ "name": "icon-tabular-lad", "prevSize": 16, "code": 60180, - "tempChar": "" + "tempChar": "" }, { "order": 106, @@ -1045,7 +1101,7 @@ "name": "icon-tabular-lad-set", "prevSize": 16, "code": 60181, - "tempChar": "" + "tempChar": "" }, { "order": 70, @@ -1053,7 +1109,7 @@ "name": "icon-tabular-realtime", "prevSize": 16, "code": 60182, - "tempChar": "" + "tempChar": "" }, { "order": 60, @@ -1061,7 +1117,7 @@ "name": "icon-tabular-scrolling", "prevSize": 16, "code": 60183, - "tempChar": "" + "tempChar": "" }, { "order": 131, @@ -1069,7 +1125,7 @@ "name": "icon-telemetry", "prevSize": 16, "code": 60184, - "tempChar": "" + "tempChar": "" }, { "order": 202, @@ -1077,7 +1133,7 @@ "name": "icon-timeline", "prevSize": 16, "code": 60185, - "tempChar": "" + "tempChar": "" }, { "order": 81, @@ -1085,7 +1141,7 @@ "name": "icon-timer", "prevSize": 16, "code": 60186, - "tempChar": "" + "tempChar": "" }, { "order": 69, @@ -1093,7 +1149,7 @@ "name": "icon-topic", "prevSize": 16, "code": 60187, - "tempChar": "" + "tempChar": "" }, { "order": 79, @@ -1101,7 +1157,7 @@ "name": "icon-box-with-dashed-lines-v2", "prevSize": 16, "code": 60188, - "tempChar": "" + "tempChar": "" }, { "order": 90, @@ -1109,7 +1165,7 @@ "name": "icon-summary-widget", "prevSize": 16, "code": 60189, - "tempChar": "" + "tempChar": "" }, { "order": 92, @@ -1117,7 +1173,7 @@ "name": "icon-notebook", "prevSize": 16, "code": 60190, - "tempChar": "" + "tempChar": "" }, { "order": 168, @@ -1125,7 +1181,7 @@ "name": "icon-tabs-view", "prevSize": 16, "code": 60191, - "tempChar": "" + "tempChar": "" }, { "order": 117, @@ -1133,7 +1189,7 @@ "name": "icon-flexible-layout", "prevSize": 16, "code": 60192, - "tempChar": "" + "tempChar": "" }, { "order": 166, @@ -1141,7 +1197,7 @@ "name": "icon-generator-sine", "prevSize": 16, "code": 60193, - "tempChar": "" + "tempChar": "" }, { "order": 167, @@ -1149,7 +1205,7 @@ "name": "icon-generator-event", "prevSize": 16, "code": 60194, - "tempChar": "" + "tempChar": "" }, { "order": 165, @@ -1157,7 +1213,7 @@ "name": "icon-gauge-v2", "prevSize": 16, "code": 60195, - "tempChar": "" + "tempChar": "" }, { "order": 170, @@ -1165,7 +1221,7 @@ "name": "icon-spectra", "prevSize": 16, "code": 60196, - "tempChar": "" + "tempChar": "" }, { "order": 171, @@ -1173,7 +1229,7 @@ "name": "icon-telemetry-spectra", "prevSize": 16, "code": 60197, - "tempChar": "" + "tempChar": "" }, { "order": 172, @@ -1181,7 +1237,7 @@ "name": "icon-pushbutton", "prevSize": 16, "code": 60198, - "tempChar": "" + "tempChar": "" }, { "order": 174, @@ -1189,7 +1245,7 @@ "name": "icon-conditional", "prevSize": 16, "code": 60199, - "tempChar": "" + "tempChar": "" }, { "order": 178, @@ -1197,7 +1253,7 @@ "name": "icon-condition-widget", "prevSize": 16, "code": 60200, - "tempChar": "" + "tempChar": "" }, { "order": 180, @@ -1205,7 +1261,7 @@ "name": "icon-alphanumeric", "prevSize": 16, "code": 60201, - "tempChar": "" + "tempChar": "" }, { "order": 183, @@ -1213,7 +1269,7 @@ "name": "icon-image-telemetry", "prevSize": 16, "code": 60202, - "tempChar": "" + "tempChar": "" }, { "order": 198, @@ -1221,7 +1277,7 @@ "name": "icon-telemetry-aggregate", "prevSize": 16, "code": 60203, - "tempChar": "" + "tempChar": "" }, { "order": 199, @@ -1229,7 +1285,7 @@ "name": "icon-bar-graph", "prevSize": 16, "code": 60204, - "tempChar": "" + "tempChar": "" }, { "order": 200, @@ -1237,7 +1293,7 @@ "name": "icon-map", "prevSize": 16, "code": 60205, - "tempChar": "" + "tempChar": "" }, { "order": 203, @@ -1245,7 +1301,7 @@ "name": "icon-plan", "prevSize": 16, "code": 60206, - "tempChar": "" + "tempChar": "" }, { "order": 204, @@ -1253,7 +1309,15 @@ "name": "icon-timelist", "prevSize": 16, "code": 60207, - "tempChar": "" + "tempChar": "" + }, + { + "order": 214, + "id": 184, + "name": "icon-notebook-restricted", + "prevSize": 16, + "code": 60209, + "tempChar": "" }, { "order": 205, @@ -2107,6 +2171,162 @@ ] } }, + { + "id": 183, + "paths": [ + "M512 0c-282.78 0-512 229.22-512 512s229.22 512 512 512 512-229.22 512-512-229.22-512-512-512zM263.1 263.1c66.48-66.48 154.88-103.1 248.9-103.1 66.74 0 130.64 18.48 185.9 52.96l-484.94 484.94c-34.5-55.24-52.96-119.16-52.96-185.9 0-94.020 36.62-182.42 103.1-248.9zM760.9 760.9c-66.48 66.48-154.88 103.1-248.9 103.1-66.74 0-130.64-18.48-185.9-52.96l484.94-484.94c34.5 55.24 52.96 119.16 52.96 185.9 0 94.020-36.62 182.42-103.1 248.9z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 16, + "tags": [ + "icon-circle-slash" + ], + "colorPermutations": { + "12552552551": [ + {} + ] + } + }, + { + "id": 182, + "paths": [ + "M136.86 52.26c54.080-34.82 120.58-52.26 199.44-52.26 103.6 0 189.7 24.76 258.24 74.28s102.82 122.88 102.82 220.060c0 59.6-14.86 109.8-44.58 150.6-17.38 24.76-50.76 56.4-100.14 94.9l-48.68 37.82c-26.54 20.64-44.14 44.7-52.82 72.2-5.5 17.44-8.46 44.48-8.92 81.14h-186.4c2.74-77.48 10.060-131 21.94-160.58s42.5-63.62 91.88-102.12l50.060-39.2c16.46-12.38 29.72-25.9 39.78-40.58 18.28-25.2 27.42-52.96 27.42-83.22 0-34.84-10.18-66.6-30.52-95.24-20.36-28.64-57.52-42.98-111.48-42.98s-90.68 17.66-112.88 52.96c-22.18 35.32-33.26 71.98-33.26 110.040h-198.76c5.5-130.64 51.12-223.24 136.86-277.82zM251.020 825.24h205.62v198.74h-205.62v-198.74z" + ], + "attrs": [ + {} + ], + "width": 697, + "isMulticolor": false, + "isMulticolor2": false, + "grid": 16, + "tags": [ + "icon-question-mark" + ], + "colorPermutations": { + "12552552551": [ + {} + ] + } + }, + { + "id": 179, + "paths": [ + "M512 0c-282.76 0-512 214.9-512 480 0 92.26 27.8 178.44 75.92 251.6l-75.92 292.4 313.5-101.42c61.040 24.1 128.12 37.42 198.5 37.42 282.76 0 512-214.9 512-480s-229.24-480-512-480zM768 448l-320 320-192-192v-192l192 192 320-320v192z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 16, + "tags": [ + "icon-status-poll-check" + ], + "colorPermutations": { + "12552552551": [ + {} + ] + } + }, + { + "id": 178, + "paths": [ + "M512 0c-282.76 0-512 214.9-512 480 0 92.26 27.8 178.44 75.92 251.6l-75.92 292.4 313.5-101.42c61.040 24.1 128.12 37.42 198.5 37.42 282.76 0 512-214.9 512-480s-229.24-480-512-480zM781.36 704h-538.72c-44.96 0-63.5-31.94-41.2-70.98l270-472.48c22.3-39.040 58.82-39.040 81.12 0l269.98 472.48c22.3 39.040 3.78 70.98-41.2 70.98z", + "M457.14 417.86l24.2 122.64h61.32l24.2-122.64v-163.5h-109.72v163.5z", + "M471.12 581.36h81.76v81.76h-81.76v-81.76z" + ], + "attrs": [ + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 16, + "tags": [ + "icon-status-poll-caution" + ], + "colorPermutations": { + "12552552551": [ + {}, + {}, + {} + ] + } + }, + { + "id": 180, + "paths": [ + "M391.18 668.7c35.72 22.98 77.32 35.3 120.82 35.3 59.84 0 116.080-23.3 158.4-65.6 42.3-42.3 65.6-98.56 65.6-158.4 0-43.5-12.32-85.080-35.3-120.82l-309.52 309.52z", + "M512 256c-59.84 0-116.080 23.3-158.4 65.6-42.3 42.3-65.6 98.56-65.6 158.4 0 43.5 12.32 85.080 35.3 120.82l309.52-309.52c-35.72-22.98-77.32-35.3-120.82-35.3z", + "M512 0c-282.76 0-512 214.9-512 480 0 92.26 27.8 178.44 75.92 251.6l-75.92 292.4 313.5-101.42c61.040 24.1 128.12 37.42 198.5 37.42 282.76 0 512-214.9 512-480s-229.24-480-512-480zM512 800c-176.74 0-320-143.26-320-320s143.26-320 320-320 320 143.26 320 320-143.26 320-320 320z" + ], + "attrs": [ + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 16, + "tags": [ + "icon-status-poll-circle-slash" + ], + "colorPermutations": { + "12552552551": [ + {}, + {}, + {} + ] + } + }, + { + "id": 181, + "paths": [ + "M512 0c-282.76 0-512 214.9-512 480 0 92.26 27.8 178.44 75.92 251.6l-75.92 292.4 313.5-101.42c61.040 24.1 128.12 37.42 198.5 37.42 282.76 0 512-214.9 512-480s-229.24-480-512-480zM579.020 832h-141.36v-136.64h141.36v136.64zM713.84 433.9c-11.94 17.020-34.9 38.78-68.84 65.24l-33.48 26c-18.24 14.18-30.34 30.74-36.32 49.64-3.78 11.98-5.82 30.58-6.14 55.8h-128.12c1.88-53.26 6.92-90.060 15.080-110.4 8.18-20.34 29.22-43.74 63.16-70.22l34.42-26.94c11.3-8.52 20.42-17.8 27.34-27.9 12.56-17.34 18.86-36.4 18.86-57.2 0-23.94-7-45.78-20.98-65.48-14-19.7-39.54-29.54-76.64-29.54s-62.34 12.14-77.6 36.4c-15.24 24.28-22.88 49.48-22.88 75.64h-136.64c3.78-89.84 35.14-153.5 94.080-191.020 37.18-23.94 82.9-35.94 137.12-35.94 71.22 0 130.42 17.020 177.54 51.060s70.68 84.48 70.68 151.3c0 40.98-10.22 75.5-30.66 103.54z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 16, + "tags": [ + "icon-status-poll-question-mark" + ], + "colorPermutations": { + "12552552551": [ + {} + ] + } + }, + { + "id": 176, + "paths": [ + "M1000.080 334.64l-336.6 336.76-20.52 6.88-450.96 153.72 160.68-471.52 332.34-332.34c-54.040-18.2-112.28-28.14-173.020-28.14-282.76 0-512 214.9-512 480 0 92.26 27.8 178.44 75.92 251.6l-75.92 292.4 313.5-101.42c61.040 24.1 128.12 37.42 198.5 37.42 282.76 0 512-214.9 512-480 0-50.68-8.4-99.5-23.92-145.36z", + "M408.42 395.24l-2.16 6.3-111.7 327.9 334.12-113.86 4.62-4.68 350.28-350.28c6.8-6.78 14.96-19.1 14.96-38.9 0-34.86-26.82-83.28-69.88-126.38-26.54-26.54-55.9-47.6-82.7-59.34-47.34-20.8-72.020-6.24-82.64 4.36l-354.9 354.88zM470.56 421.42h44v88h88v44l-4.7 12.72-139.68 47.54-47.94-47.94 47.6-139.72 12.72-4.6z" + ], + "attrs": [ + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 16, + "tags": [ + "icon-status-poll-edit" + ], + "colorPermutations": { + "12552552551": [ + {}, + {} + ] + } + }, { "id": 105, "paths": [ @@ -3326,15 +3546,21 @@ { "id": 76, "paths": [ - "M510-2l-512 320v384l512 320 512-320v-384l-512-320zM585.4 859.2c-21.2 20.8-46 30.8-76 30.8-31.2 0-56.2-9.8-76.2-29.6-20-20-29.6-44.8-29.6-76.2 0-30.4 10.2-55.2 31-76.2s45.2-31.2 74.8-31.2c29.6 0 54.2 10.4 75.6 32s31.8 46.4 31.8 76c-0.2 29-10.8 54-31.4 74.4zM638.2 546.6c-23.6 11.8-37.4 22-43.4 32.4-3.6 6.2-6 14.8-7.4 26.8v41h-161.4v-44.2c0-40.2 4.4-69.8 13-88 8-17.2 22.6-30.2 44.8-40l34.8-15.4c32-14.2 48.2-35.2 48.2-62.8 0-16-6-30.4-17.2-41.8-11.2-11.2-25.6-17.2-41.6-17.2-24 0-54.4 10-62.8 57.4l-2.2 12.2h-147l1.4-16.2c4-44.6 17-82.4 38.8-112.2 19.6-27 45.6-48.6 77-64.6s64.6-24 98.2-24c60.6 0 110.2 19.4 151.4 59.6 41.2 40 61.2 88 61.2 147.2 0 70.8-28.8 121.4-85.8 149.8z" + "M511.98 0l-511.98 320v384l512 320 512-320v-384l-512.020-320zM586.22 896h-141.36v-136.64h141.36v136.64zM721.040 497.9c-11.94 17.020-34.9 38.78-68.84 65.24l-33.48 26c-18.24 14.18-30.34 30.74-36.32 49.64-3.78 11.98-5.82 30.58-6.14 55.8h-128.12c1.88-53.26 6.92-90.060 15.080-110.4 8.18-20.34 29.22-43.74 63.16-70.22l34.42-26.94c11.3-8.52 20.42-17.8 27.34-27.9 12.56-17.34 18.86-36.4 18.86-57.2 0-23.94-7-45.78-20.98-65.48-14-19.7-39.54-29.54-76.64-29.54s-62.34 12.14-77.6 36.4c-15.24 24.28-22.88 49.48-22.88 75.64h-136.64c3.78-89.84 35.14-153.5 94.080-191.020 37.18-23.94 82.9-35.94 137.12-35.94 71.22 0 130.42 17.020 177.54 51.060s70.68 84.48 70.68 151.3c0 40.98-10.22 75.5-30.66 103.54z" + ], + "attrs": [ + {} ], - "attrs": [], "grid": 16, "tags": [ "icon-object-unknown" ], + "isMulticolor": false, + "isMulticolor2": false, "colorPermutations": { - "12552552551": [] + "12552552551": [ + {} + ] } }, { @@ -4009,6 +4235,29 @@ ] } }, + { + "id": 184, + "paths": [ + "M896 110.72c0-79.9-55.38-127.32-123.080-105.36l-772.92 250.64h896v-145.28z", + "M896 320h-896v576c0 70.4 57.6 128 128 128h768c70.4 0 128-57.6 128-128v-448c0-70.4-57.6-128-128-128zM256 832h-128v-128h128v128zM256 640h-128v-128h128v128zM896 832h-512v-128h512v128zM896 640h-512v-128h512v128z" + ], + "attrs": [ + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 16, + "tags": [ + "icon-notebook-restricted" + ], + "colorPermutations": { + "12552552551": [ + {}, + {} + ] + } + }, { "id": 176, "paths": [ diff --git a/src/styles/fonts/Open-MCT-Symbols-16px.svg b/src/styles/fonts/Open-MCT-Symbols-16px.svg index c6455e381c..38ce5985a3 100644 --- a/src/styles/fonts/Open-MCT-Symbols-16px.svg +++ b/src/styles/fonts/Open-MCT-Symbols-16px.svg @@ -3,165 +3,173 @@ Generated by IcoMoon - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/styles/fonts/Open-MCT-Symbols-16px.ttf b/src/styles/fonts/Open-MCT-Symbols-16px.ttf index 073e2c6ec556deac1f1e5c31eb9885033409d428..94ab53538b6ee4ee72e83b1e7a1afc6b78958239 100644 GIT binary patch delta 6255 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 diff --git a/src/styles/fonts/Open-MCT-Symbols-16px.woff b/src/styles/fonts/Open-MCT-Symbols-16px.woff index ca1cc26bede033633737bcd580ab3615e72ae99a..510d6e9a9b85dae9d62daa494a38a0bdad0131be 100644 GIT binary patch delta 6470 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 { + this.openmct.indicators.getIndicatorObjectsByPriority().forEach(this.addIndicator); + + this.openmct.indicators.on('addIndicator', this.addIndicator); + }, + methods: { + addIndicator(indicator) { this.$el.appendChild(indicator.element); - }); + } } + }; diff --git a/src/utils/raf.js b/src/utils/raf.js new file mode 100644 index 0000000000..d5c0c48fe5 --- /dev/null +++ b/src/utils/raf.js @@ -0,0 +1,14 @@ +export default function raf(callback) { + let rendering = false; + + return () => { + if (!rendering) { + rendering = true; + + requestAnimationFrame(() => { + callback(); + rendering = false; + }); + } + }; +} diff --git a/src/utils/rafSpec.js b/src/utils/rafSpec.js new file mode 100644 index 0000000000..0bf5ae9d9c --- /dev/null +++ b/src/utils/rafSpec.js @@ -0,0 +1,61 @@ +import raf from "./raf"; + +describe('The raf utility function', () => { + it('Throttles function calls that arrive in quick succession using Request Animation Frame', () => { + const unthrottledFunction = jasmine.createSpy('unthrottledFunction'); + const throttledCallback = jasmine.createSpy('throttledCallback'); + const throttledFunction = raf(throttledCallback); + + for (let i = 0; i < 10; i++) { + unthrottledFunction(); + throttledFunction(); + } + + return new Promise((resolve) => { + requestAnimationFrame(resolve); + }).then(() => { + expect(unthrottledFunction).toHaveBeenCalledTimes(10); + expect(throttledCallback).toHaveBeenCalledTimes(1); + }); + }); + it('Only invokes callback once per animation frame', () => { + const throttledCallback = jasmine.createSpy('throttledCallback'); + const throttledFunction = raf(throttledCallback); + + for (let i = 0; i < 10; i++) { + throttledFunction(); + } + + return new Promise(resolve => { + requestAnimationFrame(resolve); + }).then(() => { + return new Promise(resolve => { + requestAnimationFrame(resolve); + }); + }).then(() => { + expect(throttledCallback).toHaveBeenCalledTimes(1); + }); + }); + it('Invokes callback again if called in subsequent animation frame', () => { + const throttledCallback = jasmine.createSpy('throttledCallback'); + const throttledFunction = raf(throttledCallback); + + for (let i = 0; i < 10; i++) { + throttledFunction(); + } + + return new Promise(resolve => { + requestAnimationFrame(resolve); + }).then(() => { + for (let i = 0; i < 10; i++) { + throttledFunction(); + } + + return new Promise(resolve => { + requestAnimationFrame(resolve); + }); + }).then(() => { + expect(throttledCallback).toHaveBeenCalledTimes(2); + }); + }); +}); From 04ee6f49d623c88aab6169d6615c402574d12c57 Mon Sep 17 00:00:00 2001 From: Alize Nguyen Date: Thu, 2 Jun 2022 17:47:14 -0500 Subject: [PATCH 06/21] Remove all non legacy usage of zepto (#5159) * Removed Zepto * Added utility functions for compiling HTML templates and toggling classes on and off Co-authored-by: John Hill Co-authored-by: Andrew Henry --- package.json | 4 +- src/api/indicators/SimpleIndicator.js | 10 +- .../URLIndicatorPlugin/URLIndicator.js | 23 +-- .../URLIndicatorPlugin/URLIndicatorSpec.js | 41 ++--- .../autoflow/AutoflowTabularPluginSpec.js | 27 ++- .../licenses/third-party-licenses.json | 7 - src/plugins/summaryWidget/src/Condition.js | 41 +++-- .../summaryWidget/src/ConditionManager.js | 12 +- src/plugins/summaryWidget/src/Rule.js | 154 +++++++++++------- .../summaryWidget/src/SummaryWidget.js | 71 ++++---- src/plugins/summaryWidget/src/TestDataItem.js | 28 ++-- .../summaryWidget/src/TestDataManager.js | 18 +- src/plugins/summaryWidget/src/WidgetDnD.js | 27 +-- .../summaryWidget/src/input/ColorPalette.js | 21 +-- .../summaryWidget/src/input/IconPalette.js | 30 ++-- .../summaryWidget/src/input/Palette.js | 71 +++++--- src/plugins/summaryWidget/src/input/Select.js | 48 +++--- .../summaryWidget/test/ConditionSpec.js | 60 +++++-- src/plugins/summaryWidget/test/RuleSpec.js | 29 ++-- .../summaryWidget/test/SummaryWidgetSpec.js | 12 +- .../summaryWidget/test/TestDataItemSpec.js | 53 ++++-- .../summaryWidget/test/TestDataManagerSpec.js | 8 +- src/utils/template/templateHelpers.js | 14 ++ webpack.common.js | 7 - 24 files changed, 477 insertions(+), 339 deletions(-) create mode 100644 src/utils/template/templateHelpers.js diff --git a/package.json b/package.json index c1aac194e6..a62dd41924 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "eslint-plugin-vue": "8.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-saver": "2.0.5", "git-rev-sync": "3.0.2", @@ -74,8 +73,7 @@ "webpack-cli": "4.9.2", "webpack-dev-middleware": "5.3.3", "webpack-hot-middleware": "2.25.1", - "webpack-merge": "5.8.0", - "zepto": "1.2.0" + "webpack-merge": "5.8.0" }, "scripts": { "clean": "rm -rf ./dist ./node_modules ./package-lock.json", diff --git a/src/api/indicators/SimpleIndicator.js b/src/api/indicators/SimpleIndicator.js index 1ef99e6888..31ce745a52 100644 --- a/src/api/indicators/SimpleIndicator.js +++ b/src/api/indicators/SimpleIndicator.js @@ -22,6 +22,7 @@ import EventEmitter from 'EventEmitter'; import indicatorTemplate from './res/indicator-template.html'; +import { convertTemplateToHTML } from '@/utils/template/templateHelpers'; const DEFAULT_ICON_CLASS = 'icon-info'; @@ -30,7 +31,7 @@ class SimpleIndicator extends EventEmitter { super(); this.openmct = openmct; - this.element = compileTemplate(indicatorTemplate)[0]; + this.element = convertTemplateToHTML(indicatorTemplate)[0]; this.priority = openmct.priority.DEFAULT; this.textElement = this.element.querySelector('.js-indicator-text'); @@ -116,11 +117,4 @@ class SimpleIndicator extends EventEmitter { } } -function compileTemplate(htmlTemplate) { - const templateNode = document.createElement('template'); - templateNode.innerHTML = htmlTemplate; - - return templateNode.content.cloneNode(true).children; -} - export default SimpleIndicator; diff --git a/src/plugins/URLIndicatorPlugin/URLIndicator.js b/src/plugins/URLIndicatorPlugin/URLIndicator.js index 1bc83450e9..5a6785e54b 100644 --- a/src/plugins/URLIndicatorPlugin/URLIndicator.js +++ b/src/plugins/URLIndicatorPlugin/URLIndicator.js @@ -20,10 +20,8 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define( - ['zepto'], - function ($) { - +define([], + function () { // Set of connection states; changing among these states will be // reflected in the indicator's appearance. // CONNECTED: Everything nominal, expect to be able to read/write. @@ -75,12 +73,17 @@ define( }; URLIndicator.prototype.fetchUrl = function () { - $.ajax({ - type: 'GET', - url: this.URLpath, - success: this.handleSuccess, - error: this.handleError - }); + fetch(this.URLpath) + .then(response => { + if (response.ok) { + this.handleSuccess(); + } else { + this.handleError(); + } + }) + .catch(error => { + this.handleError(); + }); }; URLIndicator.prototype.handleError = function (e) { diff --git a/src/plugins/URLIndicatorPlugin/URLIndicatorSpec.js b/src/plugins/URLIndicatorPlugin/URLIndicatorSpec.js index 408a98cd09..cf6cd39ff6 100644 --- a/src/plugins/URLIndicatorPlugin/URLIndicatorSpec.js +++ b/src/plugins/URLIndicatorPlugin/URLIndicatorSpec.js @@ -25,37 +25,35 @@ define( "utils/testing", "./URLIndicator", "./URLIndicatorPlugin", - "../../MCT", - "zepto" + "../../MCT" ], function ( testingUtils, URLIndicator, URLIndicatorPlugin, - MCT, - $ + MCT ) { - const defaultAjaxFunction = $.ajax; - describe("The URLIndicator", function () { let openmct; let indicatorElement; let pluginOptions; - let ajaxOptions; let urlIndicator; // eslint-disable-line + let fetchSpy; beforeEach(function () { jasmine.clock().install(); openmct = new testingUtils.createOpenMct(); spyOn(openmct.indicators, 'add'); - spyOn($, 'ajax'); - $.ajax.and.callFake(function (options) { - ajaxOptions = options; - }); + fetchSpy = spyOn(window, 'fetch').and.callFake(() => Promise.resolve({ + ok: true + })); }); afterEach(function () { - $.ajax = defaultAjaxFunction; + if (window.fetch.restore) { + window.fetch.restore(); + } + jasmine.clock().uninstall(); return testingUtils.resetApplicationState(openmct); @@ -96,11 +94,11 @@ define( expect(indicatorElement.classList.contains('iconClass-checked')).toBe(true); }); it("uses custom interval", function () { - expect($.ajax.calls.count()).toEqual(1); + expect(window.fetch).toHaveBeenCalledTimes(1); jasmine.clock().tick(1); - expect($.ajax.calls.count()).toEqual(1); + expect(window.fetch).toHaveBeenCalledTimes(1); jasmine.clock().tick(pluginOptions.interval + 1); - expect($.ajax.calls.count()).toEqual(2); + expect(window.fetch).toHaveBeenCalledTimes(2); }); it("uses custom label if supplied in initialization", function () { expect(indicatorElement.textContent.indexOf(pluginOptions.label) >= 0).toBe(true); @@ -120,18 +118,21 @@ define( it("requests the provided URL", function () { jasmine.clock().tick(pluginOptions.interval + 1); - expect(ajaxOptions.url).toEqual(pluginOptions.url); + expect(window.fetch).toHaveBeenCalledWith(pluginOptions.url); }); - it("indicates success if connection is nominal", function () { + it("indicates success if connection is nominal", async function () { jasmine.clock().tick(pluginOptions.interval + 1); - ajaxOptions.success(); + await urlIndicator.fetchUrl(); expect(indicatorElement.classList.contains('s-status-on')).toBe(true); }); - it("indicates an error when the server cannot be reached", function () { + it("indicates an error when the server cannot be reached", async function () { + fetchSpy.and.callFake(() => Promise.resolve({ + ok: false + })); jasmine.clock().tick(pluginOptions.interval + 1); - ajaxOptions.error(); + await urlIndicator.fetchUrl(); expect(indicatorElement.classList.contains('s-status-warning-hi')).toBe(true); }); }); diff --git a/src/plugins/autoflow/AutoflowTabularPluginSpec.js b/src/plugins/autoflow/AutoflowTabularPluginSpec.js index ce20cab1d6..5e5d49489d 100644 --- a/src/plugins/autoflow/AutoflowTabularPluginSpec.js +++ b/src/plugins/autoflow/AutoflowTabularPluginSpec.js @@ -21,7 +21,6 @@ *****************************************************************************/ import AutoflowTabularPlugin from './AutoflowTabularPlugin'; import AutoflowTabularConstants from './AutoflowTabularConstants'; -import $ from 'zepto'; import DOMObserver from './dom-observer'; import { createOpenMct, @@ -122,7 +121,7 @@ xdescribe("AutoflowTabularPlugin", () => { name: "Object " + key }; }); - testContainer = $('
')[0]; + testContainer = document.createElement('div'); domObserver = new DOMObserver(testContainer); testHistories = testKeys.reduce((histories, key, index) => { @@ -195,7 +194,7 @@ xdescribe("AutoflowTabularPlugin", () => { describe("when rows have been populated", () => { function rowsMatch() { - const rows = $(testContainer).find(".l-autoflow-row").length; + const rows = testContainer.querySelectorAll(".l-autoflow-row").length; return rows === testChildren.length; } @@ -241,20 +240,20 @@ xdescribe("AutoflowTabularPlugin", () => { const nextWidth = initialWidth + AutoflowTabularConstants.COLUMN_WIDTH_STEP; - expect($(testContainer).find('.l-autoflow-col').css('width')) + expect(testContainer.querySelector('.l-autoflow-col').css('width')) .toEqual(initialWidth + 'px'); - $(testContainer).find('.change-column-width').click(); + testContainer.querySelector('.change-column-width').click(); function widthHasChanged() { - const width = $(testContainer).find('.l-autoflow-col').css('width'); + const width = testContainer.querySelector('.l-autoflow-col').css('width'); return width !== initialWidth + 'px'; } return domObserver.when(widthHasChanged) .then(() => { - expect($(testContainer).find('.l-autoflow-col').css('width')) + expect(testContainer.querySelector('.l-autoflow-col').css('width')) .toEqual(nextWidth + 'px'); }); }); @@ -267,13 +266,13 @@ xdescribe("AutoflowTabularPlugin", () => { it("displays historical telemetry", () => { function rowTextDefined() { - return $(testContainer).find(".l-autoflow-item").filter(".r").text() !== ""; + return testContainer.querySelector(".l-autoflow-item").filter(".r").text() !== ""; } return domObserver.when(rowTextDefined).then(() => { testKeys.forEach((key, index) => { const datum = testHistories[key]; - const $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r"); + const $cell = testContainer.querySelector(".l-autoflow-row").eq(index).find(".r"); expect($cell.text()).toEqual(String(datum.range)); }); }); @@ -294,7 +293,7 @@ xdescribe("AutoflowTabularPlugin", () => { return waitsForChange().then(() => { testData.forEach((datum, index) => { - const $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r"); + const $cell = testContainer.querySelector(".l-autoflow-row").eq(index).find(".r"); expect($cell.text()).toEqual(String(datum.range)); }); }); @@ -312,7 +311,7 @@ xdescribe("AutoflowTabularPlugin", () => { return waitsForChange().then(() => { testKeys.forEach((datum, index) => { - const $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r"); + const $cell = testContainer.querySelector(".l-autoflow-row").eq(index).find(".r"); expect($cell.hasClass(testClass)).toBe(true); }); }); @@ -322,16 +321,16 @@ xdescribe("AutoflowTabularPlugin", () => { const rowHeight = AutoflowTabularConstants.ROW_HEIGHT; const sliderHeight = AutoflowTabularConstants.SLIDER_HEIGHT; const count = testKeys.length; - const $container = $(testContainer); + const $container = testContainer; let promiseChain = Promise.resolve(); function columnsHaveAutoflowed() { - const itemsHeight = $container.find('.l-autoflow-items').height(); + const itemsHeight = $container.querySelector('.l-autoflow-items').height(); const availableHeight = itemsHeight - sliderHeight; const availableRows = Math.max(Math.floor(availableHeight / rowHeight), 1); const columns = Math.ceil(count / availableRows); - return $container.find('.l-autoflow-col').length === columns; + return $container.querySelector('.l-autoflow-col').length === columns; } $container.find('.abs').css({ diff --git a/src/plugins/licenses/third-party-licenses.json b/src/plugins/licenses/third-party-licenses.json index 184024eb41..c139298ce1 100644 --- a/src/plugins/licenses/third-party-licenses.json +++ b/src/plugins/licenses/third-party-licenses.json @@ -256,13 +256,6 @@ "licenseFile": "/Users/akhenry/Code/licenses/node_modules/vue/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2013-present, Yuxi (Evan) You\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.", "copyright": "Copyright (c) 2013-present, Yuxi (Evan) You" - }, - "zepto@1.2.0": { - "licenses": "MIT", - "repository": "https://github.com/madrobby/zepto", - "path": "/Users/akhenry/Code/licenses/node_modules/zepto", - "licenseFile": "/Users/akhenry/Code/licenses/node_modules/zepto/README.md", - "licenseText": "Copyright (c) 2010-2018 Thomas Fuchs\nhttp://zeptojs.com/\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." } } diff --git a/src/plugins/summaryWidget/src/Condition.js b/src/plugins/summaryWidget/src/Condition.js index 997ad67b4f..66f9ecbeb2 100644 --- a/src/plugins/summaryWidget/src/Condition.js +++ b/src/plugins/summaryWidget/src/Condition.js @@ -4,16 +4,16 @@ define([ './input/KeySelect', './input/OperationSelect', './eventHelpers', - 'EventEmitter', - 'zepto' + '../../../utils/template/templateHelpers', + 'EventEmitter' ], function ( conditionTemplate, ObjectSelect, KeySelect, OperationSelect, eventHelpers, - EventEmitter, - $ + templateHelpers, + EventEmitter ) { /** * Represents an individual condition for a summary widget rule. Manages the @@ -31,12 +31,13 @@ define([ this.index = index; this.conditionManager = conditionManager; - this.domElement = $(conditionTemplate); + this.domElement = templateHelpers.convertTemplateToHTML(conditionTemplate)[0]; + this.eventEmitter = new EventEmitter(); this.supportedCallbacks = ['remove', 'duplicate', 'change']; - this.deleteButton = $('.t-delete', this.domElement); - this.duplicateButton = $('.t-duplicate', this.domElement); + this.deleteButton = this.domElement.querySelector('.t-delete'); + this.duplicateButton = this.domElement.querySelector('.t-duplicate'); this.selects = {}; this.valueInputs = []; @@ -105,9 +106,10 @@ define([ }); Object.values(this.selects).forEach(function (select) { - $('.t-configuration', self.domElement).append(select.getDOM()); + self.domElement.querySelector('.t-configuration').append(select.getDOM()); }); - this.listenTo($('.t-value-inputs', this.domElement), 'input', onValueInput); + + this.listenTo(this.domElement.querySelector('.t-value-inputs'), 'input', onValueInput); } Condition.prototype.getDOM = function (container) { @@ -132,7 +134,7 @@ define([ * Hide the appropriate inputs when this is the only condition */ Condition.prototype.hideButtons = function () { - this.deleteButton.hide(); + this.deleteButton.style.display = 'none'; }; /** @@ -172,14 +174,14 @@ define([ */ Condition.prototype.generateValueInputs = function (operation) { const evaluator = this.conditionManager.getEvaluator(); - const inputArea = $('.t-value-inputs', this.domElement); + const inputArea = this.domElement.querySelector('.t-value-inputs'); let inputCount; let inputType; let newInput; let index = 0; let emitChange = false; - inputArea.html(''); + inputArea.innerHTML = ''; this.valueInputs = []; this.config.values = this.config.values || []; @@ -189,17 +191,24 @@ define([ while (index < inputCount) { if (inputType === 'select') { - newInput = $(''); + const options = this.generateSelectOptions(); + + newInput = document.createElement("select"); + newInput.innerHTML = options; + emitChange = true; } else { const defaultValue = inputType === 'number' ? 0 : ''; const value = this.config.values[index] || defaultValue; this.config.values[index] = value; - newInput = $(''); + + newInput = document.createElement("input"); + newInput.type = `${inputType}`; + newInput.value = `${value}`; } - this.valueInputs.push(newInput.get(0)); - inputArea.append(newInput); + this.valueInputs.push(newInput); + inputArea.appendChild(newInput); index += 1; } diff --git a/src/plugins/summaryWidget/src/ConditionManager.js b/src/plugins/summaryWidget/src/ConditionManager.js index ff90bc7bc7..e502649030 100644 --- a/src/plugins/summaryWidget/src/ConditionManager.js +++ b/src/plugins/summaryWidget/src/ConditionManager.js @@ -2,13 +2,11 @@ define ([ './ConditionEvaluator', 'objectUtils', 'EventEmitter', - 'zepto', 'lodash' ], function ( ConditionEvaluator, objectUtils, EventEmitter, - $, _ ) { @@ -232,7 +230,10 @@ define ([ self.eventEmitter.emit('add', obj); - $('.w-summary-widget').removeClass('s-status-no-data'); + const summaryWidget = document.querySelector('.w-summary-widget'); + if (summaryWidget) { + summaryWidget.classList.remove('s-status-no-data'); + } } }; @@ -256,7 +257,10 @@ define ([ this.eventEmitter.emit('remove', identifier); if (_.isEmpty(this.compositionObjs)) { - $('.w-summary-widget').addClass('s-status-no-data'); + const summaryWidget = document.querySelector('.w-summary-widget'); + if (summaryWidget) { + summaryWidget.classList.add('s-status-no-data'); + } } }; diff --git a/src/plugins/summaryWidget/src/Rule.js b/src/plugins/summaryWidget/src/Rule.js index d9217f0e0c..0b8f28804f 100644 --- a/src/plugins/summaryWidget/src/Rule.js +++ b/src/plugins/summaryWidget/src/Rule.js @@ -4,18 +4,18 @@ define([ './input/ColorPalette', './input/IconPalette', './eventHelpers', + '../../../utils/template/templateHelpers', 'EventEmitter', - 'lodash', - 'zepto' + 'lodash' ], function ( ruleTemplate, Condition, ColorPalette, IconPalette, eventHelpers, + templateHelpers, EventEmitter, - _, - $ + _ ) { /** * An object representing a summary widget rule. Maintains a set of text @@ -41,7 +41,7 @@ define([ this.widgetDnD = widgetDnD; this.container = container; - this.domElement = $(ruleTemplate); + this.domElement = templateHelpers.convertTemplateToHTML(ruleTemplate)[0]; this.eventEmitter = new EventEmitter(); this.supportedCallbacks = ['remove', 'duplicate', 'change', 'conditionChange']; this.conditions = []; @@ -50,31 +50,32 @@ define([ this.remove = this.remove.bind(this); this.duplicate = this.duplicate.bind(this); - this.thumbnail = $('.t-widget-thumb', this.domElement); - this.thumbnailIcon = $('.js-sw__icon', this.domElement); - this.thumbnailLabel = $('.c-sw__label', this.domElement); - this.title = $('.rule-title', this.domElement); - this.description = $('.rule-description', this.domElement); - this.trigger = $('.t-trigger', this.domElement); - this.toggleConfigButton = $('.js-disclosure', this.domElement); - this.configArea = $('.widget-rule-content', this.domElement); - this.grippy = $('.t-grippy', this.domElement); - this.conditionArea = $('.t-widget-rule-config', this.domElement); - this.jsConditionArea = $('.t-rule-js-condition-input-holder', this.domElement); - this.deleteButton = $('.t-delete', this.domElement); - this.duplicateButton = $('.t-duplicate', this.domElement); - this.addConditionButton = $('.add-condition', this.domElement); + this.thumbnail = this.domElement.querySelector('.t-widget-thumb'); + this.thumbnailIcon = this.domElement.querySelector('.js-sw__icon'); + this.thumbnailLabel = this.domElement.querySelector('.c-sw__label'); + this.title = this.domElement.querySelector('.rule-title'); + this.description = this.domElement.querySelector('.rule-description'); + this.trigger = this.domElement.querySelector('.t-trigger'); + this.toggleConfigButton = this.domElement.querySelector('.js-disclosure'); + this.configArea = this.domElement.querySelector('.widget-rule-content'); + this.grippy = this.domElement.querySelector('.t-grippy'); + this.conditionArea = this.domElement.querySelector('.t-widget-rule-config'); + this.jsConditionArea = this.domElement.querySelector('.t-rule-js-condition-input-holder'); + this.deleteButton = this.domElement.querySelector('.t-delete'); + this.duplicateButton = this.domElement.querySelector('.t-duplicate'); + this.addConditionButton = this.domElement.querySelector('.add-condition'); /** * The text inputs for this rule: any input included in this object will * have the appropriate event handlers registered to it, and it's corresponding * field in the domain object will be updated with its value */ + this.textInputs = { - name: $('.t-rule-name-input', this.domElement), - label: $('.t-rule-label-input', this.domElement), - message: $('.t-rule-message-input', this.domElement), - jsCondition: $('.t-rule-js-condition-input', this.domElement) + name: this.domElement.querySelector('.t-rule-name-input'), + label: this.domElement.querySelector('.t-rule-label-input'), + message: this.domElement.querySelector('.t-rule-message-input'), + jsCondition: this.domElement.querySelector('.t-rule-js-condition-input') }; this.iconInput = new IconPalette('', container); @@ -94,7 +95,7 @@ define([ function onIconInput(icon) { self.config.icon = icon; self.updateDomainObject('icon', icon); - self.thumbnailIcon.removeClass().addClass(THUMB_ICON_CLASS + ' ' + icon); + self.thumbnailIcon.className = `${THUMB_ICON_CLASS + ' ' + icon}`; self.eventEmitter.emit('change'); } @@ -106,7 +107,7 @@ define([ */ function onColorInput(color, property) { self.config.style[property] = color; - self.thumbnail.css(property, color); + self.thumbnail.style[property] = color; self.eventEmitter.emit('change'); } @@ -116,7 +117,10 @@ define([ * @private */ function encodeMsg(msg) { - return $('
').text(msg).html(); + const div = document.createElement('div'); + div.innerText = msg; + + return div.innerText; } /** @@ -144,9 +148,9 @@ define([ self.config[inputKey] = text; self.updateDomainObject(); if (inputKey === 'name') { - self.title.html(text); + self.title.innerText = text; } else if (inputKey === 'label') { - self.thumbnailLabel.html(text); + self.thumbnailLabel.innerText = text; } self.eventEmitter.emit('change'); @@ -158,13 +162,14 @@ define([ * @private */ function onDragStart(event) { - $('.t-drag-indicator').each(function () { + document.querySelectorAll('.t-drag-indicator').forEach(indicator => { // eslint-disable-next-line no-invalid-this - $(this).html($('.widget-rule-header', self.domElement).clone().get(0)); + const ruleHeader = self.domElement.querySelectorAll('.widget-rule-header')[0].cloneNode(true); + indicator.innerHTML = ruleHeader; }); - self.widgetDnD.setDragImage($('.widget-rule-header', self.domElement).clone().get(0)); + self.widgetDnD.setDragImage(self.domElement.querySelectorAll('.widget-rule-header')[0].cloneNode(true)); self.widgetDnD.dragStart(self.config.id); - self.domElement.hide(); + self.domElement.style.display = 'none'; } /** @@ -172,20 +177,31 @@ define([ * @private */ function toggleConfig() { - self.configArea.toggleClass('expanded'); - self.toggleConfigButton.toggleClass('c-disclosure-triangle--expanded'); + if (self.configArea.classList.contains('expanded')) { + self.configArea.classList.remove('expanded'); + } else { + self.configArea.classList.add('expanded'); + } + + if (self.toggleConfigButton.classList.contains('c-disclosure-triangle--expanded')) { + self.toggleConfigButton.classList.remove('c-disclosure-triangle--expanded'); + } else { + self.toggleConfigButton.classList.add('c-disclosure-triangle--expanded'); + } + self.config.expanded = !self.config.expanded; } - $('.t-rule-label-input', this.domElement).before(this.iconInput.getDOM()); + const labelInput = this.domElement.querySelector('.t-rule-label-input'); + labelInput.parentNode.insertBefore(this.iconInput.getDOM(), labelInput); this.iconInput.set(self.config.icon); this.iconInput.on('change', function (value) { onIconInput(value); }); // Initialize thumbs when first loading - this.thumbnailIcon.removeClass().addClass(THUMB_ICON_CLASS + ' ' + self.config.icon); - this.thumbnailLabel.html(self.config.label); + this.thumbnailIcon.className = `${THUMB_ICON_CLASS + ' ' + self.config.icon}`; + this.thumbnailLabel.innerText = self.config.label; Object.keys(this.colorInputs).forEach(function (inputKey) { const input = self.colorInputs[inputKey]; @@ -198,15 +214,17 @@ define([ self.updateDomainObject(); }); - $('.t-style-input', self.domElement).append(input.getDOM()); + self.domElement.querySelector('.t-style-input').append(input.getDOM()); }); Object.keys(this.textInputs).forEach(function (inputKey) { - self.textInputs[inputKey].prop('value', self.config[inputKey] || ''); - self.listenTo(self.textInputs[inputKey], 'input', function () { - // eslint-disable-next-line no-invalid-this - onTextInput(this, inputKey); - }); + if (self.textInputs[inputKey]) { + self.textInputs[inputKey].value = self.config[inputKey] || ''; + self.listenTo(self.textInputs[inputKey], 'input', function () { + // eslint-disable-next-line no-invalid-this + onTextInput(this, inputKey); + }); + } }); this.listenTo(this.deleteButton, 'click', this.remove); @@ -217,15 +235,15 @@ define([ this.listenTo(this.toggleConfigButton, 'click', toggleConfig); this.listenTo(this.trigger, 'change', onTriggerInput); - this.title.html(self.config.name); - this.description.html(self.config.description); - this.trigger.prop('value', self.config.trigger); + this.title.innerHTML = self.config.name; + this.description.innerHTML = self.config.description; + this.trigger.value = self.config.trigger; this.listenTo(this.grippy, 'mousedown', onDragStart); this.widgetDnD.on('drop', function () { // eslint-disable-next-line no-invalid-this this.domElement.show(); - $('.t-drag-indicator').hide(); + document.querySelector('.t-drag-indicator').style.display = 'none'; }, this); if (!this.conditionManager.loadCompleted()) { @@ -233,21 +251,21 @@ define([ } if (!this.config.expanded) { - this.configArea.removeClass('expanded'); - this.toggleConfigButton.removeClass('c-disclosure-triangle--expanded'); + this.configArea.classList.remove('expanded'); + this.toggleConfigButton.classList.remove('c-disclosure-triangle--expanded'); } if (this.domainObject.configuration.ruleOrder.length === 2) { - $('.t-grippy', this.domElement).hide(); + this.domElement.querySelector('.t-grippy').style.display = 'none'; } this.refreshConditions(); //if this is the default rule, hide elements that don't apply if (this.config.id === 'default') { - $('.t-delete', this.domElement).hide(); - $('.t-widget-rule-config', this.domElement).hide(); - $('.t-grippy', this.domElement).hide(); + this.domElement.querySelector('.t-delete').style.display = 'none'; + this.domElement.querySelector('.t-widget-rule-config').style.display = 'none'; + this.domElement.querySelector('.t-grippy').style.display = 'none'; } } @@ -304,8 +322,8 @@ define([ * During a rule drag event, show the placeholder element after this rule */ Rule.prototype.showDragIndicator = function () { - $('.t-drag-indicator').hide(); - $('.t-drag-indicator', this.domElement).show(); + document.querySelector('.t-drag-indicator').style.display = 'none'; + this.domElement.querySelector('.t-drag-indicator').style.display = ''; }; /** @@ -397,7 +415,10 @@ define([ const triggerContextStr = self.config.trigger === 'any' ? ' or ' : ' and '; self.conditions = []; - $('.t-condition', this.domElement).remove(); + + this.domElement.querySelectorAll('.t-condition').forEach(condition => { + condition.remove(); + }); this.config.conditions.forEach(function (condition, index) { const newCondition = new Condition(condition, index, self.conditionManager); @@ -408,16 +429,23 @@ define([ }); if (this.config.trigger === 'js') { - this.jsConditionArea.show(); - this.addConditionButton.hide(); + if (this.jsConditionArea) { + this.jsConditionArea.style.display = ''; + } + + this.addConditionButton.style.display = 'none'; } else { - this.jsConditionArea.hide(); - this.addConditionButton.show(); + if (this.jsConditionArea) { + this.jsConditionArea.style.display = 'none'; + } + + this.addConditionButton.style.display = ''; self.conditions.forEach(function (condition) { $condition = condition.getDOM(); - $('li:last-of-type', self.conditionArea).before($condition); + const lastOfType = self.conditionArea.querySelector('li:last-of-type'); + lastOfType.parentNode.insertBefore($condition, lastOfType); if (loopCnt > 0) { - $('.t-condition-context', $condition).html(triggerContextStr + ' when'); + $condition.querySelector('.t-condition-context').innerHTML = triggerContextStr + ' when'; } loopCnt++; @@ -489,7 +517,7 @@ define([ } description = (description === '' ? this.config.description : description); - this.description.html(description); + this.description.innerHTML = self.config.description; this.config.description = description; }; diff --git a/src/plugins/summaryWidget/src/SummaryWidget.js b/src/plugins/summaryWidget/src/SummaryWidget.js index 1a5c1ceba0..e9c1442bf2 100644 --- a/src/plugins/summaryWidget/src/SummaryWidget.js +++ b/src/plugins/summaryWidget/src/SummaryWidget.js @@ -5,9 +5,9 @@ define([ './TestDataManager', './WidgetDnD', './eventHelpers', + '../../../utils/template/templateHelpers', 'objectUtils', 'lodash', - 'zepto', '@braintree/sanitize-url' ], function ( widgetTemplate, @@ -16,9 +16,9 @@ define([ TestDataManager, WidgetDnD, eventHelpers, + templateHelpers, objectUtils, _, - $, urlSanitizeLib ) { @@ -54,20 +54,22 @@ define([ this.activeId = 'default'; this.rulesById = {}; - this.domElement = $(widgetTemplate); - this.toggleRulesControl = $('.t-view-control-rules', this.domElement); - this.toggleTestDataControl = $('.t-view-control-test-data', this.domElement); - this.widgetButton = this.domElement.children('#widget'); + this.domElement = templateHelpers.convertTemplateToHTML(widgetTemplate)[0]; + this.toggleRulesControl = this.domElement.querySelector('.t-view-control-rules'); + this.toggleTestDataControl = this.domElement.querySelector('.t-view-control-test-data'); + + this.widgetButton = this.domElement.querySelector(':scope > #widget'); + this.editing = false; this.container = ''; - this.editListenerUnsubscribe = $.noop; + this.editListenerUnsubscribe = () => {}; - this.outerWrapper = $('.widget-edit-holder', this.domElement); - this.ruleArea = $('#ruleArea', this.domElement); - this.configAreaRules = $('.widget-rules-wrapper', this.domElement); + this.outerWrapper = this.domElement.querySelector('.widget-edit-holder'); + this.ruleArea = this.domElement.querySelector('#ruleArea'); + this.configAreaRules = this.domElement.querySelector('.widget-rules-wrapper'); - this.testDataArea = $('.widget-test-data', this.domElement); - this.addRuleButton = $('#addRule', this.domElement); + this.testDataArea = this.domElement.querySelector('.widget-test-data'); + this.addRuleButton = this.domElement.querySelector('#addRule'); this.conditionManager = new ConditionManager(this.domainObject, this.openmct); this.testDataManager = new TestDataManager(this.domainObject, this.conditionManager, this.openmct); @@ -87,8 +89,17 @@ define([ * @private */ function toggleTestData() { - self.outerWrapper.toggleClass('expanded-widget-test-data'); - self.toggleTestDataControl.toggleClass('c-disclosure-triangle--expanded'); + if (self.outerWrapper.classList.contains('expanded-widget-test-data')) { + self.outerWrapper.classList.remove('expanded-widget-test-data'); + } else { + self.outerWrapper.classList.add('expanded-widget-test-data'); + } + + if (self.toggleTestDataControl.classList.contains('c-disclosure-triangle--expanded')) { + self.toggleTestDataControl.classList.remove('c-disclosure-triangle--expanded'); + } else { + self.toggleTestDataControl.classList.add('c-disclosure-triangle--expanded'); + } } this.listenTo(this.toggleTestDataControl, 'click', toggleTestData); @@ -98,8 +109,8 @@ define([ * @private */ function toggleRules() { - self.outerWrapper.toggleClass('expanded-widget-rules'); - self.toggleRulesControl.toggleClass('c-disclosure-triangle--expanded'); + templateHelpers.toggleClass(self.outerWrapper, 'expanded-widget-rules'); + templateHelpers.toggleClass(self.toggleRulesControl, 'c-disclosure-triangle--expanded'); } this.listenTo(this.toggleRulesControl, 'click', toggleRules); @@ -113,15 +124,15 @@ define([ */ SummaryWidget.prototype.addHyperlink = function (url, openNewTab) { if (url) { - this.widgetButton.attr('href', urlSanitizeLib.sanitizeUrl(url)); + this.widgetButton.href = urlSanitizeLib.sanitizeUrl(url); } else { - this.widgetButton.removeAttr('href'); + this.widgetButton.removeAttribute('href'); } if (openNewTab === 'newTab') { - this.widgetButton.attr('target', '_blank'); + this.widgetButton.target = '_blank'; } else { - this.widgetButton.removeAttr('target'); + this.widgetButton.removeAttribute('target'); } }; @@ -149,8 +160,8 @@ define([ SummaryWidget.prototype.show = function (container) { const self = this; this.container = container; - $(container).append(this.domElement); - $('.widget-test-data', this.domElement).append(this.testDataManager.getDOM()); + this.container.append(this.domElement); + this.domElement.querySelector('.widget-test-data').append(this.testDataManager.getDOM()); this.widgetDnD = new WidgetDnD(this.domElement, this.domainObject.configuration.ruleOrder, this.rulesById); this.initRule('default', 'Default'); this.domainObject.configuration.ruleOrder.forEach(function (ruleId) { @@ -190,7 +201,7 @@ define([ const self = this; const ruleOrder = self.domainObject.configuration.ruleOrder; const rules = self.rulesById; - self.ruleArea.html(''); + self.ruleArea.innerHTML = ''; Object.values(ruleOrder).forEach(function (ruleId) { self.ruleArea.append(rules[ruleId].getDOM()); }); @@ -205,9 +216,9 @@ define([ rules.forEach(function (ruleKey, index, array) { if (array.length > 2 && index > 0) { - $('.t-grippy', rulesById[ruleKey].domElement).show(); + rulesById[ruleKey].domElement.querySelector('.t-grippy').style.display = ''; } else { - $('.t-grippy', rulesById[ruleKey].domElement).hide(); + rulesById[ruleKey].domElement.querySelector('.t-grippy').style.display = 'none'; } }); }; @@ -218,10 +229,10 @@ define([ SummaryWidget.prototype.updateWidget = function () { const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon'; const activeRule = this.rulesById[this.activeId]; - this.applyStyle($('#widget', this.domElement), activeRule.getProperty('style')); - $('#widget', this.domElement).prop('title', activeRule.getProperty('message')); - $('#widgetLabel', this.domElement).html(activeRule.getProperty('label')); - $('#widgetIcon', this.domElement).removeClass().addClass(WIDGET_ICON_CLASS + ' ' + activeRule.getProperty('icon')); + this.applyStyle(this.domElement.querySelector('#widget'), activeRule.getProperty('style')); + this.domElement.querySelector('#widget').title = activeRule.getProperty('message'); + this.domElement.querySelector('#widgetLabel').innerHTML = activeRule.getProperty('label'); + this.domElement.querySelector('#widgetIcon').classList = WIDGET_ICON_CLASS + ' ' + activeRule.getProperty('icon'); }; /** @@ -356,7 +367,7 @@ define([ */ SummaryWidget.prototype.applyStyle = function (elem, style) { Object.keys(style).forEach(function (propId) { - elem.css(propId, style[propId]); + elem.style[propId] = style[propId]; }); }; diff --git a/src/plugins/summaryWidget/src/TestDataItem.js b/src/plugins/summaryWidget/src/TestDataItem.js index 32b737a90e..ae005c46d0 100644 --- a/src/plugins/summaryWidget/src/TestDataItem.js +++ b/src/plugins/summaryWidget/src/TestDataItem.js @@ -3,15 +3,15 @@ define([ './input/ObjectSelect', './input/KeySelect', './eventHelpers', - 'EventEmitter', - 'zepto' + '../../../utils/template/templateHelpers', + 'EventEmitter' ], function ( itemTemplate, ObjectSelect, KeySelect, eventHelpers, - EventEmitter, - $ + templateHelpers, + EventEmitter ) { /** @@ -31,12 +31,12 @@ define([ this.index = index; this.conditionManager = conditionManager; - this.domElement = $(itemTemplate); + this.domElement = templateHelpers.convertTemplateToHTML(itemTemplate)[0]; this.eventEmitter = new EventEmitter(); this.supportedCallbacks = ['remove', 'duplicate', 'change']; - this.deleteButton = $('.t-delete', this.domElement); - this.duplicateButton = $('.t-duplicate', this.domElement); + this.deleteButton = this.domElement.querySelector('.t-delete'); + this.duplicateButton = this.domElement.querySelector('.t-duplicate'); this.selects = {}; this.valueInputs = []; @@ -101,7 +101,7 @@ define([ }); Object.values(this.selects).forEach(function (select) { - $('.t-configuration', self.domElement).append(select.getDOM()); + self.domElement.querySelector('.t-configuration').append(select.getDOM()); }); this.listenTo(this.domElement, 'input', onValueInput); } @@ -139,7 +139,7 @@ define([ * Hide the appropriate inputs when this is the only item */ TestDataItem.prototype.hideButtons = function () { - this.deleteButton.hide(); + this.deleteButton.style.display = 'none'; }; /** @@ -177,17 +177,21 @@ define([ */ TestDataItem.prototype.generateValueInput = function (key) { const evaluator = this.conditionManager.getEvaluator(); - const inputArea = $('.t-value-inputs', this.domElement); + const inputArea = this.domElement.querySelector('.t-value-inputs'); const dataType = this.conditionManager.getTelemetryPropertyType(this.config.object, key); const inputType = evaluator.getInputTypeById(dataType); - inputArea.html(''); + inputArea.innerHTML = ''; if (inputType) { if (!this.config.value) { this.config.value = (inputType === 'number' ? 0 : ''); } - this.valueInput = $(' ').get(0); + const newInput = document.createElement("input"); + newInput.type = `${inputType}`; + newInput.value = `${this.config.value}`; + + this.valueInput = newInput; inputArea.append(this.valueInput); } }; diff --git a/src/plugins/summaryWidget/src/TestDataManager.js b/src/plugins/summaryWidget/src/TestDataManager.js index 819cc5ee3f..70240453d6 100644 --- a/src/plugins/summaryWidget/src/TestDataManager.js +++ b/src/plugins/summaryWidget/src/TestDataManager.js @@ -2,13 +2,13 @@ define([ './eventHelpers', '../res/testDataTemplate.html', './TestDataItem', - 'zepto', + '../../../utils/template/templateHelpers', 'lodash' ], function ( eventHelpers, testDataTemplate, TestDataItem, - $, + templateHelpers, _ ) { @@ -28,13 +28,13 @@ define([ this.openmct = openmct; this.evaluator = this.manager.getEvaluator(); - this.domElement = $(testDataTemplate); + this.domElement = templateHelpers.convertTemplateToHTML(testDataTemplate)[0]; this.config = this.domainObject.configuration.testDataConfig; this.testCache = {}; - this.itemArea = $('.t-test-data-config', this.domElement); - this.addItemButton = $('.add-test-condition', this.domElement); - this.testDataInput = $('.t-test-data-checkbox', this.domElement); + this.itemArea = this.domElement.querySelector('.t-test-data-config'); + this.addItemButton = this.domElement.querySelector('.add-test-condition'); + this.testDataInput = this.domElement.querySelector('.t-test-data-checkbox'); /** * Toggles whether the associated {ConditionEvaluator} uses the actual @@ -139,7 +139,10 @@ define([ } self.items = []; - $('.t-test-data-item', this.domElement).remove(); + + this.domElement.querySelectorAll('.t-test-data-item').forEach(item => { + item.remove(); + }); this.config.forEach(function (item, index) { const newItem = new TestDataItem(item, index, self.manager); @@ -150,7 +153,6 @@ define([ }); self.items.forEach(function (item) { - // $('li:last-of-type', self.itemArea).before(item.getDOM()); self.itemArea.prepend(item.getDOM()); }); diff --git a/src/plugins/summaryWidget/src/WidgetDnD.js b/src/plugins/summaryWidget/src/WidgetDnD.js index e9ee2f0400..90cd3b6971 100644 --- a/src/plugins/summaryWidget/src/WidgetDnD.js +++ b/src/plugins/summaryWidget/src/WidgetDnD.js @@ -1,11 +1,11 @@ define([ '../res/ruleImageTemplate.html', 'EventEmitter', - 'zepto' + '../../../utils/template/templateHelpers' ], function ( ruleImageTemplate, EventEmitter, - $ + templateHelpers ) { /** @@ -19,8 +19,8 @@ define([ this.ruleOrder = ruleOrder; this.rulesById = rulesById; - this.imageContainer = $(ruleImageTemplate); - this.image = $('.t-drag-rule-image', this.imageContainer); + this.imageContainer = templateHelpers.convertTemplateToHTML(ruleImageTemplate)[0]; + this.image = this.imageContainer.querySelector('.t-drag-rule-image'); this.draggingId = ''; this.draggingRulePrevious = ''; this.eventEmitter = new EventEmitter(); @@ -29,18 +29,18 @@ define([ this.drag = this.drag.bind(this); this.drop = this.drop.bind(this); - $(this.container).on('mousemove', this.drag); - $(document).on('mouseup', this.drop); - $(this.container).before(this.imageContainer); - $(this.imageContainer).hide(); + this.container.addEventListener('mousemove', this.drag); + document.addEventListener('mouseup', this.drop); + this.container.parentNode.insertBefore(this.imageContainer, this.container); + this.imageContainer.style.display = 'none'; } /** * Remove event listeners registered to elements external to the widget */ WidgetDnD.prototype.destroy = function () { - $(this.container).off('mousemove', this.drag); - $(document).off('mouseup', this.drop); + this.container.removeEventListener('mousemove', this.drag); + document.removeEventListener('mouseup', this.drop); }; /** @@ -81,7 +81,8 @@ define([ let target = ''; ruleOrder.forEach(function (ruleId, index) { - offset = rulesById[ruleId].getDOM().offset(); + const ruleDOM = rulesById[ruleId].getDOM(); + offset = window.innerWidth - (ruleDOM.offsetLeft + ruleDOM.offsetWidth); y = offset.top; height = offset.height; if (index === 0) { @@ -114,7 +115,7 @@ define([ this.imageContainer.show(); this.imageContainer.offset({ top: event.pageY - this.image.height() / 2, - left: event.pageX - $('.t-grippy', this.image).width() + left: event.pageX - this.image.querySelector('.t-grippy').style.width }); }; @@ -129,7 +130,7 @@ define([ dragTarget = this.getDropLocation(event); this.imageContainer.offset({ top: event.pageY - this.image.height() / 2, - left: event.pageX - $('.t-grippy', this.image).width() + left: event.pageX - this.image.querySelector('.t-grippy').style.width }); if (this.rulesById[dragTarget]) { this.rulesById[dragTarget].showDragIndicator(); diff --git a/src/plugins/summaryWidget/src/input/ColorPalette.js b/src/plugins/summaryWidget/src/input/ColorPalette.js index 0bbe236419..2319f98304 100644 --- a/src/plugins/summaryWidget/src/input/ColorPalette.js +++ b/src/plugins/summaryWidget/src/input/ColorPalette.js @@ -1,10 +1,8 @@ define([ - './Palette', - 'zepto' + './Palette' ], function ( - Palette, - $ + Palette ) { //The colors that will be used to instantiate this palette if none are provided @@ -33,17 +31,16 @@ function ( this.palette.setNullOption('rgba(0,0,0,0)'); - const domElement = $(this.palette.getDOM()); + const domElement = this.palette.getDOM(); const self = this; - $('.c-button--menu', domElement).addClass('c-button--swatched'); - $('.t-swatch', domElement).addClass('color-swatch'); - $('.c-palette', domElement).addClass('c-palette--color'); + domElement.querySelector('.c-button--menu').classList.add('c-button--swatched'); + domElement.querySelector('.t-swatch').classList.add('color-swatch'); + domElement.querySelector('.c-palette').classList.add('c-palette--color'); - $('.c-palette__item', domElement).each(function () { + domElement.querySelectorAll('.c-palette__item').forEach(item => { // eslint-disable-next-line no-invalid-this - const elem = this; - $(elem).css('background-color', elem.dataset.item); + item.style.backgroundColor = item.dataset.item; }); /** @@ -53,7 +50,7 @@ function ( */ function updateSwatch() { const color = self.palette.getCurrent(); - $('.color-swatch', domElement).css('background-color', color); + domElement.querySelector('.color-swatch').style.backgroundColor = color; } this.palette.on('change', updateSwatch); diff --git a/src/plugins/summaryWidget/src/input/IconPalette.js b/src/plugins/summaryWidget/src/input/IconPalette.js index cdc011d5da..557cc4d958 100644 --- a/src/plugins/summaryWidget/src/input/IconPalette.js +++ b/src/plugins/summaryWidget/src/input/IconPalette.js @@ -1,9 +1,7 @@ define([ - './Palette', - 'zepto' + './Palette' ], function ( - Palette, - $ + Palette ) { //The icons that will be used to instantiate this palette if none are provided const DEFAULT_ICONS = [ @@ -45,20 +43,19 @@ define([ this.icons = icons || DEFAULT_ICONS; this.palette = new Palette(cssClass, container, this.icons); - this.palette.setNullOption(' '); - this.oldIcon = this.palette.current || ' '; + this.palette.setNullOption(''); + this.oldIcon = this.palette.current || ''; - const domElement = $(this.palette.getDOM()); + const domElement = this.palette.getDOM(); const self = this; - $('.c-button--menu', domElement).addClass('c-button--swatched'); - $('.t-swatch', domElement).addClass('icon-swatch'); - $('.c-palette', domElement).addClass('c-palette--icon'); + domElement.querySelector('.c-button--menu').classList.add('c-button--swatched'); + domElement.querySelector('.t-swatch').classList.add('icon-swatch'); + domElement.querySelector('.c-palette').classList.add('c-palette--icon'); - $('.c-palette-item', domElement).each(function () { + domElement.querySelectorAll('.c-palette-item').forEach(item => { // eslint-disable-next-line no-invalid-this - const elem = this; - $(elem).addClass(elem.dataset.item); + item.classList.add(item.dataset.item); }); /** @@ -67,8 +64,11 @@ define([ * @private */ function updateSwatch() { - $('.icon-swatch', domElement).removeClass(self.oldIcon) - .addClass(self.palette.getCurrent()); + if (self.oldIcon) { + domElement.querySelector('.icon-swatch').classList.remove(self.oldIcon); + } + + domElement.querySelector('.icon-swatch').classList.add(self.palette.getCurrent()); self.oldIcon = self.palette.getCurrent(); } diff --git a/src/plugins/summaryWidget/src/input/Palette.js b/src/plugins/summaryWidget/src/input/Palette.js index ff1d3b5500..96df813de2 100644 --- a/src/plugins/summaryWidget/src/input/Palette.js +++ b/src/plugins/summaryWidget/src/input/Palette.js @@ -1,13 +1,13 @@ define([ '../eventHelpers', '../../res/input/paletteTemplate.html', - 'EventEmitter', - 'zepto' + '../../../../utils/template/templateHelpers', + 'EventEmitter' ], function ( eventHelpers, paletteTemplate, - EventEmitter, - $ + templateHelpers, + EventEmitter ) { /** * Instantiates a new Open MCT Color Palette input @@ -28,36 +28,41 @@ define([ this.items = items; this.container = container; - this.domElement = $(paletteTemplate); + this.domElement = templateHelpers.convertTemplateToHTML(paletteTemplate)[0]; + this.itemElements = { - nullOption: $('.c-palette__item-none .c-palette__item', this.domElement) + nullOption: this.domElement.querySelector('.c-palette__item-none .c-palette__item') }; this.eventEmitter = new EventEmitter(); this.supportedCallbacks = ['change']; this.value = this.items[0]; this.nullOption = ' '; - this.button = $('.js-button', this.domElement); - this.menu = $('.c-menu', this.domElement); + this.button = this.domElement.querySelector('.js-button'); + this.menu = this.domElement.querySelector('.c-menu'); this.hideMenu = this.hideMenu.bind(this); - self.button.addClass(this.cssClass); + if (this.cssClass) { + self.button.classList.add(this.cssClass); + } + self.setNullOption(this.nullOption); self.items.forEach(function (item) { - const itemElement = $('
'); - $('.c-palette__items', self.domElement).append(itemElement); - self.itemElements[item] = itemElement; + const itemElement = `
`; + const temp = document.createElement('div'); + temp.innerHTML = itemElement; + self.itemElements[item] = temp.firstChild; + self.domElement.querySelector('.c-palette__items').appendChild(temp.firstChild); }); - $('.c-menu', self.domElement).hide(); + self.domElement.querySelector('.c-menu').style.display = 'none'; - this.listenTo($(document), 'click', this.hideMenu); - this.listenTo($('.js-button', self.domElement), 'click', function (event) { + this.listenTo(window.document, 'click', this.hideMenu); + this.listenTo(self.domElement.querySelector('.js-button'), 'click', function (event) { event.stopPropagation(); - $('.c-menu', self.container).hide(); - $('.c-menu', self.domElement).show(); + self.container.querySelector('.c-menu').style.display = 'none'; + self.domElement.querySelector('.c-menu').style.display = ''; }); /** @@ -70,10 +75,12 @@ define([ const elem = event.currentTarget; const item = elem.dataset.item; self.set(item); - $('.c-menu', self.domElement).hide(); + self.domElement.querySelector('.c-menu').style.display = 'none'; } - this.listenTo($('.c-palette__item', self.domElement), 'click', handleItemClick); + self.domElement.querySelectorAll('.c-palette__item').forEach(item => { + this.listenTo(item, 'click', handleItemClick); + }); } /** @@ -91,7 +98,7 @@ define([ }; Palette.prototype.hideMenu = function () { - $('.c-menu', this.domElement).hide(); + this.domElement.querySelector('.c-menu').style.display = 'none'; }; /** @@ -141,12 +148,16 @@ define([ * Update the view assoicated with the currently selected item */ Palette.prototype.updateSelected = function (item) { - $('.c-palette__item', this.domElement).removeClass('is-selected'); - this.itemElements[item].addClass('is-selected'); + this.domElement.querySelectorAll('.c-palette__item').forEach(paletteItem => { + if (paletteItem.classList.contains('is-selected')) { + paletteItem.classList.remove('is-selected'); + } + }); + this.itemElements[item].classList.add('is-selected'); if (item === 'nullOption') { - $('.t-swatch', this.domElement).addClass('no-selection'); + this.domElement.querySelector('.t-swatch').classList.add('no-selection'); } else { - $('.t-swatch', this.domElement).removeClass('no-selection'); + this.domElement.querySelector('.t-swatch').classList.remove('no-selection'); } }; @@ -157,14 +168,20 @@ define([ */ Palette.prototype.setNullOption = function (item) { this.nullOption = item; - this.itemElements.nullOption.data('item', item); + this.itemElements.nullOption.data = { item: item }; }; /** * Hides the 'no selection' option to be hidden in the view if it doesn't apply */ Palette.prototype.toggleNullOption = function () { - $('.c-palette__item-none', this.domElement).toggle(); + const elem = this.domElement.querySelector('.c-palette__item-none'); + + if (elem.style.display === 'none') { + this.domElement.querySelector('.c-palette__item-none').style.display = 'flex'; + } else { + this.domElement.querySelector('.c-palette__item-none').style.display = 'none'; + } }; return Palette; diff --git a/src/plugins/summaryWidget/src/input/Select.js b/src/plugins/summaryWidget/src/input/Select.js index 3f89034caf..676a9791b2 100644 --- a/src/plugins/summaryWidget/src/input/Select.js +++ b/src/plugins/summaryWidget/src/input/Select.js @@ -1,13 +1,13 @@ define([ '../eventHelpers', '../../res/input/selectTemplate.html', - 'EventEmitter', - 'zepto' + '../../../../utils/template/templateHelpers', + 'EventEmitter' ], function ( eventHelpers, selectTemplate, - EventEmitter, - $ + templateHelpers, + EventEmitter ) { /** @@ -20,7 +20,8 @@ define([ const self = this; - this.domElement = $(selectTemplate); + this.domElement = templateHelpers.convertTemplateToHTML(selectTemplate)[0]; + this.options = []; this.eventEmitter = new EventEmitter(); this.supportedCallbacks = ['change']; @@ -35,12 +36,12 @@ define([ */ function onChange(event) { const elem = event.target; - const value = self.options[$(elem).prop('selectedIndex')]; + const value = self.options[elem.selectedIndex]; self.eventEmitter.emit('change', value[0]); } - this.listenTo($('select', this.domElement), 'change', onChange, this); + this.listenTo(this.domElement.querySelector('select'), 'change', onChange, this); } /** @@ -74,16 +75,19 @@ define([ const self = this; let selectedIndex = 0; - selectedIndex = $('select', this.domElement).prop('selectedIndex'); - $('option', this.domElement).remove(); + selectedIndex = this.domElement.querySelector('select').selectedIndex; - self.options.forEach(function (option, index) { - $('select', self.domElement) - .append(''); + this.domElement.querySelector('select').innerHTML = ''; + + self.options.forEach(function (option) { + const optionElement = document.createElement('option'); + optionElement.value = option[0]; + optionElement.innerText = `+ ${option[1]}`; + + self.domElement.querySelector('select').appendChild(optionElement); }); - $('select', this.domElement).prop('selectedIndex', selectedIndex); + this.domElement.querySelector('select').selectedIndex = selectedIndex; }; /** @@ -120,7 +124,7 @@ define([ selectedIndex = index; } }); - $('select', this.domElement).prop('selectedIndex', selectedIndex); + this.domElement.querySelector('select').selectedIndex = selectedIndex; selectedOption = this.options[selectedIndex]; this.eventEmitter.emit('change', selectedOption[0]); @@ -131,17 +135,21 @@ define([ * @return {string} */ Select.prototype.getSelected = function () { - return $('select', this.domElement).prop('value'); + return this.domElement.querySelector('select').value; }; Select.prototype.hide = function () { - $(this.domElement).addClass('hidden'); - $('.equal-to').addClass('hidden'); + this.domElement.classList.add('hidden'); + if (this.domElement.querySelector('.equal-to')) { + this.domElement.querySelector('.equal-to').classList.add('hidden'); + } }; Select.prototype.show = function () { - $(this.domElement).removeClass('hidden'); - $('.equal-to').removeClass('hidden'); + this.domElement.classList.remove('hidden'); + if (this.domElement.querySelector('.equal-to')) { + this.domElement.querySelector('.equal-to').classList.remove('hidden'); + } }; Select.prototype.destroy = function () { diff --git a/src/plugins/summaryWidget/test/ConditionSpec.js b/src/plugins/summaryWidget/test/ConditionSpec.js index a69742065b..8b166bf872 100644 --- a/src/plugins/summaryWidget/test/ConditionSpec.js +++ b/src/plugins/summaryWidget/test/ConditionSpec.js @@ -20,7 +20,7 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define(['../src/Condition', 'zepto'], function (Condition, $) { +define(['../src/Condition'], function (Condition) { xdescribe('A summary widget condition', function () { let testCondition; let mockConfig; @@ -33,7 +33,7 @@ define(['../src/Condition', 'zepto'], function (Condition, $) { let generateValuesSpy; beforeEach(function () { - mockContainer = $(document.createElement('div')); + mockContainer = document.createElement('div'); mockConfig = { object: 'object1', @@ -78,7 +78,7 @@ define(['../src/Condition', 'zepto'], function (Condition, $) { it('exposes a DOM element to represent itself in the view', function () { mockContainer.append(testCondition.getDOM()); - expect($('.t-condition', mockContainer).get().length).toEqual(1); + expect(mockContainer.querySelectorAll('.t-condition').length).toEqual(1); }); it('responds to a change in its object select', function () { @@ -111,41 +111,59 @@ define(['../src/Condition', 'zepto'], function (Condition, $) { }); it('generates value inputs of the appropriate type and quantity', function () { + let inputs; + mockContainer.append(testCondition.getDOM()); mockEvaluator.getInputType.and.returnValue('number'); mockEvaluator.getInputCount.and.returnValue(3); testCondition.generateValueInputs(''); - expect($('input', mockContainer).filter('[type=number]').get().length).toEqual(3); - expect($('input', mockContainer).eq(0).prop('valueAsNumber')).toEqual(1); - expect($('input', mockContainer).eq(1).prop('valueAsNumber')).toEqual(2); - expect($('input', mockContainer).eq(2).prop('valueAsNumber')).toEqual(3); + + inputs = mockContainer.querySelectorAll('input'); + const numberInputs = Array.from(inputs).filter(input => input.type === 'number'); + + expect(numberInputs.length).toEqual(3); + expect(numberInputs[0].valueAsNumber).toEqual(1); + expect(numberInputs[1].valueAsNumber).toEqual(2); + expect(numberInputs[2].valueAsNumber).toEqual(3); mockEvaluator.getInputType.and.returnValue('text'); mockEvaluator.getInputCount.and.returnValue(2); testCondition.config.values = ['Text I Am', 'Text It Is']; testCondition.generateValueInputs(''); - expect($('input', mockContainer).filter('[type=text]').get().length).toEqual(2); - expect($('input', mockContainer).eq(0).prop('value')).toEqual('Text I Am'); - expect($('input', mockContainer).eq(1).prop('value')).toEqual('Text It Is'); + + inputs = mockContainer.querySelectorAll('input'); + const textInputs = Array.from(inputs).filter(input => input.type === 'text'); + + expect(textInputs.length).toEqual(2); + expect(textInputs[0].value).toEqual('Text I Am'); + expect(textInputs[1].value).toEqual('Text It Is'); }); it('ensures reasonable defaults on values if none are provided', function () { + let inputs; + mockContainer.append(testCondition.getDOM()); mockEvaluator.getInputType.and.returnValue('number'); mockEvaluator.getInputCount.and.returnValue(3); testCondition.config.values = []; testCondition.generateValueInputs(''); - expect($('input', mockContainer).eq(0).prop('valueAsNumber')).toEqual(0); - expect($('input', mockContainer).eq(1).prop('valueAsNumber')).toEqual(0); - expect($('input', mockContainer).eq(2).prop('valueAsNumber')).toEqual(0); + + inputs = Array.from(mockContainer.querySelectorAll('input')); + + expect(inputs[0].valueAsNumber).toEqual(0); + expect(inputs[1].valueAsNumber).toEqual(0); + expect(inputs[2].valueAsNumber).toEqual(0); expect(testCondition.config.values).toEqual([0, 0, 0]); mockEvaluator.getInputType.and.returnValue('text'); mockEvaluator.getInputCount.and.returnValue(2); testCondition.config.values = []; testCondition.generateValueInputs(''); - expect($('input', mockContainer).eq(0).prop('value')).toEqual(''); - expect($('input', mockContainer).eq(1).prop('value')).toEqual(''); + + inputs = Array.from(mockContainer.querySelectorAll('input')); + + expect(inputs[0].value).toEqual(''); + expect(inputs[1].value).toEqual(''); expect(testCondition.config.values).toEqual(['', '']); }); @@ -154,8 +172,16 @@ define(['../src/Condition', 'zepto'], function (Condition, $) { mockEvaluator.getInputType.and.returnValue('number'); mockEvaluator.getInputCount.and.returnValue(3); testCondition.generateValueInputs(''); - $('input', mockContainer).eq(1).prop('value', 9001); - $('input', mockContainer).eq(1).trigger('input'); + + const event = new Event('input', { + bubbles: true, + cancelable: true + }); + const inputs = mockContainer.querySelectorAll('input'); + + inputs[1].value = 9001; + inputs[1].dispatchEvent(event); + expect(changeSpy).toHaveBeenCalledWith({ value: 9001, property: 'values[1]', diff --git a/src/plugins/summaryWidget/test/RuleSpec.js b/src/plugins/summaryWidget/test/RuleSpec.js index 4d6c5b7149..df5108f7ae 100644 --- a/src/plugins/summaryWidget/test/RuleSpec.js +++ b/src/plugins/summaryWidget/test/RuleSpec.js @@ -1,4 +1,4 @@ -define(['../src/Rule', 'zepto'], function (Rule, $) { +define(['../src/Rule'], function (Rule) { describe('A Summary Widget Rule', function () { let mockRuleConfig; let mockDomainObject; @@ -78,7 +78,7 @@ define(['../src/Rule', 'zepto'], function (Rule, $) { 'dragStart' ]); - mockContainer = $(document.createElement('div')); + mockContainer = document.createElement('div'); removeSpy = jasmine.createSpy('removeCallback'); duplicateSpy = jasmine.createSpy('duplicateCallback'); @@ -99,7 +99,7 @@ define(['../src/Rule', 'zepto'], function (Rule, $) { it('gets its DOM element', function () { mockContainer.append(testRule.getDOM()); - expect($('.l-widget-rule', mockContainer).get().length).toBeGreaterThan(0); + expect(mockContainer.querySelectorAll('.l-widget-rule').length).toBeGreaterThan(0); }); it('gets its configuration properties', function () { @@ -185,7 +185,7 @@ define(['../src/Rule', 'zepto'], function (Rule, $) { it('builds condition view from condition configuration', function () { mockContainer.append(testRule.getDOM()); - expect($('.t-condition', mockContainer).get().length).toEqual(2); + expect(mockContainer.querySelectorAll('.t-condition').length).toEqual(2); }); it('responds to input of style properties, and updates the preview', function () { @@ -196,9 +196,9 @@ define(['../src/Rule', 'zepto'], function (Rule, $) { testRule.colorInputs.color.set('#999999'); expect(mockRuleConfig.style.color).toEqual('#999999'); - expect(testRule.thumbnail.css('background-color')).toEqual('rgb(67, 67, 67)'); - expect(testRule.thumbnail.css('border-color')).toEqual('rgb(102, 102, 102)'); - expect(testRule.thumbnail.css('color')).toEqual('rgb(153, 153, 153)'); + expect(testRule.thumbnail.style['background-color']).toEqual('rgb(67, 67, 67)'); + expect(testRule.thumbnail.style['border-color']).toEqual('rgb(102, 102, 102)'); + expect(testRule.thumbnail.style.color).toEqual('rgb(153, 153, 153)'); expect(changeSpy).toHaveBeenCalled(); }); @@ -228,8 +228,12 @@ define(['../src/Rule', 'zepto'], function (Rule, $) { // }); it('allows input for when the rule triggers', function () { - testRule.trigger.prop('value', 'all'); - testRule.trigger.trigger('change'); + testRule.trigger.value = 'all'; + const event = new Event('change', { + bubbles: true, + cancelable: true + }); + testRule.trigger.dispatchEvent(event); expect(testRule.config.trigger).toEqual('all'); expect(conditionChangeSpy).toHaveBeenCalled(); }); @@ -247,7 +251,12 @@ define(['../src/Rule', 'zepto'], function (Rule, $) { }); it('initiates a drag event when its grippy is clicked', function () { - testRule.grippy.trigger('mousedown'); + const event = new Event('mousedown', { + bubbles: true, + cancelable: true + }); + testRule.grippy.dispatchEvent(event); + expect(mockWidgetDnD.setDragImage).toHaveBeenCalled(); expect(mockWidgetDnD.dragStart).toHaveBeenCalledWith('mockRule'); }); diff --git a/src/plugins/summaryWidget/test/SummaryWidgetSpec.js b/src/plugins/summaryWidget/test/SummaryWidgetSpec.js index 1bee993e2d..877b1b366d 100644 --- a/src/plugins/summaryWidget/test/SummaryWidgetSpec.js +++ b/src/plugins/summaryWidget/test/SummaryWidgetSpec.js @@ -20,7 +20,7 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define(['../src/SummaryWidget', 'zepto'], function (SummaryWidget, $) { +define(['../src/SummaryWidget'], function (SummaryWidget) { xdescribe('The Summary Widget', function () { let summaryWidget; let mockDomainObject; @@ -111,7 +111,7 @@ define(['../src/SummaryWidget', 'zepto'], function (SummaryWidget, $) { }); it('builds rules and rule placeholders in view from configuration', function () { - expect($('.l-widget-rule', summaryWidget.ruleArea).get().length).toEqual(2); + expect(summaryWidget.ruleArea.querySelectorAll('.l-widget-rule').length).toEqual(2); }); it('allows initializing a new rule with a particular identifier', function () { @@ -130,7 +130,7 @@ define(['../src/SummaryWidget', 'zepto'], function (SummaryWidget, $) { mockDomainObject.configuration.ruleOrder.forEach(function (ruleId) { expect(mockDomainObject.configuration.ruleConfigById[ruleId]).toBeDefined(); }); - expect($('.l-widget-rule', summaryWidget.ruleArea).get().length).toEqual(6); + expect(summaryWidget.ruleArea.querySelectorAll('.l-widget-rule').length).toEqual(6); }); it('allows duplicating a rule from source configuration', function () { @@ -186,10 +186,10 @@ define(['../src/SummaryWidget', 'zepto'], function (SummaryWidget, $) { it('adds hyperlink to the widget button and sets newTab preference', function () { summaryWidget.addHyperlink('https://www.nasa.gov', 'newTab'); - const widgetButton = $('#widget', mockContainer); + const widgetButton = mockContainer.querySelector('#widget'); - expect(widgetButton.attr('href')).toEqual('https://www.nasa.gov'); - expect(widgetButton.attr('target')).toEqual('_blank'); + expect(widgetButton.href).toEqual('https://www.nasa.gov/'); + expect(widgetButton.target).toEqual('_blank'); }); }); }); diff --git a/src/plugins/summaryWidget/test/TestDataItemSpec.js b/src/plugins/summaryWidget/test/TestDataItemSpec.js index dffa6c6f22..171753efe4 100644 --- a/src/plugins/summaryWidget/test/TestDataItemSpec.js +++ b/src/plugins/summaryWidget/test/TestDataItemSpec.js @@ -1,4 +1,4 @@ -define(['../src/TestDataItem', 'zepto'], function (TestDataItem, $) { +define(['../src/TestDataItem'], function (TestDataItem) { describe('A summary widget test data item', function () { let testDataItem; let mockConfig; @@ -11,7 +11,7 @@ define(['../src/TestDataItem', 'zepto'], function (TestDataItem, $) { let generateValueSpy; beforeEach(function () { - mockContainer = $(document.createElement('div')); + mockContainer = document.createElement('div'); mockConfig = { object: 'object1', @@ -56,7 +56,7 @@ define(['../src/TestDataItem', 'zepto'], function (TestDataItem, $) { it('exposes a DOM element to represent itself in the view', function () { mockContainer.append(testDataItem.getDOM()); - expect($('.t-test-data-item', mockContainer).get().length).toEqual(1); + expect(mockContainer.querySelectorAll('.t-test-data-item').length).toEqual(1); }); it('responds to a change in its object select', function () { @@ -80,34 +80,54 @@ define(['../src/TestDataItem', 'zepto'], function (TestDataItem, $) { }); it('generates a value input of the appropriate type', function () { + let inputs; + mockContainer.append(testDataItem.getDOM()); mockEvaluator.getInputTypeById.and.returnValue('number'); testDataItem.generateValueInput(''); - expect($('input', mockContainer).filter('[type=number]').get().length).toEqual(1); - expect($('input', mockContainer).prop('valueAsNumber')).toEqual(1); + + inputs = mockContainer.querySelectorAll('input'); + const numberInputs = Array.from(inputs).filter(input => input.type === 'number'); + + expect(numberInputs.length).toEqual(1); + expect(inputs[0].valueAsNumber).toEqual(1); mockEvaluator.getInputTypeById.and.returnValue('text'); testDataItem.config.value = 'Text I Am'; testDataItem.generateValueInput(''); - expect($('input', mockContainer).filter('[type=text]').get().length).toEqual(1); - expect($('input', mockContainer).prop('value')).toEqual('Text I Am'); + + inputs = mockContainer.querySelectorAll('input'); + const textInputs = Array.from(inputs).filter(input => input.type === 'text'); + + expect(textInputs.length).toEqual(1); + expect(inputs[0].value).toEqual('Text I Am'); }); it('ensures reasonable defaults on values if none are provided', function () { + let inputs; + mockContainer.append(testDataItem.getDOM()); mockEvaluator.getInputTypeById.and.returnValue('number'); testDataItem.config.value = undefined; testDataItem.generateValueInput(''); - expect($('input', mockContainer).filter('[type=number]').get().length).toEqual(1); - expect($('input', mockContainer).prop('valueAsNumber')).toEqual(0); + + inputs = mockContainer.querySelectorAll('input'); + const numberInputs = Array.from(inputs).filter(input => input.type === 'number'); + + expect(numberInputs.length).toEqual(1); + expect(inputs[0].valueAsNumber).toEqual(0); expect(testDataItem.config.value).toEqual(0); mockEvaluator.getInputTypeById.and.returnValue('text'); testDataItem.config.value = undefined; testDataItem.generateValueInput(''); - expect($('input', mockContainer).filter('[type=text]').get().length).toEqual(1); - expect($('input', mockContainer).prop('value')).toEqual(''); + + inputs = mockContainer.querySelectorAll('input'); + const textInputs = Array.from(inputs).filter(input => input.type === 'text'); + + expect(textInputs.length).toEqual(1); + expect(inputs[0].value).toEqual(''); expect(testDataItem.config.value).toEqual(''); }); @@ -115,8 +135,15 @@ define(['../src/TestDataItem', 'zepto'], function (TestDataItem, $) { mockContainer.append(testDataItem.getDOM()); mockEvaluator.getInputTypeById.and.returnValue('number'); testDataItem.generateValueInput(''); - $('input', mockContainer).prop('value', 9001); - $('input', mockContainer).trigger('input'); + + const event = new Event('input', { + bubbles: true, + cancelable: true + }); + + mockContainer.querySelector('input').value = 9001; + mockContainer.querySelector('input').dispatchEvent(event); + expect(changeSpy).toHaveBeenCalledWith({ value: 9001, property: 'value', diff --git a/src/plugins/summaryWidget/test/TestDataManagerSpec.js b/src/plugins/summaryWidget/test/TestDataManagerSpec.js index 70042250d3..59ce37d92c 100644 --- a/src/plugins/summaryWidget/test/TestDataManagerSpec.js +++ b/src/plugins/summaryWidget/test/TestDataManagerSpec.js @@ -1,4 +1,4 @@ -define(['../src/TestDataManager', 'zepto'], function (TestDataManager, $) { +define(['../src/TestDataManager'], function (TestDataManager) { describe('A Summary Widget Rule', function () { let mockDomainObject; let mockOpenMCT; @@ -103,7 +103,7 @@ define(['../src/TestDataManager', 'zepto'], function (TestDataManager, $) { mockConditionManager.getObjectName.and.returnValue('Object Name'); mockConditionManager.getTelemetryPropertyName.and.returnValue('Property Name'); - mockContainer = $(document.createElement('div')); + mockContainer = document.createElement('div'); testDataManager = new TestDataManager(mockDomainObject, mockConditionManager, mockOpenMCT); }); @@ -114,7 +114,7 @@ define(['../src/TestDataManager', 'zepto'], function (TestDataManager, $) { it('exposes a DOM element to represent itself in the view', function () { mockContainer.append(testDataManager.getDOM()); - expect($('.t-widget-test-data-content', mockContainer).get().length).toBeGreaterThan(0); + expect(mockContainer.querySelectorAll('.t-widget-test-data-content').length).toBeGreaterThan(0); }); it('generates a test cache in the format expected by a condition evaluator', function () { @@ -207,7 +207,7 @@ define(['../src/TestDataManager', 'zepto'], function (TestDataManager, $) { it('builds item view from item configuration', function () { mockContainer.append(testDataManager.getDOM()); - expect($('.t-test-data-item', mockContainer).get().length).toEqual(3); + expect(mockContainer.querySelectorAll('.t-test-data-item').length).toEqual(3); }); it('can remove a item from its configuration', function () { diff --git a/src/utils/template/templateHelpers.js b/src/utils/template/templateHelpers.js new file mode 100644 index 0000000000..70d381ce7d --- /dev/null +++ b/src/utils/template/templateHelpers.js @@ -0,0 +1,14 @@ +export function convertTemplateToHTML(templateString) { + const template = document.createElement('template'); + template.innerHTML = templateString; + + return template.content.cloneNode(true).children; +} + +export function toggleClass(element, className) { + if (element.classList.contains(className)) { + element.classList.remove(className); + } else { + element.classList.add(className); + } +} diff --git a/webpack.common.js b/webpack.common.js index 5b1de5da4f..20f9705f48 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -101,13 +101,6 @@ const config = { test: /\.html$/, type: 'asset/source' }, - { - test: /zepto/, - use: [ - "imports-loader?this=>window", - "exports-loader?Zepto" - ] - }, { test: /\.(jpg|jpeg|png|svg)$/, type: 'asset/resource', From 40a74510648761fc6e82b0ed60aebb774f175041 Mon Sep 17 00:00:00 2001 From: Shefali Joshi Date: Fri, 3 Jun 2022 09:46:27 -0700 Subject: [PATCH 07/21] Fix stackplots static style (#5045) * [4864] Fixes cancelling edit properties console error * Get the style receiver when the styleRuleManager is initialized. This prevents any ambiguity about which element should receive the style * Don't subscribe if the styleRuleManager has been destroyed Co-authored-by: John Hill Co-authored-by: Jamie V Co-authored-by: Nikhil Co-authored-by: Andrew Henry --- .circleci/config.yml | 10 +++++----- src/plugins/condition/StyleRuleManager.js | 6 ++++-- src/plugins/formActions/EditPropertiesAction.js | 10 +++++++++- src/plugins/formActions/pluginSpec.js | 7 +++++++ 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3268c660a6..8863b9e057 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,7 @@ commands: type: string steps: - when: - condition: + condition: equal: [false, << pipeline.parameters.BUST_CACHE >> ] steps: - restore_cache: @@ -41,7 +41,7 @@ commands: parameters: node-version: type: string - steps: + steps: - save_cache: key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }} paths: @@ -61,7 +61,7 @@ commands: upload_code_covio: description: "Command to upload code coverage reports to codecov.io" steps: - - run: curl -Os https://uploader.codecov.io/latest/linux/codecov;chmod +x codecov;./codecov + - run: curl -Os https://uploader.codecov.io/latest/linux/codecov;chmod +x codecov;./codecov orbs: node: circleci/node@4.9.0 browser-tools: circleci/browser-tools@1.3.0 @@ -101,7 +101,7 @@ jobs: equal: [ "FirefoxESR", <> ] steps: - browser-tools/install-firefox: - version: "91.7.1esr" #https://archive.mozilla.org/pub/firefox/releases/ + version: "91.7.1esr" #https://archive.mozilla.org/pub/firefox/releases/ - when: condition: equal: [ "FirefoxHeadless", <> ] @@ -158,7 +158,7 @@ workflows: - lint: name: node16-lint node-version: lts/gallium - - unit-test: + - unit-test: name: node14-chrome node-version: lts/fermium browser: ChromeHeadless diff --git a/src/plugins/condition/StyleRuleManager.js b/src/plugins/condition/StyleRuleManager.js index e7f201ca7e..18063b337c 100644 --- a/src/plugins/condition/StyleRuleManager.js +++ b/src/plugins/condition/StyleRuleManager.js @@ -78,11 +78,13 @@ export default class StyleRuleManager extends EventEmitter { this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => { this.openmct.telemetry.request(conditionSetDomainObject) .then(output => { - if (output && output.length) { + if (output && output.length && (this.conditionSetIdentifier && this.openmct.objects.areIdsEqual(conditionSetDomainObject.identifier, this.conditionSetIdentifier))) { this.handleConditionSetResultUpdated(output[0]); } }); - this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this)); + if (this.conditionSetIdentifier && this.openmct.objects.areIdsEqual(conditionSetDomainObject.identifier, this.conditionSetIdentifier)) { + this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this)); + } }); } diff --git a/src/plugins/formActions/EditPropertiesAction.js b/src/plugins/formActions/EditPropertiesAction.js index 53afadfe90..65ceaaadd1 100644 --- a/src/plugins/formActions/EditPropertiesAction.js +++ b/src/plugins/formActions/EditPropertiesAction.js @@ -76,6 +76,13 @@ export default class EditPropertiesAction extends PropertiesAction { } } + /** + * @private + */ + _onCancel() { + //noop + } + /** * @private */ @@ -87,6 +94,7 @@ export default class EditPropertiesAction extends PropertiesAction { formStructure.title = 'Edit ' + this.domainObject.name; return this.openmct.forms.showForm(formStructure) - .then(this._onSave.bind(this)); + .then(this._onSave.bind(this)) + .catch(this._onCancel.bind(this)); } } diff --git a/src/plugins/formActions/pluginSpec.js b/src/plugins/formActions/pluginSpec.js index 9c4cbb2cc0..232ff0d303 100644 --- a/src/plugins/formActions/pluginSpec.js +++ b/src/plugins/formActions/pluginSpec.js @@ -123,6 +123,9 @@ describe('EditPropertiesAction plugin', () => { } editPropertiesAction.invoke([domainObject]) + .then(() => { + done(); + }) .catch(() => { done(); }); @@ -208,6 +211,10 @@ describe('EditPropertiesAction plugin', () => { }; editPropertiesAction.invoke([domainObject]) + .then(() => { + expect(domainObject.name).toEqual(name); + done(); + }) .catch(() => { expect(domainObject.name).toEqual(name); From 1c525f50c88cb662699811c620e59ee285b67abd Mon Sep 17 00:00:00 2001 From: Charles Hacskaylo Date: Fri, 3 Jun 2022 09:53:21 -0700 Subject: [PATCH 08/21] Display Layout toolbar refinements for units (#5197) * Fixes #3197 - Moved position of hide/show units toggle button. - Added labels to many toolbar buttons, including hide/show units, hide/show frame, edit text, more. - Added label to toolbar-toggle-button.vue. - Added separator between stackOrder button and position inputs. * Fixes #3197 - Removed unwanted margin in alphanumerics when label is hidden. Co-authored-by: Shefali Joshi --- .../displayLayout/DisplayLayoutToolbar.js | 40 ++++++++++++------- .../components/telemetry-view.scss | 8 ++-- src/plugins/displayLayout/pluginSpec.js | 2 +- .../components/toolbar-toggle-button.vue | 12 ++++-- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/plugins/displayLayout/DisplayLayoutToolbar.js b/src/plugins/displayLayout/DisplayLayoutToolbar.js index 24038b8555..5bcf3ee45d 100644 --- a/src/plugins/displayLayout/DisplayLayoutToolbar.js +++ b/src/plugins/displayLayout/DisplayLayoutToolbar.js @@ -211,13 +211,15 @@ define(['lodash'], function (_) { options: [ { value: false, - icon: 'icon-frame-show', - title: "Frame visible" + icon: 'icon-frame-hide', + title: "Frame visible", + label: 'Hide frame' }, { value: true, - icon: 'icon-frame-hide', - title: "Frame hidden" + icon: 'icon-frame-show', + title: "Frame hidden", + label: 'Show frame' } ] }; @@ -401,6 +403,7 @@ define(['lodash'], function (_) { }, icon: "icon-pencil", title: "Edit text properties", + label: "Edit text", dialog: DIALOG_FORM.text }; } @@ -514,12 +517,14 @@ define(['lodash'], function (_) { { value: true, icon: 'icon-eye-open', - title: "Show units" + title: "Show units", + label: "Show units" }, { value: false, icon: 'icon-eye-disabled', - title: "Hide units" + title: "Hide units", + label: "Hide units" } ] }; @@ -562,6 +567,7 @@ define(['lodash'], function (_) { domainObject: selectedParent, icon: "icon-object", title: "Switch the way this telemetry is displayed", + label: "View type", options: viewOptions, method: function (option) { displayLayoutContext.switchViewType(selectedItemContext, option.value, selection); @@ -662,9 +668,9 @@ define(['lodash'], function (_) { 'display-mode': [], 'telemetry-value': [], 'style': [], + 'unit-toggle': [], 'position': [], 'duplicate': [], - 'unit-toggle': [], 'remove': [], 'toggle-grid': [] }; @@ -689,6 +695,7 @@ define(['lodash'], function (_) { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -712,9 +719,17 @@ define(['lodash'], function (_) { toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selectedObjects)]; } + if (toolbar['unit-toggle'].length === 0) { + let toggleUnitsButton = getToggleUnitsButton(selectedParent, selectedObjects); + if (toggleUnitsButton) { + toolbar['unit-toggle'] = [toggleUnitsButton]; + } + } + if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -729,17 +744,11 @@ define(['lodash'], function (_) { if (toolbar.viewSwitcher.length === 0) { toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)]; } - - if (toolbar['unit-toggle'].length === 0) { - let toggleUnitsButton = getToggleUnitsButton(selectedParent, selectedObjects); - if (toggleUnitsButton) { - toolbar['unit-toggle'] = [toggleUnitsButton]; - } - } } else if (layoutItem.type === 'text-view') { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -758,6 +767,7 @@ define(['lodash'], function (_) { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -772,6 +782,7 @@ define(['lodash'], function (_) { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), @@ -786,6 +797,7 @@ define(['lodash'], function (_) { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), + getSeparator(), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getX2Input(selectedParent, selectedObjects), diff --git a/src/plugins/displayLayout/components/telemetry-view.scss b/src/plugins/displayLayout/components/telemetry-view.scss index 0dbfc75ac6..8b73d118a0 100644 --- a/src/plugins/displayLayout/components/telemetry-view.scss +++ b/src/plugins/displayLayout/components/telemetry-view.scss @@ -17,14 +17,14 @@ } } - > * + * { - margin-left: $interiorMargin; - } - &__value { @include isLimit(); } + &__label { + margin-right: $interiorMargin; + } + .c-frame & { @include abs(); border: 1px solid transparent; diff --git a/src/plugins/displayLayout/pluginSpec.js b/src/plugins/displayLayout/pluginSpec.js index 643f13be6e..a1ac3764a6 100644 --- a/src/plugins/displayLayout/pluginSpec.js +++ b/src/plugins/displayLayout/pluginSpec.js @@ -351,7 +351,7 @@ describe('the plugin', function () { it('provides controls including separators', () => { const displayLayoutToolbar = openmct.toolbars.get(selection); - expect(displayLayoutToolbar.length).toBe(7); + expect(displayLayoutToolbar.length).toBe(8); }); }); }); diff --git a/src/ui/toolbar/components/toolbar-toggle-button.vue b/src/ui/toolbar/components/toolbar-toggle-button.vue index e3d37c4c5f..27ca3d62f0 100644 --- a/src/ui/toolbar/components/toolbar-toggle-button.vue +++ b/src/ui/toolbar/components/toolbar-toggle-button.vue @@ -5,9 +5,15 @@ :title="nextValue.title" :class="[nextValue.icon, {'c-icon-button--mixed': nonSpecific}]" @click="cycle" - >
-
- + > +
+ {{ nextValue.label }} +
+ + diff --git a/src/ui/components/tags/TagEditor.vue b/src/ui/components/tags/TagEditor.vue new file mode 100644 index 0000000000..65d9ace133 --- /dev/null +++ b/src/ui/components/tags/TagEditor.vue @@ -0,0 +1,155 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + + + + diff --git a/src/ui/components/tags/TagSelection.vue b/src/ui/components/tags/TagSelection.vue new file mode 100644 index 0000000000..3163ae0456 --- /dev/null +++ b/src/ui/components/tags/TagSelection.vue @@ -0,0 +1,152 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + + + + diff --git a/src/ui/components/tags/tags.scss b/src/ui/components/tags/tags.scss new file mode 100644 index 0000000000..ebd3e7a184 --- /dev/null +++ b/src/ui/components/tags/tags.scss @@ -0,0 +1,67 @@ +/******************************* TAGS */ +.c-tag { + border-radius: 10px; //TODO: convert to theme constant + display: inline-flex; + padding: 1px 10px; //TODO: convert to theme constant + + > * + * { + margin-left: $interiorMargin; + } + + &__remove-btn { + color: inherit !important; + display: none; + opacity: 0; + overflow: hidden; + padding: 1px !important; + transition: $transIn; + width: 0; + + &:hover { + opacity: 1; + } + } + + /* SEARCH RESULTS */ + &.--is-not-search-match { + opacity: 0.5; + } +} + +/******************************* TAG EDITOR */ +.c-tag-applier { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + + > * + * { + margin-left: $interiorMargin; + } + + &__add-btn { + &:before { font-size: 0.9em; } + } + + .c-tag { + flex-direction: row; + align-items: center; + padding-right: 3px !important; + + &__remove-btn { + display: block; + } + } +} + +/******************************* HOVERS */ +.has-tag-applier { + // Apply this class to all components that should trigger tag removal btn on hover + &:hover { + .c-tag__remove-btn { + width: 1.1em; + opacity: 0.7; + transition: $transOut; + } + } + } diff --git a/src/ui/layout/Layout.vue b/src/ui/layout/Layout.vue index 2ded6498d6..ee52964363 100644 --- a/src/ui/layout/Layout.vue +++ b/src/ui/layout/Layout.vue @@ -18,6 +18,9 @@ }" > + + + + + + diff --git a/src/ui/layout/search/GrandSearch.vue b/src/ui/layout/search/GrandSearch.vue new file mode 100644 index 0000000000..788ae1e589 --- /dev/null +++ b/src/ui/layout/search/GrandSearch.vue @@ -0,0 +1,145 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + + + + diff --git a/src/ui/layout/search/ObjectSearchResult.vue b/src/ui/layout/search/ObjectSearchResult.vue new file mode 100644 index 0000000000..2e3416a8ee --- /dev/null +++ b/src/ui/layout/search/ObjectSearchResult.vue @@ -0,0 +1,102 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + + + + diff --git a/src/ui/layout/search/SearchResultsDropDown.vue b/src/ui/layout/search/SearchResultsDropDown.vue new file mode 100644 index 0000000000..21e1487cb8 --- /dev/null +++ b/src/ui/layout/search/SearchResultsDropDown.vue @@ -0,0 +1,99 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + + + + diff --git a/src/ui/layout/search/search.scss b/src/ui/layout/search/search.scss new file mode 100644 index 0000000000..fe3e3513db --- /dev/null +++ b/src/ui/layout/search/search.scss @@ -0,0 +1,137 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + +/******************************* EXPANDED SEARCH 2022 */ +.c-gsearch { + .l-shell__head & { + // Search input in the shell head + width: 20%; + + .c-search { + background: rgba($colorHeadFg, 0.2); + box-shadow: none; + } + } + + &__results-wrapper { + @include menuOuter(); + display: flex; + flex-direction: column; + padding: $interiorMarginLg; + min-width: 500px; + max-height: 500px; + } + + &__results, + &__results-section { + flex: 1 1 auto; + } + + &__results { + // Holds n __results-sections + padding-right: $interiorMargin; // Fend off scrollbar + overflow-y: auto; + + > * + * { + margin-top: $interiorMarginLg; + } + } + + &__results-section { + > * + * { + margin-top: $interiorMarginSm; + } + } + + &__results-section-title { + @include propertiesHeader(); + } +} + +.c-gsearch-result { + display: flex; + padding: $interiorMargin $interiorMarginSm; + + > * + * { + margin-left: $interiorMarginLg; + } + + + .c-gsearch-result { + border-top: 1px solid $colorInteriorBorder; + } + + &__type-icon, + &__more-options-button { + flex: 0 0 auto; + } + + &__type-icon { + color: $colorItemTreeIcon; + font-size: 2.2em; + + // TEMP: uses object-label component, hide label part + .c-object-label__name { + display: none; + } + } + + &__more-options-button { + display: none; // TEMP until enabled + } + + &__body { + flex: 1 1 auto; + + > * + * { + margin-top: $interiorMarginSm; + } + + .c-location { + font-size: 0.9em; + opacity: 0.8; + } + } + + &__tags { + display: flex; + + > * + * { + margin-left: $interiorMargin; + } + } + + &__title { + border-radius: $basicCr; + color: pullForward($colorBodyFg, 30%); + cursor: pointer; + font-size: 1.15em; + padding: 3px $interiorMarginSm; + + &:hover { + background-color: $colorItemTreeHoverBg; + } + } + + .c-tag { + font-size: 0.9em; + } +} diff --git a/src/ui/mixins/context-menu-gesture.js b/src/ui/mixins/context-menu-gesture.js index 404015f383..ad7fa0de31 100644 --- a/src/ui/mixins/context-menu-gesture.js +++ b/src/ui/mixins/context-menu-gesture.js @@ -33,6 +33,10 @@ export default { }, methods: { showContextMenu(event) { + if (this.readOnly) { + return; + } + event.preventDefault(); event.stopPropagation(); diff --git a/src/ui/router/Browse.js b/src/ui/router/Browse.js index 05106815ae..1c8f622457 100644 --- a/src/ui/router/Browse.js +++ b/src/ui/router/Browse.js @@ -133,9 +133,7 @@ define([ composition.load() .then(children => { let lastChild = children[children.length - 1]; - if (!lastChild) { - console.debug('Unable to navigate to anything. No root objects found.'); - } else { + if (lastChild) { let lastChildId = openmct.objects.makeKeyString(lastChild.identifier); openmct.router.setPath(`#/browse/${lastChildId}`); } From 59c0da1b5753fc1fd1630d42a401c4de822131eb Mon Sep 17 00:00:00 2001 From: Charles Hacskaylo Date: Fri, 3 Jun 2022 14:34:03 -0700 Subject: [PATCH 15/21] Add units to Gauges (#5196) * Fixes #3197 - Code and styling to allow units display. Co-authored-by: Shefali Joshi Co-authored-by: Nikhil Co-authored-by: Andrew Henry --- src/plugins/gauge/GaugePlugin.js | 46 ++++++++++++------- src/plugins/gauge/GaugePluginSpec.js | 38 ++++++++------- src/plugins/gauge/components/Gauge.vue | 38 +++++++++++++-- .../gauge/components/GaugeFormController.vue | 2 + src/plugins/gauge/gauge.scss | 12 ++--- 5 files changed, 94 insertions(+), 42 deletions(-) diff --git a/src/plugins/gauge/GaugePlugin.js b/src/plugins/gauge/GaugePlugin.js index c9db912df1..441e53cd57 100644 --- a/src/plugins/gauge/GaugePlugin.js +++ b/src/plugins/gauge/GaugePlugin.js @@ -49,6 +49,7 @@ export default function () { gaugeType: GAUGE_TYPES[0][1], isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: true, limitLow: 10, limitHigh: 90, @@ -59,6 +60,23 @@ export default function () { }; }, form: [ + { + name: "Gauge type", + options: GAUGE_TYPES.map(type => { + return { + name: type[0], + value: type[1] + }; + }), + control: "select", + cssClass: "l-input-sm", + key: "gaugeController", + property: [ + "configuration", + "gaugeController", + "gaugeType" + ] + }, { name: "Display current value", control: "toggleSwitch", @@ -70,6 +88,17 @@ export default function () { "isDisplayCurVal" ] }, + { + name: "Display units", + control: "toggleSwitch", + cssClass: "l-input", + key: "isDisplayUnits", + property: [ + "configuration", + "gaugeController", + "isDisplayUnits" + ] + }, { name: "Display range values", control: "toggleSwitch", @@ -92,23 +121,6 @@ export default function () { "precision" ] }, - { - name: "Gauge type", - options: GAUGE_TYPES.map(type => { - return { - name: type[0], - value: type[1] - }; - }), - control: "select", - cssClass: "l-input-sm", - key: "gaugeController", - property: [ - "configuration", - "gaugeController", - "gaugeType" - ] - }, { name: "Value ranges and limits", control: "gauge-controller", diff --git a/src/plugins/gauge/GaugePluginSpec.js b/src/plugins/gauge/GaugePluginSpec.js index 5894498063..0c6b3d7a1f 100644 --- a/src/plugins/gauge/GaugePluginSpec.js +++ b/src/plugins/gauge/GaugePluginSpec.js @@ -63,30 +63,30 @@ describe('Gauge plugin', () => { }); it('Plugin installed by default', () => { - const gaugueType = openmct.types.get('gauge'); + const GaugeType = openmct.types.get('gauge'); - expect(gaugueType).not.toBeNull(); - expect(gaugueType.definition.name).toEqual('Gauge'); + expect(GaugeType).not.toBeNull(); + expect(GaugeType.definition.name).toEqual('Gauge'); }); - it('Gaugue plugin is creatable', () => { - const gaugueType = openmct.types.get('gauge'); + it('Gauge plugin is creatable', () => { + const GaugeType = openmct.types.get('gauge'); - expect(gaugueType.definition.creatable).toBeTrue(); + expect(GaugeType.definition.creatable).toBeTrue(); }); - it('Gaugue plugin is creatable', () => { - const gaugueType = openmct.types.get('gauge'); + it('Gauge plugin is creatable', () => { + const GaugeType = openmct.types.get('gauge'); - expect(gaugueType.definition.creatable).toBeTrue(); + expect(GaugeType.definition.creatable).toBeTrue(); }); - it('Gaugue form controller', () => { + it('Gauge form controller', () => { const gaugeController = openmct.forms.getFormControl('gauge-controller'); expect(gaugeController).toBeDefined(); }); - describe('Gaugue with Filled Dial', () => { + describe('Gauge with Filled Dial', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -105,6 +105,7 @@ describe('Gauge plugin', () => { gaugeType: 'dial-filled', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -222,7 +223,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Needle Dial', () => { + describe('Gauge with Needle Dial', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -240,6 +241,7 @@ describe('Gauge plugin', () => { gaugeType: 'dial-needle', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -357,7 +359,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Vertical Meter', () => { + describe('Gauge with Vertical Meter', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -375,6 +377,7 @@ describe('Gauge plugin', () => { gaugeType: 'meter-vertical', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -492,7 +495,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Vertical Meter Inverted', () => { + describe('Gauge with Vertical Meter Inverted', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -506,6 +509,7 @@ describe('Gauge plugin', () => { gaugeType: 'meter-vertical', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -574,7 +578,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Horizontal Meter', () => { + describe('Gauge with Horizontal Meter', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -588,6 +592,7 @@ describe('Gauge plugin', () => { gaugeType: 'meter-vertical', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: false, limitLow: -0.9, limitHigh: 0.9, @@ -656,7 +661,7 @@ describe('Gauge plugin', () => { }); }); - describe('Gaugue with Filled Dial with Use Telemetry Limits', () => { + describe('Gauge with Filled Dial with Use Telemetry Limits', () => { let gaugeViewProvider; let gaugeView; let gaugeViewObject; @@ -673,6 +678,7 @@ describe('Gauge plugin', () => { gaugeType: 'dial-filled', isDisplayMinMax: true, isDisplayCurVal: true, + isDisplayUnits: true, isUseTelemetryLimits: true, limitLow: 10, limitHigh: 90, diff --git a/src/plugins/gauge/components/Gauge.vue b/src/plugins/gauge/components/Gauge.vue index 19fa4a8218..5c1ed0f503 100644 --- a/src/plugins/gauge/components/Gauge.vue +++ b/src/plugins/gauge/components/Gauge.vue @@ -64,11 +64,11 @@ @@ -79,6 +79,17 @@ style="transform: translate(50%, 70%)" >{{ curVal }} + + {{ units }} + {{ curVal }} + > + {{ curVal }} + {{ units }} + + {{ units }} @@ -288,12 +315,15 @@ export default { precision: gaugeController.precision, displayMinMax: gaugeController.isDisplayMinMax, displayCurVal: gaugeController.isDisplayCurVal, + displayUnits: gaugeController.isDisplayUnits, limitHigh: gaugeController.limitHigh, limitLow: gaugeController.limitLow, rangeHigh: gaugeController.max, rangeLow: gaugeController.min, gaugeType: gaugeController.gaugeType, - activeTimeSystem: this.openmct.time.timeSystem() + showUnits: gaugeController.showUnits, + activeTimeSystem: this.openmct.time.timeSystem(), + units: '' }; }, computed: { @@ -524,6 +554,8 @@ export default { const length = values.length; this.updateValue(values[length - 1]); }); + + this.units = this.metadata.value(this.valueKey).unit || ''; }, round(val, decimals = this.precision) { let precision = Math.pow(10, decimals); diff --git a/src/plugins/gauge/components/GaugeFormController.vue b/src/plugins/gauge/components/GaugeFormController.vue index ea80457880..648e0c12d3 100644 --- a/src/plugins/gauge/components/GaugeFormController.vue +++ b/src/plugins/gauge/components/GaugeFormController.vue @@ -111,6 +111,7 @@ export default { isUseTelemetryLimits: this.model.value.isUseTelemetryLimits, isDisplayMinMax: this.model.value.isDisplayMinMax, isDisplayCurVal: this.model.value.isDisplayCurVal, + isDisplayUnits: this.model.value.isDisplayUnits, limitHigh: this.model.value.limitHigh, limitLow: this.model.value.limitLow, max: this.model.value.max, @@ -125,6 +126,7 @@ export default { gaugeType: this.model.value.gaugeType, isDisplayMinMax: this.isDisplayMinMax, isDisplayCurVal: this.isDisplayCurVal, + isDisplayUnits: this.isDisplayUnits, isUseTelemetryLimits: this.isUseTelemetryLimits, limitLow: this.limitLow, limitHigh: this.limitHigh, diff --git a/src/plugins/gauge/gauge.scss b/src/plugins/gauge/gauge.scss index a56f566a6e..3ce533a033 100644 --- a/src/plugins/gauge/gauge.scss +++ b/src/plugins/gauge/gauge.scss @@ -16,13 +16,12 @@ // Both dial and meter types overflow: hidden; - &__range { + &__range, + &__units, + &__units text { $c: $colorGaugeRange; color: $c; - - text { - fill: $c; - } + fill: $c; } &__wrapper { @@ -66,7 +65,8 @@ svg[class*='c-dial'] { transition: transform $transitionTimeGauge; } - &__current-value-text { + &__current-value-text, + &__units-text { fill: $colorGaugeTextValue; font-family: $heroFont; } From 111b0d0d68d3f93cdd37b8b5ba81eaa1d989d38c Mon Sep 17 00:00:00 2001 From: Nikhil Date: Fri, 3 Jun 2022 18:24:43 -0700 Subject: [PATCH 16/21] Imagery layers (#4968) * Moved imagery controls to a separate component * Zoom pan controls moved to component * Implement adjustments to encapsulate state into ImageryControls * Track modifier key pressed for layouts * image control popup open/close fix * Styling for imagery local controls Co-authored-by: Michael Rogers Co-authored-by: Shefali Joshi Co-authored-by: Andrew Henry Co-authored-by: Scott Bell Co-authored-by: Charles Hacskaylo Co-authored-by: unlikelyzero Co-authored-by: Jamie Vigliotta Co-authored-by: John Hill --- e2e/test-data/PerformanceDisplayLayout.json | 2 +- e2e/tests/performance/imagery.perf.spec.js | 2 +- .../imagery/exampleImagery.e2e.spec.js | 57 +++-- example/imagery/plugin.js | 19 +- .../overlays/components/OverlayComponent.vue | 1 + .../displayLayout/components/LayoutFrame.vue | 3 +- .../components/layout-frame.scss | 4 - .../imagery/components/FilterSettings.vue | 74 ++++++ .../imagery/components/ImageControls.vue | 131 ++++++----- .../imagery/components/ImageryView.vue | 125 +++++++--- .../components/ImageryViewMenuSwitcher.vue | 65 ++++++ .../imagery/components/LayerSettings.vue | 59 +++++ .../imagery/components/ZoomSettings.vue | 89 +++++++ .../imagery/components/imagery-view.scss | 220 +++++++++++------- .../layers/example-imagery-layer-16x9.png | Bin 0 -> 8554 bytes .../layers/example-imagery-layer-safe.png | Bin 0 -> 9245 bytes .../layers/example-imagery-layer-scale.png | Bin 0 -> 11616 bytes src/plugins/imagery/pluginSpec.js | 24 ++ .../telemetryTable/components/table.scss | 5 +- src/styles/_controls.scss | 25 ++ src/styles/_global.scss | 19 ++ src/styles/_legacy-plots.scss | 4 +- src/styles/_table.scss | 2 +- src/ui/components/ObjectFrame.vue | 25 ++ src/ui/components/object-frame.scss | 12 +- webpack.common.js | 4 + 26 files changed, 748 insertions(+), 223 deletions(-) create mode 100644 src/plugins/imagery/components/FilterSettings.vue create mode 100644 src/plugins/imagery/components/ImageryViewMenuSwitcher.vue create mode 100644 src/plugins/imagery/components/LayerSettings.vue create mode 100644 src/plugins/imagery/components/ZoomSettings.vue create mode 100644 src/plugins/imagery/layers/example-imagery-layer-16x9.png create mode 100644 src/plugins/imagery/layers/example-imagery-layer-safe.png create mode 100644 src/plugins/imagery/layers/example-imagery-layer-scale.png diff --git a/e2e/test-data/PerformanceDisplayLayout.json b/e2e/test-data/PerformanceDisplayLayout.json index eebc7635ad..de81d7b4ca 100644 --- a/e2e/test-data/PerformanceDisplayLayout.json +++ b/e2e/test-data/PerformanceDisplayLayout.json @@ -1 +1 @@ -{"openmct":{"21338566-d472-4377-aed1-21b79272c8de":{"identifier":{"key":"21338566-d472-4377-aed1-21b79272c8de","namespace":""},"name":"Performance Display Layout","type":"layout","composition":[{"key":"644c2e47-2903-475f-8a4a-6be1588ee02f","namespace":""}],"configuration":{"items":[{"width":32,"height":18,"x":1,"y":1,"identifier":{"key":"644c2e47-2903-475f-8a4a-6be1588ee02f","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"5aeb5a71-3149-41ed-9d8a-d34b0a18b053"}],"layoutGrid":[10,10]},"modified":1652228997384,"location":"mine","persisted":1652228997384},"644c2e47-2903-475f-8a4a-6be1588ee02f":{"identifier":{"key":"644c2e47-2903-475f-8a4a-6be1588ee02f","namespace":""},"name":"Performance Example Imagery","type":"example.imagery","configuration":{"imageLocation":"","imageLoadDelayInMilliSeconds":20000,"imageSamples":[]},"telemetry":{"values":[{"name":"Name","key":"name"},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2}},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1}},{"name":"Image","key":"url","format":"image","hints":{"image":1}},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1}}]},"modified":1652228997375,"location":"21338566-d472-4377-aed1-21b79272c8de","persisted":1652228997375}},"rootId":"21338566-d472-4377-aed1-21b79272c8de"} \ No newline at end of file +{"openmct":{"b3cee102-86dd-4c0a-8eec-4d5d276f8691":{"identifier":{"key":"b3cee102-86dd-4c0a-8eec-4d5d276f8691","namespace":""},"name":"Performance Display Layout","type":"layout","composition":[{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""}],"configuration":{"items":[{"width":32,"height":18,"x":12,"y":9,"identifier":{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"23ca351d-a67d-46aa-a762-290eb742d2f1"}],"layoutGrid":[10,10]},"modified":1654299875432,"location":"mine","persisted":1654299878751},"9666e7b4-be0c-47a5-94b8-99accad7155e":{"identifier":{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""},"name":"Performance Example Imagery","type":"example.imagery","configuration":{"imageLocation":"","imageLoadDelayInMilliSeconds":20000,"imageSamples":[],"layers":[{"source":"dist/imagery/example-imagery-layer-16x9.png","name":"16:9","visible":false},{"source":"dist/imagery/example-imagery-layer-safe.png","name":"Safe","visible":false},{"source":"dist/imagery/example-imagery-layer-scale.png","name":"Scale","visible":false}]},"telemetry":{"values":[{"name":"Name","key":"name"},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2}},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1}},{"name":"Image","key":"url","format":"image","hints":{"image":1},"layers":[{"source":"dist/imagery/example-imagery-layer-16x9.png","name":"16:9"},{"source":"dist/imagery/example-imagery-layer-safe.png","name":"Safe"},{"source":"dist/imagery/example-imagery-layer-scale.png","name":"Scale"}]},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1}}]},"modified":1654299840077,"location":"b3cee102-86dd-4c0a-8eec-4d5d276f8691","persisted":1654299840078}},"rootId":"b3cee102-86dd-4c0a-8eec-4d5d276f8691"} \ No newline at end of file diff --git a/e2e/tests/performance/imagery.perf.spec.js b/e2e/tests/performance/imagery.perf.spec.js index e7033ef10d..433bc1699d 100644 --- a/e2e/tests/performance/imagery.perf.spec.js +++ b/e2e/tests/performance/imagery.perf.spec.js @@ -164,7 +164,7 @@ test.describe('Performance tests', () => { console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming)); // Click Close Icon - await page.locator('.c-click-icon').click(); + await page.locator('[aria-label="Close"]').click(); await page.evaluate(() => window.performance.mark("view-large-close-button")); //await client.send('HeapProfiler.enable'); diff --git a/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js index d5c204cdb2..0a570e5c2a 100644 --- a/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js +++ b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js @@ -32,7 +32,6 @@ const { 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' }); @@ -61,19 +60,19 @@ test.describe('Example Imagery', () => { test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => { const bgImageLocator = page.locator(backgroundImageSelector); const deltaYStep = 100; //equivalent to 1x zoom - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox(); // zoom in - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); await page.mouse.wheel(0, deltaYStep * 2); // wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox(); // zoom out - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); await page.mouse.wheel(0, -deltaYStep); // wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); const imageMouseZoomedOut = await page.locator(backgroundImageSelector).boundingBox(); expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height); @@ -88,11 +87,11 @@ test.describe('Example Imagery', () => { const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt']; const bgImageLocator = page.locator(backgroundImageSelector); - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); // zoom in await page.mouse.wheel(0, deltaYStep * 2); - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); const zoomedBoundingBox = await bgImageLocator.boundingBox(); const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2; const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2; @@ -151,22 +150,22 @@ test.describe('Example Imagery', () => { test('Can use + - buttons to zoom on the image', async ({ page }) => { const bgImageLocator = page.locator(backgroundImageSelector); - await bgImageLocator.hover(); - const zoomInBtn = page.locator('.t-btn-zoom-in'); - const zoomOutBtn = page.locator('.t-btn-zoom-out'); + await bgImageLocator.hover({trial: true}); + const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0); + const zoomOutBtn = page.locator('.t-btn-zoom-out').nth(0); const initialBoundingBox = await bgImageLocator.boundingBox(); await zoomInBtn.click(); await zoomInBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); 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(); + await bgImageLocator.hover({trial: true}); const zoomedOutBoundingBox = await bgImageLocator.boundingBox(); expect(zoomedOutBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height); expect(zoomedOutBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width); @@ -176,18 +175,18 @@ test.describe('Example Imagery', () => { test('Can use the reset button to reset the image', async ({ page }) => { const bgImageLocator = page.locator(backgroundImageSelector); // wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); - const zoomInBtn = page.locator('.t-btn-zoom-in'); - const zoomResetBtn = page.locator('.t-btn-zoom-reset'); + const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0); + const zoomResetBtn = page.locator('.t-btn-zoom-reset').nth(0); const initialBoundingBox = await bgImageLocator.boundingBox(); await zoomInBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); await zoomInBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); const zoomedInBoundingBox = await bgImageLocator.boundingBox(); expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height); @@ -195,7 +194,7 @@ test.describe('Example Imagery', () => { await zoomResetBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); const resetBoundingBox = await bgImageLocator.boundingBox(); expect.soft(resetBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height); @@ -209,18 +208,18 @@ test.describe('Example Imagery', () => { const bgImageLocator = page.locator(backgroundImageSelector); const pausePlayButton = page.locator('.c-button.pause-play'); // wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); // open the time conductor drop down - await page.locator('.c-conductor__controls button.c-mode-button').click(); + await page.locator('button:has-text("Fixed Timespan")').click(); // Click local clock - await page.locator('.icon-clock >> text=Local Clock').click(); + await page.locator('[data-testid="conductor-modeOption-realtime"]').click(); await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/); - const zoomInBtn = page.locator('.t-btn-zoom-in'); + const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0); await zoomInBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); return expect(pausePlayButton).not.toHaveClass(/is-paused/); }); @@ -267,7 +266,7 @@ test('Example Imagery in Display layout', async ({ page }) => { await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery'); const bgImageLocator = page.locator(backgroundImageSelector); - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); // Click previous image button const previousImageButton = page.locator('.c-nav--prev'); @@ -279,7 +278,7 @@ test('Example Imagery in Display layout', async ({ page }) => { // Zoom in const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox(); - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); const deltaYStep = 100; // equivalent to 1x zoom await page.mouse.wheel(0, deltaYStep * 2); const zoomedBoundingBox = await bgImageLocator.boundingBox(); @@ -287,7 +286,7 @@ test('Example Imagery in Display layout', async ({ page }) => { const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2; // Wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox(); expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height); expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width); @@ -311,11 +310,11 @@ test('Example Imagery in Display layout', async ({ page }) => { await page.locator('[data-testid=conductor-modeOption-realtime]').click(); // Zoom in on next image - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); await page.mouse.wheel(0, deltaYStep * 2); // Wait for zoom animation to finish - await bgImageLocator.hover(); + await bgImageLocator.hover({trial: true}); const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox(); expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height); expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width); diff --git a/example/imagery/plugin.js b/example/imagery/plugin.js index 6823ede509..47d6f4ef70 100644 --- a/example/imagery/plugin.js +++ b/example/imagery/plugin.js @@ -59,7 +59,8 @@ export default function () { object.configuration = { imageLocation: '', imageLoadDelayInMilliSeconds: DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS, - imageSamples: [] + imageSamples: [], + layers: [] }; object.telemetry = { @@ -90,7 +91,21 @@ export default function () { format: 'image', hints: { image: 1 - } + }, + layers: [ + { + source: 'dist/imagery/example-imagery-layer-16x9.png', + name: '16:9' + }, + { + source: 'dist/imagery/example-imagery-layer-safe.png', + name: 'Safe' + }, + { + source: 'dist/imagery/example-imagery-layer-scale.png', + name: 'Scale' + } + ] }, { name: 'Image Download Name', diff --git a/src/api/overlays/components/OverlayComponent.vue b/src/api/overlays/components/OverlayComponent.vue index f8a66f7597..9742fd7367 100644 --- a/src/api/overlays/components/OverlayComponent.vue +++ b/src/api/overlays/components/OverlayComponent.vue @@ -7,6 +7,7 @@
diff --git a/src/plugins/displayLayout/components/LayoutFrame.vue b/src/plugins/displayLayout/components/LayoutFrame.vue index 052fa63a3c..c81fb80f71 100644 --- a/src/plugins/displayLayout/components/LayoutFrame.vue +++ b/src/plugins/displayLayout/components/LayoutFrame.vue @@ -25,8 +25,7 @@ class="l-layout__frame c-frame" :class="{ 'no-frame': !item.hasFrame, - 'u-inspectable': inspectable, - 'is-in-small-container': size.width < 600 || size.height < 600 + 'u-inspectable': inspectable }" :style="style" > diff --git a/src/plugins/displayLayout/components/layout-frame.scss b/src/plugins/displayLayout/components/layout-frame.scss index 63f2299ecb..d036814021 100644 --- a/src/plugins/displayLayout/components/layout-frame.scss +++ b/src/plugins/displayLayout/components/layout-frame.scss @@ -9,10 +9,6 @@ > *:first-child { flex: 1 1 auto; } - - &.is-in-small-container { - //background: rgba(blue, 0.1); - } } .c-frame__move-bar { diff --git a/src/plugins/imagery/components/FilterSettings.vue b/src/plugins/imagery/components/FilterSettings.vue new file mode 100644 index 0000000000..c88d215d55 --- /dev/null +++ b/src/plugins/imagery/components/FilterSettings.vue @@ -0,0 +1,74 @@ + + + diff --git a/src/plugins/imagery/components/ImageControls.vue b/src/plugins/imagery/components/ImageControls.vue index a14a402df5..b14c13f8b2 100644 --- a/src/plugins/imagery/components/ImageControls.vue +++ b/src/plugins/imagery/components/ImageControls.vue @@ -21,75 +21,62 @@ *****************************************************************************/ diff --git a/src/plugins/imagery/components/LayerSettings.vue b/src/plugins/imagery/components/LayerSettings.vue new file mode 100644 index 0000000000..1e99a0aee6 --- /dev/null +++ b/src/plugins/imagery/components/LayerSettings.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/plugins/imagery/components/ZoomSettings.vue b/src/plugins/imagery/components/ZoomSettings.vue new file mode 100644 index 0000000000..e53d6289ed --- /dev/null +++ b/src/plugins/imagery/components/ZoomSettings.vue @@ -0,0 +1,89 @@ + + + diff --git a/src/plugins/imagery/components/imagery-view.scss b/src/plugins/imagery/components/imagery-view.scss index 8cdae861db..e781fdce9a 100644 --- a/src/plugins/imagery/components/imagery-view.scss +++ b/src/plugins/imagery/components/imagery-view.scss @@ -28,6 +28,27 @@ display: flex; flex-direction: column; flex: 1 1 auto; + + &.unnsynced{ + @include sUnsynced(); + } + + &.cursor-zoom-in { + cursor: zoom-in; + } + + &.cursor-zoom-out { + cursor: zoom-out; + } + + &.pannable { + @include cursorGrab(); + } + } + + .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); } .image-wrapper { @@ -45,19 +66,6 @@ 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; @@ -77,6 +85,7 @@ background: rgba(black, 0.2); border-radius: $smallCr; padding: 2px $interiorMargin; + pointer-events: none; position: absolute; right: $m; top: $m; @@ -146,6 +155,11 @@ } + &__layer-image { + pointer-events: none; + z-index: 1; + } + &__thumbs-wrapper { display: flex; // Uses row layout justify-content: flex-end; @@ -179,6 +193,50 @@ font-size: 0.8em; margin: $interiorMarginSm; } + + .c-control-menu { + // Controls on left of flex column layout, close btn on right + @include menuOuter(); + + border-radius: $controlCr; + display: flex; + align-items: flex-start; + justify-content: space-between; + padding: $interiorMargin; + width: min-content; + + > * + * { + margin-left: $interiorMargin; + } + } + + .c-switcher-menu { + display: contents; + + &__content { + // Menu panel + top: 28px; + position: absolute; + + .c-so-view & { + top: 25px; + } + } + } +} + +.--width-less-than-220 .--show-if-less-than-220.c-switcher-menu { + display: contents !important; +} + +.s-image-layer { + position: absolute; + height: 100%; + width: 100%; + opacity: 0.5; + background-size: contain; + background-repeat: no-repeat; + background-position: center; } /*************************************** THUMBS */ @@ -229,70 +287,36 @@ /*************************************** IMAGERY LOCAL CONTROLS*/ .c-imagery { .h-local-controls--overlay-content { + display: flex; + flex-direction: row; position: absolute; left: $interiorMargin; top: $interiorMargin; z-index: 70; background: $colorLocalControlOvrBg; border-radius: $basicCr; - max-width: 250px; - min-width: 170px; - width: 35%; align-items: center; - padding: $interiorMargin $interiorMarginLg; - - input[type="range"] { - display: block; - width: 100%; - &:not(:first-child) { - margin-top: $interiorMarginLg; - } - - &:before { - margin-right: $interiorMarginSm; - } - } + padding: $interiorMargin $interiorMargin; .s-status-taking-snapshot & { display: none; } } - - &__lc { - &__reset-btn { - // Span that holds bracket graphics and button - $bc: $scrollbarTrackColorBg; - - &:before, - &:after { - border-right: 1px solid $bc; - content:''; - display: block; - width: 5px; - height: 4px; - } - - &:before { - border-top: 1px solid $bc; - margin-bottom: 2px; - } - - &:after { - border-bottom: 1px solid $bc; - margin-top: 2px; - } - - .c-icon-link { - color: $colorBtnFg; - } + [class*='--menus-aligned'] { + > * + * { + button { margin-left: $interiorMarginSm; } } } } .c-image-controls { + &__controls-wrapper { + // Wraps __controls and __close-btn + display: flex; + } + &__controls { display: flex; align-items: stretch; - flex-direction: column; > * + * { margin-top: $interiorMargin; @@ -314,31 +338,67 @@ } - &__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-left: $interiorMargin; } // Is this used? } - &__sliders { - display: flex; - flex: 1 1 auto; - flex-direction: column; + &--filters { + // Styles specific to the brightness and contrast controls - > * + * { - margin-top: 11px; + .c-image-controls { + &__sliders { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-width: 80px; + + > * + * { + margin-top: 11px; + } + + input[type="range"] { + display: block; + width: 100%; + } + } + + &__slider-wrapper { + display: flex; + align-items: center; + + &:before { margin-right: $interiorMargin; } + } + + &__reset-btn { + // Span that holds bracket graphics and button + $bc: $scrollbarTrackColorBg; + flex: 0 0 auto; + + &:before, + &:after { + border-right: 1px solid $bc; + content:''; + display: block; + width: 5px; + height: 4px; + } + + &:before { + border-top: 1px solid $bc; + margin-bottom: 2px; + } + + &:after { + border-bottom: 1px solid $bc; + margin-top: 2px; + } + + .c-icon-link { + color: $colorBtnFg; + } + } } } - - &__btn-reset { - flex: 0 0 auto; - } } /*************************************** BUTTONS */ @@ -383,7 +443,7 @@ @include cArrowButtonSizing($dimOuter: 48px); border-radius: $controlCr; - .is-in-small-container & { + .--width-less-than-600 & { @include cArrowButtonSizing($dimOuter: 32px); } } @@ -409,10 +469,6 @@ background-color: $colorBodyFg; } - //[class*='__image-placeholder'] { - // display: none; - //} - img { display: block !important; } diff --git a/src/plugins/imagery/layers/example-imagery-layer-16x9.png b/src/plugins/imagery/layers/example-imagery-layer-16x9.png new file mode 100644 index 0000000000000000000000000000000000000000..877a0e6038b220964e2d232ab10c7a37f6eaf055 GIT binary patch literal 8554 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 { location: "parentId", modified: 0, persisted: 0, + configuration: { + layers: [{ + name: '16:9', + visible: true + }] + }, telemetry: { values: [ { "name": "Image", "key": "url", "format": "image", + "layers": [ + { + source: location.host + '/images/bg-splash.jpg', + name: '16:9' + } + ], "hints": { "image": 1, "priority": 3 @@ -366,6 +378,18 @@ describe("The Imagery View Layouts", () => { }); }); + it("on mount should show the any image layers", (done) => { + //Looks like we need Vue.nextTick here so that computed properties settle down + Vue.nextTick().then(() => { + Vue.nextTick(() => { + const layerEls = parent.querySelectorAll('.js-layer-image'); + console.log(layerEls); + expect(layerEls.length).toEqual(1); + done(); + }); + }); + }); + it("should show the clicked thumbnail as the main image", (done) => { //Looks like we need Vue.nextTick here so that computed properties settle down Vue.nextTick(() => { diff --git a/src/plugins/telemetryTable/components/table.scss b/src/plugins/telemetryTable/components/table.scss index 512af8c3a1..03d54c0f72 100644 --- a/src/plugins/telemetryTable/components/table.scss +++ b/src/plugins/telemetryTable/components/table.scss @@ -63,8 +63,9 @@ padding-top: 0; padding-bottom: 0; } - .is-in-small-container & { - display: none; + + .--width-less-than-600 & { + display: none !important; } } } diff --git a/src/styles/_controls.scss b/src/styles/_controls.scss index ac11686977..3e356036d6 100644 --- a/src/styles/_controls.scss +++ b/src/styles/_controls.scss @@ -42,6 +42,17 @@ } } +@mixin menuPositioning() { + display: flex; + flex-direction: column; + position: absolute; + z-index: 100; + + > * { + flex: 0 0 auto; + } +} + @mixin menuInner() { li { @include cControl(); @@ -479,6 +490,10 @@ select { &__row { > * + * { margin-left: $interiorMargin; } } + + li { + white-space: nowrap; + } } /******************************************************** TABS */ @@ -567,6 +582,7 @@ select { /******************************************************** MENUS */ .c-menu { @include menuOuter(); + @include menuPositioning(); @include menuInner(); &__section-hint { @@ -590,6 +606,7 @@ select { .c-super-menu { // Two column layout, menu items on left with detail of hover element on right @include menuOuter(); + @include menuPositioning(); display: flex; padding: $interiorMarginLg; flex-direction: row; @@ -1035,6 +1052,14 @@ input[type="range"] { display: inline-flex; align-items: center; } + + [class*='--menus-aligned'] { + // Contains top level elements that hold dropdown menus + // Top level elements use display: contents to allow their menus to compactly align + // 03-18-22: used in ImageControls.vue + display: flex; + flex-direction: row; + } } .c-local-controls { diff --git a/src/styles/_global.scss b/src/styles/_global.scss index 5b2efb8efd..46ab0ad66b 100644 --- a/src/styles/_global.scss +++ b/src/styles/_global.scss @@ -349,3 +349,22 @@ body.desktop .has-local-controls { pointer-events: none !important; cursor: default !important; } + +/******************************************************** RESPONSIVE CONTAINERS */ +@mixin responsiveContainerWidths($dimension) { + // 3/21/22: `--width-less-than*` classes set in ObjectView.vue + .--show-if-less-than-#{$dimension} { + // Hide anything that displays within a given width by default. + // `display` property must be set within a more specific class + // for the particular item to be displayed. + display: none !important + } + + .--width-less-than-#{$dimension} { + .--hide-if-less-than-#{$dimension} { display: none; } + } +} + +//.--hide-by-default { display: none !important; } +@include responsiveContainerWidths('220'); +@include responsiveContainerWidths('600'); diff --git a/src/styles/_legacy-plots.scss b/src/styles/_legacy-plots.scss index 4ec0529e0f..b9ae1b6d9b 100644 --- a/src/styles/_legacy-plots.scss +++ b/src/styles/_legacy-plots.scss @@ -118,7 +118,7 @@ mct-plot { } } - .is-in-small-container & { + .--width-less-than-600 & { .c-control-bar { display: none; } @@ -498,7 +498,7 @@ mct-plot { margin-bottom: $interiorMarginSm; } - .is-in-small-container & { + .--width-less-than-600 & { &.is-legend-hidden { display: none; } diff --git a/src/styles/_table.scss b/src/styles/_table.scss index 12540ae933..d6c85206d0 100644 --- a/src/styles/_table.scss +++ b/src/styles/_table.scss @@ -90,7 +90,7 @@ div.c-table { flex: 1 1 auto; } - .is-in-small-container & { + .--width-less-than-600 & { &:not(.is-paused) { .c-table-control-bar { display: none; diff --git a/src/ui/components/ObjectFrame.vue b/src/ui/components/ObjectFrame.vue index cef8931873..1254cc3cbb 100644 --- a/src/ui/components/ObjectFrame.vue +++ b/src/ui/components/ObjectFrame.vue @@ -21,9 +21,11 @@ *****************************************************************************/