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 073e2c6ec5..94ab53538b 100644 Binary files a/src/styles/fonts/Open-MCT-Symbols-16px.ttf and b/src/styles/fonts/Open-MCT-Symbols-16px.ttf differ diff --git a/src/styles/fonts/Open-MCT-Symbols-16px.woff b/src/styles/fonts/Open-MCT-Symbols-16px.woff index ca1cc26bed..510d6e9a9b 100644 Binary files a/src/styles/fonts/Open-MCT-Symbols-16px.woff and b/src/styles/fonts/Open-MCT-Symbols-16px.woff differ diff --git a/src/styles/vue-styles.scss b/src/styles/vue-styles.scss index 8cfe9c6997..f0ff173df4 100644 --- a/src/styles/vue-styles.scss +++ b/src/styles/vue-styles.scss @@ -54,6 +54,7 @@ @import "./notebook.scss"; @import "../plugins/notebook/components/sidebar.scss"; @import "../plugins/gauge/gauge.scss"; +@import "../plugins/operatorStatus/operator-status"; #splash-screen { display: none; diff --git a/src/ui/layout/status-bar/Indicators.vue b/src/ui/layout/status-bar/Indicators.vue index 7c9a975fc8..fd7c3f1222 100644 --- a/src/ui/layout/status-bar/Indicators.vue +++ b/src/ui/layout/status-bar/Indicators.vue @@ -24,10 +24,19 @@ export default { inject: ['openmct'], + beforeDestroy() { + this.openmct.indicators.off('addIndicator', this.addIndicator); + }, mounted() { - this.openmct.indicators.getIndicatorObjectsByPriority().forEach((indicator) => { + 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); + }); + }); +});