diff --git a/.webpack/webpack.common.js b/.webpack/webpack.common.js index fc7d337..70fd2c8 100644 --- a/.webpack/webpack.common.js +++ b/.webpack/webpack.common.js @@ -41,6 +41,8 @@ const config = { * Globals **/ "openmct": path.join(__dirname, '..', "node_modules/openmct/dist/openmct.js"), + // this is a core openmct alias required to resolve '@' in core components + "@": path.join(__dirname, '..', "node_modules/openmct/src"), "saveAs": "file-saver/src/FileSaver.js", "EventEmitter": "eventemitter3", "bourbon": "bourbon.scss", @@ -53,6 +55,7 @@ const config = { "services": path.join(__dirname, '..', "src/services"), "lib": path.join(__dirname, '..', "src/lib"), "tables": path.join(__dirname, '..', "src/tables"), + "utils": path.join(__dirname, '..', "src/utils"), /** * Open MCT Folder View Components **/ @@ -72,8 +75,9 @@ const config = { /** * Telemetry Table Components **/ - "openmct.tables.components.Table": path.join(__dirname, '..', "node_modules/openmct/src/plugins/telemetryTable/components/table.vue"), - "openmct.tables.components.TableConfiguration": path.join(__dirname, '..', "node_modules/openmct/src/plugins/telemetryTable/components/table-configuration.vue"), + "openmct.tables.components.Table": path.join(__dirname, '..', "node_modules/openmct/src/plugins/telemetryTable/components/TableComponent.vue"), + "openmct.tables.components.TableConfiguration": path.join(__dirname, '..', "node_modules/openmct/src/plugins/telemetryTable/components/TableConfiguration.vue"), + "vue": "vue/dist/vue.esm-bundler.js" } }, plugins: [ @@ -81,7 +85,9 @@ const config = { __VISTA_VERSION__: `'${packageDefinition.version}'`, __VISTA_BUILD_DATE__: `'${new Date()}'`, __VISTA_REVISION__: `'${gitRevision}'`, - __VISTA_BUILD_BRANCH__: `'${gitBranch}'` + __VISTA_BUILD_BRANCH__: `'${gitBranch}'`, + __VUE_OPTIONS_API__: true, // enable/disable Options API support, default: true + __VUE_PROD_DEVTOOLS__: false // enable/disable devtools support in production, default: false }), new VueLoaderPlugin(), new MiniCssExtractPlugin({ @@ -98,13 +104,23 @@ const config = { { loader: 'css-loader' }, - 'resolve-url-loader', - 'sass-loader' + { + loader: 'resolve-url-loader' + }, + { + loader: 'sass-loader', + options: { sourceMap: true } + } ] }, { test: /\.vue$/, - use: 'vue-loader' + loader: 'vue-loader', + options: { + compilerOptions: { + whitespace: 'preserve' + } + } }, { test: /\.html$/, diff --git a/.webpack/webpack.dev.js b/.webpack/webpack.dev.js index 655e7af..e6b86e6 100644 --- a/.webpack/webpack.dev.js +++ b/.webpack/webpack.dev.js @@ -21,11 +21,6 @@ module.exports = merge(common, { entry: { config: './config.js' }, - resolve: { - alias: { - vue: path.join(__dirname, '..', 'node_modules/vue/dist/vue.js'), - } - }, plugins: [ new CopyWebpackPlugin({ patterns: [ diff --git a/.webpack/webpack.prod.js b/.webpack/webpack.prod.js index adc6b9e..6767fc4 100644 --- a/.webpack/webpack.prod.js +++ b/.webpack/webpack.prod.js @@ -11,11 +11,5 @@ const path = require('path'); /** @type {import('webpack').Configuration} */ module.exports = merge(common, { - mode: 'production', - resolve: { - alias: { - "vue": path.join(__dirname, '..', 'node_modules/vue/dist/vue.min.js'), - } - }, - devtool: 'source-map' + mode: 'production' }); diff --git a/config.js b/config.js index bbfc180..fcb2bd3 100644 --- a/config.js +++ b/config.js @@ -481,6 +481,22 @@ } ], */ + /** + * Table Performance Mode Configuration + * Can increase performance by limiting the maximum rows retained and displayed by tables + * Affects all bounded table types such as Telemetry and EVR tables + * Does not affect latest available tables such as Channel tables + * @typedef TablePerformanceOptions + * @type {object} + * @property {('performance'|'unlimited')} telemetryMode performance mode limits the maximum table rows + * @property {Boolean} persistModeChange whether changes in the UI are persisted with the table + * @property {Number} rowLimit the maximum number of rows in performance mode + */ + tablePerformanceOptions: { + telemetryMode: 'unlimited', + persistModeChange: false, + rowLimit: 50 + }, /** * Developer Settings-- do not modify these unless you know what * they do! diff --git a/index.html b/index.html index 23013c8..2ec3d9d 100644 --- a/index.html +++ b/index.html @@ -20,5 +20,6 @@ +
diff --git a/loader.js b/loader.js index 8994d3b..ee1a43d 100644 --- a/loader.js +++ b/loader.js @@ -70,14 +70,14 @@ define([ } )); openmct.install(ClearDataIndicator.default(config.globalStalenessInterval)); - openmct.install(CommandEventsViewPlugin.default()); - openmct.install(MessagesPlugin.default()); - openmct.install(ProductStatusPlugin.default()); + openmct.install(CommandEventsViewPlugin.default(config.tablePerformanceOptions)); + openmct.install(MessagesPlugin.default(config.tablePerformanceOptions)); + openmct.install(ProductStatusPlugin.default(config.tablePerformanceOptions)); openmct.install(openmct.plugins.UTCTimeSystem()) openmct.install(openmct.plugins.Notebook()); openmct.install(MetadataActionPlugin.default()); - openmct.install(DictionaryViewPlugin.default()); - openmct.install(PacketSummaryPlugin.default()); + openmct.install(DictionaryViewPlugin.default(config.tablePerformanceOptions)); + openmct.install(PacketSummaryPlugin.default(config.tablePerformanceOptions)); openmct.install(ContainerViewPlugin.default()); openmct.install(openmct.plugins.Clock( { useClockIndicator: false } diff --git a/package.json b/package.json index 79886f9..b9930e7 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Open MCT for MCWS", "devDependencies": { "@braintree/sanitize-url": "6.0.2", + "@vue/compiler-sfc": "3.3.8", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", "bower": "^1.7.7", @@ -31,7 +32,7 @@ "mini-css-extract-plugin": "2.6.0", "moment": "2.29.4", "node-bourbon": "^4.2.3", - "openmct": "nasa/openmct#omm-r5.2.0-rc3", + "openmct": "nasa/openmct#omm-release/5.3-next", "printj": "^1.2.1", "raw-loader": "^0.5.1", "resolve-url-loader": "5.0.0", @@ -39,9 +40,8 @@ "sass-loader": "12.6.0", "source-map-loader": "^0.2.4", "style-loader": "^1.0.1", - "vue": "2.6.14", - "vue-loader": "15.9.8", - "vue-template-compiler": "2.6.14", + "vue": "3.3.8", + "vue-loader": "16.8.3", "webpack": "5.76.3", "webpack-cli": "5.0.0", "webpack-dev-server": "4.11.1", diff --git a/src/AMMOSPlugins.js b/src/AMMOSPlugins.js index 6019dc1..0d440e1 100644 --- a/src/AMMOSPlugins.js +++ b/src/AMMOSPlugins.js @@ -97,7 +97,7 @@ define([ mcwsClient.default.configure(options, identityPlugin.login); - openmct.install(MultipleHistoricalSessions.default()); + openmct.install(MultipleHistoricalSessions.default(options.tablePerformanceOptions)); openmct.install(RealtimeSessions.default()); openmct.install(new HistoricalTelemetryPlugin(options)); @@ -108,12 +108,12 @@ define([ openmct.install(new VenuePlugin.default(options)); openmct.install(FrameWatchViewPlugin.default()); openmct.install(FrameEventFilterViewPlugin.default()); - openmct.install(new ChannelTablePlugin.default()); + openmct.install(new ChannelTablePlugin.default(options.tablePerformanceOptions)); openmct.install(new ChannelTableSetPlugin.default()); openmct.install(new ChannelLimitsPlugin.default()); - openmct.install(new FrameAccountabilityPlugin.default(options.frameAccountabilityExpectedVcidList)); - openmct.install(EVRViewPlugin.default(options.taxonomy)); - openmct.install(new AlarmsViewPlugin.default()); + openmct.install(new FrameAccountabilityPlugin.default(options)); + openmct.install(EVRViewPlugin.default(options)); + openmct.install(new AlarmsViewPlugin.default(options.tablePerformanceOptions)); openmct.install(MCWSIndicatorPlugin.default()); if (window.openmctMCWSConfig.messageStreamUrl && window.openmctMCWSConfig.messageStreamUrl !== '') { diff --git a/src/actionModifiers/ImportExportWithDatasets/ImportWithDatasetsFormComponent.vue b/src/actionModifiers/ImportExportWithDatasets/ImportWithDatasetsFormComponent.vue index 2edc693..17fc1b5 100644 --- a/src/actionModifiers/ImportExportWithDatasets/ImportWithDatasetsFormComponent.vue +++ b/src/actionModifiers/ImportExportWithDatasets/ImportWithDatasetsFormComponent.vue @@ -139,7 +139,7 @@ export default { const referencedDatasetKeyString = this.makeKeyString(referencedDataset); const datasetKeyString = this.makeKeyString(this.datasets[0]); - this.$set(this.mapping, referencedDatasetKeyString, datasetKeyString); + this.mapping[referencedDatasetKeyString] = datasetKeyString; }); }, makeKeyString(domainObject) { diff --git a/src/actionModifiers/ImportExportWithDatasets/importWithDatasetsModifier.js b/src/actionModifiers/ImportExportWithDatasets/importWithDatasetsModifier.js index fbd0afc..545f1eb 100644 --- a/src/actionModifiers/ImportExportWithDatasets/importWithDatasetsModifier.js +++ b/src/actionModifiers/ImportExportWithDatasets/importWithDatasetsModifier.js @@ -1,5 +1,5 @@ import ImportWithDatasetsFormComponent from './ImportWithDatasetsFormComponent.vue'; -import Vue from 'vue'; +import mount from 'utils/mountVueComponent'; import DatasetCache from 'services/dataset/DatasetCache'; import Types from 'types/types'; @@ -9,6 +9,7 @@ function importWithDatasetsModifier(openmct) { let datasets; let referencedDatasets; let component; + let _destroy = null; const importAsJSONAction = openmct.actions._allActions['import.JSON']; @@ -75,7 +76,7 @@ function importWithDatasetsModifier(openmct) { onSave(domainObject, changes); }) .catch(error => { - component.$destroy(); + _destroy?.(); }); } @@ -124,8 +125,7 @@ function importWithDatasetsModifier(openmct) { function getImportWithDatasetsFormController(openmct) { return { show(element, model, onChange) { - component = new Vue({ - el: element, + const componentDefinition = { components: { ImportWithDatasetsFormComponent }, @@ -149,12 +149,19 @@ function importWithDatasetsModifier(openmct) { this.hasImport = true } } - }); + }; + const componentOptions = { element }; - return component; + const { + componentInstance, + destroy, + el + } = mount(componentDefinition, componentOptions) + + _destroy = destroy; }, destroy() { - component.$destroy(); + _destroy?.(); resetAction(); } }; @@ -164,6 +171,7 @@ function importWithDatasetsModifier(openmct) { datasets = undefined; referencedDatasets = undefined; component = undefined; + _destroy = undefined; } function getReferencedDatasetsFromImport(json) { diff --git a/src/alarmsView/alarms-view-timeout.vue b/src/alarmsView/AlarmsAutoclear.vue similarity index 98% rename from src/alarmsView/alarms-view-timeout.vue rename to src/alarmsView/AlarmsAutoclear.vue index 2bd1a4f..e46c7aa 100644 --- a/src/alarmsView/alarms-view-timeout.vue +++ b/src/alarmsView/AlarmsAutoclear.vue @@ -49,7 +49,7 @@ export default { this.unlisteners = []; this.openmct.editor.on('isEditing', this.toggleEdit); }, - destroyed() { + beforeUnmount() { this.openmct.editor.off('isEditing', this.toggleEdit); this.unlisteners.forEach((unlisten) => unlisten()); } diff --git a/src/alarmsView/AlarmsAutoclearViewProvider.js b/src/alarmsView/AlarmsAutoclearViewProvider.js new file mode 100644 index 0000000..593b257 --- /dev/null +++ b/src/alarmsView/AlarmsAutoclearViewProvider.js @@ -0,0 +1,61 @@ +import mount from 'utils/mountVueComponent'; +import AlarmsAutoclear from './AlarmsAutoclear.vue'; +import TelemetryTableConfiguration from 'openmct.tables.TelemetryTableConfiguration'; + +export default class AlarmsAutoClearViewProvider { + constructor(openmct, options) { + this.key = 'vista.alarmsView-configuration'; + this.name = 'Autoclear'; + + this.openmct = openmct; + this.options = options; + this._destroy = null; + } + + canView(selection) { + if (selection.length === 0) { + return false; + } + + const domainObject = selection[0][0].context.item; + + return domainObject?.type === 'vista.alarmsView'; + } + + view(selection) { + const domainObject = selection[0][0].context.item; + const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct, this.options); + + return { + show: function (element) { + const componentDefinition = { + provide: { + openmct, + tableConfiguration + }, + components: { + AlarmsAutoclear + }, + template: '' + }; + const componentOptions = { + element + }; + + const { + componentInstance, + destroy, + el + } = mount(componentDefinition, componentOptions); + + this._destroy = destroy; + }, + priority: function () { + return openmct.priority.HIGH; + }, + destroy: function () { + this._destroy?.(); + } + } + } +} diff --git a/src/alarmsView/AlarmsViewProvider.js b/src/alarmsView/AlarmsViewProvider.js index 949dc5c..7730ae6 100644 --- a/src/alarmsView/AlarmsViewProvider.js +++ b/src/alarmsView/AlarmsViewProvider.js @@ -1,10 +1,11 @@ -import Vue from 'vue'; +import mount from 'utils/mountVueComponent'; import TableComponent from 'openmct.tables.components.Table'; import AlarmsTable from './AlarmsTable.js'; export default class AlarmsViewProvider { - constructor(openmct) { + constructor(openmct, options) { this.openmct = openmct; + this.options = options; this.key = 'vista.alarmsView'; this.name = 'Alarms Table'; @@ -16,18 +17,19 @@ export default class AlarmsViewProvider { } view(domainObject, objectPath) { - let table = new AlarmsTable(domainObject, openmct); let component; - let markingProp = { + let _destroy = null; + + const table = new AlarmsTable(domainObject, openmct, this.options); + const markingProp = { enable: true, useAlternateControlBar: false, rowName: '', rowNamePlural: '' }; const view = { - show: function (element, editMode) { - component = new Vue({ - el: element, + show: function (element, editMode, { renderWhenVisible }) { + const componentDefinition = { components: { TableComponent }, @@ -42,7 +44,8 @@ export default class AlarmsViewProvider { openmct, table, objectPath, - currentView: view + currentView: view, + renderWhenVisible }, template: `` - }); + }; + const componentOptions = { + element + }; + + const { + componentInstance, + destroy, + el + } = mount(componentDefinition, componentOptions); + + component = componentInstance; + _destroy = destroy; }, onEditModeChange(editMode) { component.isEditing = editMode; @@ -62,7 +77,7 @@ export default class AlarmsViewProvider { }, getViewContext() { if (component) { - let context = component.$refs.tableComponent.getViewContext(); + const context = component.$refs.tableComponent.getViewContext(); context['vista.alarmsView'] = true; context.clearOutOfAlarmRows = () => { @@ -77,9 +92,8 @@ export default class AlarmsViewProvider { }; } }, - destroy: function (element) { - component.$destroy(); - component = undefined; + destroy: function () { + _destroy?.(); } }; diff --git a/src/alarmsView/plugin.js b/src/alarmsView/plugin.js index 96698b2..13ba9de 100644 --- a/src/alarmsView/plugin.js +++ b/src/alarmsView/plugin.js @@ -1,13 +1,12 @@ import AlarmsViewProvider from './AlarmsViewProvider'; +import AlarmsAutoclearViewProvider from './AlarmsAutoclearViewProvider'; import AlarmsViewActions from './AlarmsViewActions'; -import AlarmsViewTimeoutComponent from './alarms-view-timeout.vue'; import VistaTableConfigurationProvider from '../tables/VistaTableConfigurationProvider'; -import TelemetryTableConfiguration from 'openmct.tables.TelemetryTableConfiguration'; -import Vue from 'vue'; -export default function AlarmsViewPlugin() { +export default function AlarmsViewPlugin(options) { return function install(openmct) { - openmct.objectViews.addProvider(new AlarmsViewProvider(openmct)); + openmct.objectViews.addProvider(new AlarmsViewProvider(openmct, options)); + openmct.inspectorViews.addProvider(new AlarmsAutoclearViewProvider(openmct, options)); AlarmsViewActions.forEach(action => { openmct.actions.register(action); @@ -25,48 +24,12 @@ export default function AlarmsViewPlugin() { }; } }); - openmct.inspectorViews.addProvider({ - key: 'vista.alarmsView-configuration', - name: 'Autoclear', - canView: function (selection) { - if (selection.length === 0) { - return false; - } - let object = selection[0][0].context.item; - return object && object.type === 'vista.alarmsView'; - }, - view: function (selection) { - let component; - let domainObject = selection[0][0].context.item; - const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct); - return { - show: function (element) { - component = new Vue({ - provide: { - openmct, - tableConfiguration - }, - components: { - AlarmsViewTimeout: AlarmsViewTimeoutComponent - }, - template: '', - el: element - }); - }, - priority: function () { - return openmct.priority.HIGH; - }, - destroy: function () { - component.$destroy(); - component = undefined; - } - } - } - }); + openmct.inspectorViews.addProvider(new VistaTableConfigurationProvider( 'vista.alarm-view-configuration', 'Config', - 'vista.alarmsView' + 'vista.alarmsView', + options )); openmct.composition.addPolicy((parent, child) => { diff --git a/src/channelTable/channelTablePlugin/CellFormatConfigurationComponent.js b/src/channelTable/channelTablePlugin/CellFormatConfigurationComponent.js deleted file mode 100644 index 108a4e6..0000000 --- a/src/channelTable/channelTablePlugin/CellFormatConfigurationComponent.js +++ /dev/null @@ -1,39 +0,0 @@ -define(['./cell-format-configuration.html'], function (CellFormatConfigurationTemplate) { - return { - inject: ['tableConfiguration', 'openmct'], - template: CellFormatConfigurationTemplate, - data() { - let configuration = this.tableConfiguration.getConfiguration(); - let selection = this.openmct.selection.get()[0][0]; - - configuration.cellFormat = configuration.cellFormat || {}; - let rowFormat = configuration.cellFormat[selection.context.row] || {}; - - return { - isEditing: this.openmct.editor.isEditing(), - cellFormat: rowFormat[selection.context.column] - } - }, - methods: { - toggleEdit(isEditing) { - this.isEditing = isEditing; - }, - formatCell(event) { - let selection = this.openmct.selection.get()[0][0]; - let configuration = this.tableConfiguration.getConfiguration(); - - configuration.cellFormat = configuration.cellFormat || {}; - configuration.cellFormat[selection.context.row] = configuration.cellFormat[selection.context.row] || {}; - configuration.cellFormat[selection.context.row][selection.context.column] = event.currentTarget.value; - this.tableConfiguration.updateConfiguration(configuration); - } - }, - mounted() { - this.openmct.editor.on('isEditing', this.toggleEdit); - }, - destroyed() { - this.tableConfiguration.destroy(); - this.openmct.editor.off('isEditing', this.toggleEdit); - } - } -}); diff --git a/src/channelTable/channelTablePlugin/CellFormatConfigurationComponent.vue b/src/channelTable/channelTablePlugin/CellFormatConfigurationComponent.vue new file mode 100644 index 0000000..7dfec4c --- /dev/null +++ b/src/channelTable/channelTablePlugin/CellFormatConfigurationComponent.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/channelTable/channelTablePlugin/ChannelTable.js b/src/channelTable/channelTablePlugin/ChannelTable.js index cb2cf86..2355d78 100644 --- a/src/channelTable/channelTablePlugin/ChannelTable.js +++ b/src/channelTable/channelTablePlugin/ChannelTable.js @@ -1,143 +1,133 @@ -define([ - './ChannelTableRowCollection', - './ChannelTableRow', - 'openmct.tables.TelemetryTable', - 'openmct.tables.TelemetryTableColumn', - './EmptyChannelTableRow', - './ObjectNameColumn' -], function ( - ChannelTableRowCollection, - ChannelTableRow, - TelemetryTable, - TelemetryTableColumn, - EmptyChannelTableRow, - ObjectNameColumn -) { - class ChannelTable extends TelemetryTable { - constructor(domainObject, openmct) { - super(domainObject, openmct); - this.updateConfiguration = this.updateConfiguration.bind(this); - this.reorder = this.reorder.bind(this); - this.addDummyRowForObject = this.addDummyRowForObject.bind(this); - - this.configuration.on('change', this.updateConfiguration); - this.objectNames = {}; - } - - initialize() { - this.filterObserver = this.openmct.objects.observe(this.domainObject, 'configuration.filters', this.updateFilters); - this.filters = this.domainObject.configuration && this.domainObject.configuration.filters; - this.loadComposition(); - this.tableComposition.on('reorder', this.reorder); - } - - createTableRowCollections() { - this.tableRows = new ChannelTableRowCollection(this.openmct); - - let sortOptions = this.configuration.getConfiguration().sortOptions; - - if (sortOptions) { - this.tableRows.sortBy(sortOptions); - } - - this.tableRows.on('resetRowsFromAllData', this.resetRowsFromAllData); - } - - addTelemetryObject(telemetryObject) { - super.addTelemetryObject(telemetryObject); - this.addDummyRowForObject(telemetryObject); - } - - addNameColumn(telemetryObject) { - let nameColumn = new ObjectNameColumn(telemetryObject.name); - this.configuration.addSingleColumnForObject(telemetryObject, nameColumn, 0); - } - - addDummyRowForObject(object) { - let objectKeyString = this.openmct.objects.makeKeyString(object.identifier); - let columns = this.getColumnMapForObject(objectKeyString); - let dummyRow = new EmptyChannelTableRow(columns, objectKeyString); - - this.tableRows.addRows([dummyRow]); - } - - updateConfiguration(newConfiguration) { - let cellFormatConfiguration = newConfiguration.cellFormat || {}; - this.tableRows.rows.forEach(row => row.updateRowConfiguration && row.updateRowConfiguration(cellFormatConfiguration[row.objectKeyString])); - this.emit('refresh'); - } - - getTelemetryProcessor(keyString, columnMap, limitEvaluator) { - return (telemetry) => { - //Check that telemetry object has not been removed since telemetry was requested. - if (!this.telemetryObjects[keyString]) { - return; - } - - telemetry.forEach(datum => { - const row = this.createRow(datum, columnMap, keyString, limitEvaluator); - - if (this.paused) { - this.delayedActions.push(this.tableRows.addOrUpdateRow.bind(this, row)); - } else { - this.tableRows.addOrUpdateRow(row); - } - }); - }; - } - - buildOptionsFromConfiguration(telemetryObject) { - const requestOptions = super.buildOptionsFromConfiguration(telemetryObject); - requestOptions.strategy = 'latest'; - requestOptions.size = 1; - - return requestOptions; - } - - requestDataFor(telemetryObject) { - this.incrementOutstandingRequests(); - let requestOptions = this.buildOptionsFromConfiguration(telemetryObject); - requestOptions.strategy = 'latest'; - requestOptions.size = 1; - - return this.openmct.telemetry.request(telemetryObject, requestOptions) - .then(telemetryData => { - let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); - let columnMap = this.getColumnMapForObject(keyString); - let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); - this.processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator); - }).finally(() => this.decrementOutstandingRequests()); - } - - processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) { - let telemetryRows = telemetryData.map(datum => this.createRow(datum, columnMap, keyString, limitEvaluator)); - this.tableRows.addRows(telemetryRows); - } - - processRealtimeDatum(datum, columnMap, keyString, limitEvaluator) { - this.tableRows.addOne(this.createRow(datum, columnMap, keyString, limitEvaluator)); - } - - createColumn(metadatum) { - return new TelemetryTableColumn(this.openmct, metadatum, {selectable: true}); - } - - createRow(datum, columnMap, keyString, limitEvaluator) { - let cellFormatConfiguration = this.configuration.getConfiguration().cellFormat || {}; - return new ChannelTableRow(datum, columnMap, keyString, limitEvaluator, cellFormatConfiguration[keyString]); - } - - reorder(reorderPlan) { - this.tableRows.reorder(reorderPlan); - this.emit('refresh'); - } - - destroy() { - super.destroy(); - this.configuration.off('change', this.updateConfiguration); - this.tableComposition.off('reorder', this.reorder); - } +import ChannelTableRowCollection from './ChannelTableRowCollection'; +import ChannelTableRow from './ChannelTableRow'; +import TelemetryTable from 'openmct.tables.TelemetryTable'; +import TelemetryTableColumn from 'openmct.tables.TelemetryTableColumn'; +import EmptyChannelTableRow from './EmptyChannelTableRow'; +import ObjectNameColumn from './ObjectNameColumn'; + +export default class ChannelTable extends TelemetryTable { + constructor(domainObject, openmct, options) { + super(domainObject, openmct, options); + + this.updateConfiguration = this.updateConfiguration.bind(this); + this.reorder = this.reorder.bind(this); + this.addDummyRowForObject = this.addDummyRowForObject.bind(this); + + this.configuration.on('change', this.updateConfiguration); + this.objectNames = {}; + } + + initialize() { + this.filterObserver = this.openmct.objects.observe(this.domainObject, 'configuration.filters', this.updateFilters); + this.filters = this.domainObject.configuration && this.domainObject.configuration.filters; + this.loadComposition(); + this.tableComposition.on('reorder', this.reorder); + } + + createTableRowCollections() { + this.tableRows = new ChannelTableRowCollection(this.openmct); + + let sortOptions = this.configuration.getConfiguration().sortOptions; + + if (sortOptions) { + this.tableRows.sortBy(sortOptions); } - return ChannelTable; -}); + this.tableRows.on('resetRowsFromAllData', this.resetRowsFromAllData); + } + + addTelemetryObject(telemetryObject) { + super.addTelemetryObject(telemetryObject); + this.addDummyRowForObject(telemetryObject); + } + + addNameColumn(telemetryObject) { + let nameColumn = new ObjectNameColumn(telemetryObject.name); + this.configuration.addSingleColumnForObject(telemetryObject, nameColumn, 0); + } + + addDummyRowForObject(object) { + let objectKeyString = this.openmct.objects.makeKeyString(object.identifier); + let columns = this.getColumnMapForObject(objectKeyString); + let dummyRow = new EmptyChannelTableRow(columns, objectKeyString); + + this.tableRows.addRows([dummyRow]); + } + + updateConfiguration(newConfiguration) { + let cellFormatConfiguration = newConfiguration.cellFormat || {}; + this.tableRows.rows.forEach(row => row.updateRowConfiguration && row.updateRowConfiguration(cellFormatConfiguration[row.objectKeyString])); + this.emit('refresh'); + } + + getTelemetryProcessor(keyString, columnMap, limitEvaluator) { + return (telemetry) => { + //Check that telemetry object has not been removed since telemetry was requested. + if (!this.telemetryObjects[keyString]) { + return; + } + + telemetry.forEach(datum => { + const row = this.createRow(datum, columnMap, keyString, limitEvaluator); + + if (this.paused) { + this.delayedActions.push(this.tableRows.addOrUpdateRow.bind(this, row)); + } else { + this.tableRows.addOrUpdateRow(row); + } + }); + }; + } + + buildOptionsFromConfiguration(telemetryObject) { + const requestOptions = super.buildOptionsFromConfiguration(telemetryObject); + requestOptions.strategy = 'latest'; + requestOptions.size = 1; + + return requestOptions; + } + + requestDataFor(telemetryObject) { + this.incrementOutstandingRequests(); + let requestOptions = this.buildOptionsFromConfiguration(telemetryObject); + requestOptions.strategy = 'latest'; + requestOptions.size = 1; + + return this.openmct.telemetry.request(telemetryObject, requestOptions) + .then(telemetryData => { + let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); + let columnMap = this.getColumnMapForObject(keyString); + let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); + this.processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator); + }).finally(() => this.decrementOutstandingRequests()); + } + + processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) { + let telemetryRows = telemetryData.map(datum => this.createRow(datum, columnMap, keyString, limitEvaluator)); + this.tableRows.addRows(telemetryRows); + } + + processRealtimeDatum(datum, columnMap, keyString, limitEvaluator) { + this.tableRows.addOne(this.createRow(datum, columnMap, keyString, limitEvaluator)); + } + + createColumn(metadatum) { + return new TelemetryTableColumn(this.openmct, metadatum, { selectable: true }); + } + + createRow(datum, columnMap, keyString, limitEvaluator) { + let cellFormatConfiguration = this.configuration.getConfiguration().cellFormat || {}; + return new ChannelTableRow(datum, columnMap, keyString, limitEvaluator, cellFormatConfiguration[keyString]); + } + + reorder(reorderPlan) { + this.tableRows.reorder(reorderPlan); + this.emit('refresh'); + } + + destroy() { + super.destroy(); + this.configuration.off('change', this.updateConfiguration); + this.tableComposition.off('reorder', this.reorder); + } +} diff --git a/src/channelTable/channelTablePlugin/ChannelTableFormatViewProvider.js b/src/channelTable/channelTablePlugin/ChannelTableFormatViewProvider.js index d7e5b7a..5a3fe1c 100644 --- a/src/channelTable/channelTablePlugin/ChannelTableFormatViewProvider.js +++ b/src/channelTable/channelTablePlugin/ChannelTableFormatViewProvider.js @@ -1,56 +1,59 @@ -define([ - './CellFormatConfigurationComponent', - 'openmct.tables.TelemetryTableConfiguration', - 'vue' -], function ( - CellFormatConfigurationComponent, - TelemetryTableConfiguration, - Vue -) { +import CellFormatConfigurationComponent from './CellFormatConfigurationComponent.vue'; +import TelemetryTableConfiguration from 'openmct.tables.TelemetryTableConfiguration'; +import mount from 'utils/mountVueComponent'; - function ChannelTableFormatViewProvider(openmct) { - return { - key: 'channel-list-format', - name: 'Format', - canView: function (selection) { - let selectionPath = selection[0]; - if (selectionPath && selectionPath.length > 1) { - let parentObject = selectionPath[1].context.item; - let selectedContext = selectionPath[0].context; - return parentObject && - parentObject.type === 'vista.chanTableGroup' && - selectedContext.type === 'table-cell'; - } - return false; +export default function ChannelTableFormatViewProvider(openmct, options) { + return { + key: 'channel-list-format', + name: 'Format', + canView: function (selection) { + let selectionPath = selection[0]; + if (selectionPath && selectionPath.length > 1) { + let parentObject = selectionPath[1].context.item; + let selectedContext = selectionPath[0].context; + return parentObject && + parentObject.type === 'vista.chanTableGroup' && + selectedContext.type === 'table-cell'; + } + return false; + }, + view: function (selection) { + let _destroy = null; + let domainObject = selection[0][1].context.item; + const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct, options); + + return { + show: function (element) { + const componentDefinition = { + provide: { + openmct, + tableConfiguration + }, + components: { + CellFormatConfiguration: CellFormatConfigurationComponent }, - view: function (selection) { - let component; - let domainObject = selection[0][1].context.item; - const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct); - return { - show: function (element) { - component = new Vue({ - provide: { - openmct, - tableConfiguration - }, - components: { - CellFormatConfiguration: CellFormatConfigurationComponent - }, - template: '', - el: element - }); - }, - priority: function () { - return openmct.priority.HIGH; - }, - destroy: function () { - component.$destroy(); - component = undefined; - } - } - } + template: '', + }; + const componentOptions = { + element + }; + + const { + componentInstance, + destroy, + el + } = mount(componentDefinition, componentOptions); + + _destroy = destroy; + }, + priority: function () { + return openmct.priority.HIGH; + }, + destroy: function () { + _destroy?.(); } + } } - return ChannelTableFormatViewProvider; -}); + } +} + diff --git a/src/channelTable/channelTablePlugin/ChannelTableRow.js b/src/channelTable/channelTablePlugin/ChannelTableRow.js index 8def479..d762014 100644 --- a/src/channelTable/channelTablePlugin/ChannelTableRow.js +++ b/src/channelTable/channelTablePlugin/ChannelTableRow.js @@ -1,29 +1,26 @@ -define([ - 'lib/HistoricalContextTableRow', - 'printj', -], function (HistoricalContextTableRow, printj) { - class ChannelTableRow extends HistoricalContextTableRow{ - constructor(datum, columns, objectKeyString, limitEvaluator, rowFormatConfiguration) { - super(datum, columns, objectKeyString, limitEvaluator); - this.rowFormats = rowFormatConfiguration || {}; - } +import HistoricalContextTableRow from 'lib/HistoricalContextTableRow'; +import printj from 'printj'; - getFormattedValue(key) { - if (this.rowFormats[key]) { - return this.getCustomFormattedValue(this.datum[key], this.rowFormats[key]); - } else { - let column = this.columns[key]; - return column && column.getFormattedValue(this.datum[key]); - } - } +export default class ChannelTableRow extends HistoricalContextTableRow { + constructor(datum, columns, objectKeyString, limitEvaluator, rowFormatConfiguration) { + super(datum, columns, objectKeyString, limitEvaluator); + this.rowFormats = rowFormatConfiguration || {}; + } - getCustomFormattedValue(value, format) { - return printj.sprintf(format, value); - } - - updateRowConfiguration(rowFormatConfiguration) { - this.rowFormats = rowFormatConfiguration || {}; - } + getFormattedValue(key) { + if (this.rowFormats[key]) { + return this.getCustomFormattedValue(this.datum[key], this.rowFormats[key]); + } else { + let column = this.columns[key]; + return column && column.getFormattedValue(this.datum[key]); } - return ChannelTableRow; -}); + } + + getCustomFormattedValue(value, format) { + return printj.sprintf(format, value); + } + + updateRowConfiguration(rowFormatConfiguration) { + this.rowFormats = rowFormatConfiguration || {}; + } +} diff --git a/src/channelTable/channelTablePlugin/ChannelTableRowCollection.js b/src/channelTable/channelTablePlugin/ChannelTableRowCollection.js index cd28bc3..c1fbc7a 100644 --- a/src/channelTable/channelTablePlugin/ChannelTableRowCollection.js +++ b/src/channelTable/channelTablePlugin/ChannelTableRowCollection.js @@ -1,132 +1,119 @@ -define( - [ - 'lodash', - 'openmct.tables.collections.TableRowCollection', - './EmptyChannelTableRow' - - ], - function ( - _, - TableRowCollection, - EmptyChannelTableRow - ) { - - class ChannelTableRowCollection extends TableRowCollection { - constructor (openmct) { - super(); - - this.openmct = openmct; - this.ladMap = new Map(); - this.timeColumn = openmct.time.timeSystem().key; - this.addOrUpdateRow = this.addOrUpdateRow.bind(this); - } - - addOrUpdateRow(row) { - if (this.isLADRow(row)) { - this.addRows([row]); - } - } - - isLADRow(newRow) { - const isStaleData = this.rows.some(row => - row.objectKeyString === newRow.objectKeyString - && !row.isDummyRow - && row.datum[this.timeColumn] > newRow.datum[this.timeColumn] - ); - - return !isStaleData; - } - - addOne(item) { - if (item.isDummyRow) { - this.ladMap.set(item.objectKeyString, this.rows.length); - this.rows.push(item); - this.emit('add', item); - return true; - } - - if (this.isNewerThanLAD(item)) { - let rowIndex = this.ladMap.get(item.objectKeyString); - this.rows[rowIndex] = item; - this.removeExistingByKeystring(item.objectKeyString); - this.emit('add', [item]); - return true; - } - return false; - } - - addRows(rows) { - let rowsToAdd = this.filterRows(rows); - - if (rowsToAdd.length > 0) { - rowsToAdd.forEach(this.addOne.bind(this)); - this.emit('add', rowsToAdd); - } - } - - removeAllRowsForObject(objectKeyString) { - super.removeAllRowsForObject(objectKeyString); - this.rebuildLadMap(); - } - - removeExistingByKeystring(keyString) { - let removed = []; - - this.rows.forEach((row) => { - if (row.objectKeyString === keyString) { - removed.push(row); - - return false; - } else { - return true; - } - }); - - this.emit('remove', removed); - } - - rebuildLadMap() { - this.ladMap.clear(); - this.rows.forEach((row, index) => { - this.ladMap.set(row.objectKeyString, index); - }); - } - - reorder(reorderPlan) { - let oldRows = this.rows.slice(); - reorderPlan.forEach(reorderEvent => { - let item = oldRows[reorderEvent.oldIndex]; - this.rows[reorderEvent.newIndex] = item; - this.ladMap.set(item.objectKeyString, reorderEvent.newIndex); - }); - } - - sortByTimeSystem(timeSystem) { - this.timeColumn = timeSystem.key; - } - - isNewerThanLAD(item) { - let rowIndex = this.ladMap.get(item.objectKeyString); - let latestRow = this.rows[rowIndex]; - let newerThanLatest = latestRow === undefined || - item.datum[this.timeColumn] > latestRow.datum[this.timeColumn] || - latestRow.isDummyRow; - - return !this.ladMap.has(item.objectKeyString) || newerThanLatest; - } - - getRows() { - return this.rows; - } - - clear() { - this.rows = this.rows.map( - row => new EmptyChannelTableRow(row.columns, row.objectKeyString) - ); - this.rebuildLadMap(); - } - - destroy() {} - } - return ChannelTableRowCollection; -}); +import TableRowCollection from 'openmct.tables.collections.TableRowCollection'; +import EmptyChannelTableRow from './EmptyChannelTableRow.js'; +export default class ChannelTableRowCollection extends TableRowCollection { + constructor(openmct) { + super(); + + this.openmct = openmct; + this.ladMap = new Map(); + this.timeColumn = openmct.time.timeSystem().key; + this.addOrUpdateRow = this.addOrUpdateRow.bind(this); + } + + addOrUpdateRow(row) { + if (this.isLADRow(row)) { + this.addRows([row]); + } + } + + isLADRow(newRow) { + const isStaleData = this.rows.some(row => + row.objectKeyString === newRow.objectKeyString + && !row.isDummyRow + && row.datum[this.timeColumn] > newRow.datum[this.timeColumn] + ); + + return !isStaleData; + } + + addOne(item) { + if (item.isDummyRow) { + this.ladMap.set(item.objectKeyString, this.rows.length); + this.rows.push(item); + this.emit('add', item); + return true; + } + + if (this.isNewerThanLAD(item)) { + let rowIndex = this.ladMap.get(item.objectKeyString); + this.rows[rowIndex] = item; + this.removeExistingByKeystring(item.objectKeyString); + this.emit('add', [item]); + return true; + } + return false; + } + + addRows(rows) { + let rowsToAdd = this.filterRows(rows); + + if (rowsToAdd.length > 0) { + rowsToAdd.forEach(this.addOne.bind(this)); + this.emit('add', rowsToAdd); + } + } + + removeAllRowsForObject(objectKeyString) { + super.removeAllRowsForObject(objectKeyString); + this.rebuildLadMap(); + } + + removeExistingByKeystring(keyString) { + let removed = []; + + this.rows.forEach((row) => { + if (row.objectKeyString === keyString) { + removed.push(row); + + return false; + } else { + return true; + } + }); + + this.emit('remove', removed); + } + + rebuildLadMap() { + this.ladMap.clear(); + this.rows.forEach((row, index) => { + this.ladMap.set(row.objectKeyString, index); + }); + } + + reorder(reorderPlan) { + let oldRows = this.rows.slice(); + reorderPlan.forEach(reorderEvent => { + let item = oldRows[reorderEvent.oldIndex]; + this.rows[reorderEvent.newIndex] = item; + this.ladMap.set(item.objectKeyString, reorderEvent.newIndex); + }); + } + + sortByTimeSystem(timeSystem) { + this.timeColumn = timeSystem.key; + } + + isNewerThanLAD(item) { + let rowIndex = this.ladMap.get(item.objectKeyString); + let latestRow = this.rows[rowIndex]; + let newerThanLatest = latestRow === undefined || + item.datum[this.timeColumn] > latestRow.datum[this.timeColumn] || + latestRow.isDummyRow; + + return !this.ladMap.has(item.objectKeyString) || newerThanLatest; + } + + getRows() { + return this.rows; + } + + clear() { + this.rows = this.rows.map( + row => new EmptyChannelTableRow(row.columns, row.objectKeyString) + ); + this.rebuildLadMap(); + } + + destroy() { } +} diff --git a/src/channelTable/channelTablePlugin/ChannelTableViewProvider.js b/src/channelTable/channelTablePlugin/ChannelTableViewProvider.js index a764d52..9979aee 100644 --- a/src/channelTable/channelTablePlugin/ChannelTableViewProvider.js +++ b/src/channelTable/channelTablePlugin/ChannelTableViewProvider.js @@ -1,10 +1,12 @@ import ChannelTable from './ChannelTable'; import TableComponent from 'openmct.tables.components.Table'; -import Vue from 'vue'; +import mount from 'utils/mountVueComponent'; export default class ChannelTableViewProvider { - constructor(openmct) { + constructor(openmct, options) { this.openmct = openmct; + this.options = options; + this.key = 'vista.channel-list'; this.name = 'Channel List'; this.cssClass = 'icon-tabular-realtime'; @@ -22,17 +24,19 @@ export default class ChannelTableViewProvider { view(domainObject, objectPath) { let component; - let markingProp = { + let _destroy = null; + + const markingProp = { enable: true, useAlternateControlBar: false, rowName: '', rowNamePlural: '' }; + const table = new ChannelTable(domainObject, this.openmct, this.options); - const table = new ChannelTable(domainObject, this.openmct); const view = { - show(element, isEditing) { - component = new Vue({ + show(element, isEditing, { renderWhenVisible }) { + const componentDefinition = { data() { return { isEditing, @@ -47,12 +51,12 @@ export default class ChannelTableViewProvider { openmct, table, objectPath, - currentView: view + currentView: view, + renderWhenVisible }, - el: element, template: ` ` - }); + }; + const componentOptions = { + element + }; + + const { + componentInstance, + destroy, + el + } = mount(componentDefinition, componentOptions); + + component = componentInstance; + _destroy = destroy; }, onEditModeChange(isEditing) { component.isEditing = isEditing; @@ -78,8 +94,7 @@ export default class ChannelTableViewProvider { } }, destroy() { - component.$destroy(); - component = undefined; + _destroy?.(); } } diff --git a/src/channelTable/channelTablePlugin/EmptyChannelTableRow.js b/src/channelTable/channelTablePlugin/EmptyChannelTableRow.js index d49a4a9..25c5beb 100644 --- a/src/channelTable/channelTablePlugin/EmptyChannelTableRow.js +++ b/src/channelTable/channelTablePlugin/EmptyChannelTableRow.js @@ -1,40 +1,39 @@ -define(['lib/HistoricalContextTableRow'], function (HistoricalContextTableRow) { - class EmptyLADRow extends HistoricalContextTableRow { - constructor(columns, objectKeyString) { - super({}, columns, objectKeyString); - this.isDummyRow = true; - this.columns = columns; - this.objectKeyString = objectKeyString; - this.datum = Object.keys(columns).reduce((datum, column) => { - datum[column] = undefined; - return datum; - }, {}); - } +import HistoricalContextTableRow from 'lib/HistoricalContextTableRow'; - getFormattedDatum(headers) { - return Object.keys(headers).reduce((formattedDatum, columnKey) => { - formattedDatum[columnKey] = this.getFormattedValue(columnKey); - return formattedDatum; - }, {}); - } +export default class EmptyLADRow extends HistoricalContextTableRow { + constructor(columns, objectKeyString) { + super({}, columns, objectKeyString); + this.isDummyRow = true; + this.columns = columns; + this.objectKeyString = objectKeyString; + this.datum = Object.keys(columns).reduce((datum, column) => { + datum[column] = undefined; + return datum; + }, {}); + } - getFormattedValue(key) { - if (key === 'vista-lad-name') { - let column = this.columns[key]; - return column && column.getFormattedValue(); - } else if (this.columns[key] === undefined) { - return ''; - } else { - return this.datum[key] || '--' - } - } + getFormattedDatum(headers) { + return Object.keys(headers).reduce((formattedDatum, columnKey) => { + formattedDatum[columnKey] = this.getFormattedValue(columnKey); + return formattedDatum; + }, {}); + } - getRowClass() { - } - - getCellLimitClasses() { - return {}; - } + getFormattedValue(key) { + if (key === 'vista-lad-name') { + let column = this.columns[key]; + return column && column.getFormattedValue(); + } else if (this.columns[key] === undefined) { + return ''; + } else { + return this.datum[key] || '--' } - return EmptyLADRow; -}); \ No newline at end of file + } + + getRowClass() { + } + + getCellLimitClasses() { + return {}; + } +} diff --git a/src/channelTable/channelTablePlugin/cell-format-configuration.html b/src/channelTable/channelTablePlugin/cell-format-configuration.html deleted file mode 100644 index e403ed3..0000000 --- a/src/channelTable/channelTablePlugin/cell-format-configuration.html +++ /dev/null @@ -1,9 +0,0 @@ -
-
Cell Format
-
    -
  • -
    -
    -
  • -
-
\ No newline at end of file diff --git a/src/channelTable/channelTablePlugin/plugin.js b/src/channelTable/channelTablePlugin/plugin.js index d7f4c38..14da41a 100644 --- a/src/channelTable/channelTablePlugin/plugin.js +++ b/src/channelTable/channelTablePlugin/plugin.js @@ -3,7 +3,7 @@ import ChannelTableViewProvider from './ChannelTableViewProvider'; import ChannelTableFormatViewProvider from './ChannelTableFormatViewProvider'; import VistaTableConfigurationProvider from '../../tables/VistaTableConfigurationProvider'; -export default function install() { +export default function install(options) { return function ChannelTablePlugin(openmct) { openmct.types.addType(CHANNEL_TABLE_KEY, { name: CHANNEL_TABLE_NAME, @@ -19,16 +19,17 @@ export default function install() { } }); - openmct.objectViews.addProvider(new ChannelTableViewProvider(openmct)); + openmct.objectViews.addProvider(new ChannelTableViewProvider(openmct, options)); openmct.inspectorViews.addProvider( new VistaTableConfigurationProvider( 'vista.channel-list-configuration', 'Config', - CHANNEL_TABLE_KEY + CHANNEL_TABLE_KEY, + options ) ); - openmct.inspectorViews.addProvider(new ChannelTableFormatViewProvider(openmct)); + openmct.inspectorViews.addProvider(new ChannelTableFormatViewProvider(openmct, options)); openmct.composition.addPolicy((parent, child) => { if (parent.type === CHANNEL_TABLE_KEY) { diff --git a/src/channelTable/channelTableSetPlugin/ChannelRow.vue b/src/channelTable/channelTableSetPlugin/ChannelRow.vue index 545604a..0fefb72 100644 --- a/src/channelTable/channelTableSetPlugin/ChannelRow.vue +++ b/src/channelTable/channelTableSetPlugin/ChannelRow.vue @@ -22,6 +22,7 @@ const BLANK_VALUE = '---'; export default { inject: ['openmct', 'currentView'], + emits: ['rowContextClick'], props: { domainObject: { type: Object, @@ -105,7 +106,7 @@ this.telemetryCollection.on('clear', this.resetValues); this.telemetryCollection.load(); }, - destroyed() { + beforeUnmount() { this.openmct.time.off('timeSystem', this.updateTimeSystem); this.telemetryCollection.off('add', this.setLatestValues); this.telemetryCollection.off('clear', this.resetValues); diff --git a/src/channelTable/channelTableSetPlugin/ChannelTableSet.vue b/src/channelTable/channelTableSetPlugin/ChannelTableSet.vue index 456922b..3d38133 100644 --- a/src/channelTable/channelTableSetPlugin/ChannelTableSet.vue +++ b/src/channelTable/channelTableSetPlugin/ChannelTableSet.vue @@ -11,9 +11,9 @@