Merge branch 'master' of https://github.com/nasa/openmct into activity-states-and-compact-view

This commit is contained in:
Shefali 2024-01-18 09:23:16 -08:00
commit 50de0f6f27
19 changed files with 249 additions and 197 deletions

View File

@ -6,15 +6,15 @@ This is the OpenMCT common webpack file. It is imported by the other three webpa
There are separate npm scripts to use these configurations, though simply running `npm install`
will use the default production configuration.
*/
import path from 'node:path';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import webpack from 'webpack';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import fs from 'node:fs';
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import { VueLoaderPlugin } from 'vue-loader';
import webpack from 'webpack';
let gitRevision = 'error-retrieving-revision';
let gitBranch = 'error-retrieving-branch';
@ -22,9 +22,7 @@ const packageDefinition = JSON.parse(fs.readFileSync(new URL('../package.json',
try {
gitRevision = execSync('git rev-parse HEAD').toString().trim();
gitBranch = execSync('git rev-parse --abbrev-ref HEAD')
.toString()
.trim();
gitBranch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
} catch (err) {
console.warn(err);
}
@ -67,7 +65,6 @@ const config = {
alias: {
'@': path.join(projectRootDir, 'src'),
legacyRegistry: path.join(projectRootDir, 'src/legacyRegistry'),
saveAs: 'file-saver/src/FileSaver.js',
csv: 'comma-separated-values',
EventEmitter: 'eventemitter3',
bourbon: 'bourbon.scss',

View File

@ -43,7 +43,7 @@ test.describe('Clear Data Action', () => {
await expect(page.locator(backgroundImageSelector)).toBeVisible();
});
test('works as expected with Example Imagery', async ({ page }) => {
await expect(await page.locator('.c-thumb__image').count()).toBeGreaterThan(0);
expect(await page.locator('.c-thumb__image').count()).toBeGreaterThan(0);
// Click the "Clear Data" menu action
await page.getByTitle('More actions').click();
await expect(
@ -59,6 +59,6 @@ test.describe('Clear Data Action', () => {
// Verify that the background image is no longer visible
await expect(page.locator(backgroundImageSelector)).toBeHidden();
await expect(await page.locator('.c-thumb__image').count()).toBe(0);
expect(await page.locator('.c-thumb__image').count()).toBe(0);
});
});

View File

@ -95,18 +95,15 @@ async function makeOverlayPlot(page, myItemsFolderName) {
await page.locator('button.c-create-button').click();
await page.locator('li[role="menuitem"]:has-text("Overlay Plot")').click();
// Click OK button and wait for Navigate event
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear
page.waitForLoadState(),
await page.getByRole('button', { name: 'Save' }).click(),
// Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
//Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
// save the overlay plot
await saveOverlayPlot(page);
// create a sinewave generator
@ -114,34 +111,24 @@ async function makeOverlayPlot(page, myItemsFolderName) {
await page.locator('button.c-create-button').click();
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
// set amplitude to 6, offset 4, period 2
// set amplitude to 6, offset 4, data rate 2 hz
await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').click();
await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').fill('6');
await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').click();
await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').fill('4');
await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').click();
await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').fill('2');
// Click OK to make generator
await page.getByLabel('Amplitude').fill('6');
await page.getByLabel('Offset').fill('4');
await page.getByLabel('Data Rate (hz)').fill('2');
// Click OK button and wait for Navigate event
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear
page.waitForLoadState(),
await page.getByRole('button', { name: 'Save' }).click(),
// Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
//Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
// click on overlay plot
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([
page.waitForNavigation(),
page.waitForLoadState(),
page.locator('text=Unnamed Overlay Plot').first().click()
]);
}

View File

@ -21,24 +21,24 @@
*****************************************************************************/
const DEFAULT_IMAGE_SAMPLES = [
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg',
'https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg'
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18732.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18733.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18734.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18735.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18736.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18737.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18738.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18739.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18740.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18741.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18742.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18743.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18744.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18745.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18746.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18747.jpg',
'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18748.jpg'
];
const DEFAULT_IMAGE_LOAD_DELAY_IN_MILLISECONDS = 20000;
const MIN_IMAGE_LOAD_DELAY_IN_MILLISECONDS = 5000;

View File

@ -78,9 +78,7 @@ class MenuAPI {
if (isActionGroup) {
action = this.actionsToMenuItems(action, objectPath, view);
} else {
action.onItemClicked = () => {
action.invoke(objectPath, view);
};
action.onItemClicked = () => action.invoke(objectPath, view);
}
return action;

View File

@ -32,7 +32,7 @@ class Overlay extends EventEmitter {
const { destroy } = mount(
{
components: {
OverlayComponent: OverlayComponent
OverlayComponent
},
provide: {
dismiss: this.notifyAndDismiss.bind(this),
@ -60,7 +60,6 @@ class Overlay extends EventEmitter {
dismiss() {
this.emit('destroy');
document.body.removeChild(this.container);
this.destroy();
}

View File

@ -82,14 +82,17 @@ class OverlayAPI {
}
/**
* A description of option properties that can be passed into the overlay
* @typedef options
* @property {object} element DOMElement that is to be inserted/shown on the overlay
* @property {string} size preferred size of the overlay (large, small, fit)
* @property {array} buttons optional button objects with label and callback properties
* @property {function} onDestroy callback to be called when overlay is destroyed
* @property {boolean} dismissable allow user to dismiss overlay by using esc, and clicking away
* from overlay. Unless set to false, all overlays will be dismissable by default.
* Creates and displays an overlay with the specified options.
*
* @typedef {Object} OverlayOptions
* @property {HTMLElement} element The DOM Element to be inserted or shown in the overlay.
* @property {'large'|'small'|'fit'} size The preferred size of the overlay.
* @property {Array<{label: string, callback: Function}>} [buttons] Optional array of button objects, each with 'label' and 'callback' properties.
* @property {Function} onDestroy Callback to be called when the overlay is destroyed.
* @property {boolean} [dismissable=true] Whether the overlay can be dismissed by pressing 'esc' or clicking outside of it. Defaults to true.
*
* @param {OverlayOptions} options - The configuration options for the overlay.
* @returns {Overlay} An instance of the Overlay class.
*/
overlay(options) {
let overlay = new Overlay(options);

View File

@ -17,7 +17,7 @@ class ProgressDialog extends Overlay {
}) {
const { vNode, destroy } = mount({
components: {
ProgressDialogComponent: ProgressDialogComponent
ProgressDialogComponent
},
provide: {
iconClass,
@ -28,16 +28,15 @@ class ProgressDialog extends Overlay {
},
data() {
return {
model: {
progressPerc: progressPerc || 0,
progressText
}
progressPerc,
progressText
};
},
template: '<progress-dialog-component :model="model"></progress-dialog-component>'
template:
'<progress-dialog-component :progress-perc="progressPerc" :progress-text="progressText"></progress-dialog-component>'
});
component = vNode.componentInstance;
component = vNode.componentInstance;
super({
element: vNode.el,
size: 'fit',
@ -51,8 +50,8 @@ class ProgressDialog extends Overlay {
}
updateProgress(progressPerc, progressText) {
component.model.progressPerc = progressPerc;
component.model.progressText = progressText;
component.$data.progressPerc = progressPerc;
component.$data.progressText = progressText;
}
}

View File

@ -20,9 +20,9 @@
at runtime from the About dialog for additional information.
-->
<template>
<dialog-component>
<progress-component :model="model" />
</dialog-component>
<DialogComponent>
<ProgressComponent :progress-perc="progressPerc" :progress-text="progressText" />
</DialogComponent>
</template>
<script>
@ -31,14 +31,18 @@ import DialogComponent from './DialogComponent.vue';
export default {
components: {
DialogComponent: DialogComponent,
ProgressComponent: ProgressComponent
DialogComponent,
ProgressComponent
},
inject: ['iconClass', 'title', 'hint', 'timestamp', 'message'],
props: {
model: {
type: Object,
required: true
progressPerc: {
type: Number,
default: 0
},
progressText: {
type: String,
default: ''
}
}
};

View File

@ -21,7 +21,7 @@
*****************************************************************************/
import CSV from 'comma-separated-values';
import { saveAs } from 'saveAs';
import { saveAs } from 'file-saver';
class CSVExporter {
export(rows, options) {

View File

@ -31,8 +31,8 @@ function replaceDotsWithUnderscores(filename) {
return filename.replace(regex, '_');
}
import { saveAs } from 'file-saver';
import html2canvas from 'html2canvas';
import { saveAs } from 'saveAs';
import { v4 as uuid } from 'uuid';
class ImageExporter {

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { saveAs } from 'saveAs';
import { saveAs } from 'file-saver';
class JSONExporter {
export(obj, options) {

View File

@ -24,8 +24,19 @@ import { v4 as uuid } from 'uuid';
import JSONExporter from '/src/exporters/JSONExporter.js';
export default class ExportAsJSONAction {
#openmct;
/**
* @param {import('../../../openmct').OpenMCT} openmct The Open MCT API
*/
constructor(openmct) {
this.openmct = openmct;
this.#openmct = openmct;
// Bind public methods
this.invoke = this.invoke.bind(this);
this.appliesTo = this.appliesTo.bind(this);
// FIXME: This should be private but is used in tests
this.saveAs = this.saveAs.bind(this);
this.name = 'Export as JSON';
this.key = 'export.JSON';
@ -37,6 +48,10 @@ export default class ExportAsJSONAction {
this.tree = null;
this.calls = null;
this.idMap = null;
this.dialog = null;
this.progressPerc = 0;
this.exportedCount = 0;
this.totalToExport = 0;
this.JSONExportService = new JSONExporter();
}
@ -50,87 +65,127 @@ export default class ExportAsJSONAction {
appliesTo(objectPath) {
let domainObject = objectPath[0];
return this._isCreatableAndPersistable(domainObject);
return this.#isCreatableAndPersistable(domainObject);
}
/**
*
* @param {object} objectpath
* @param {import('../../api/objects/ObjectAPI').DomainObject[]} objectPath
*/
invoke(objectpath) {
invoke(objectPath) {
this.tree = {};
this.calls = 0;
this.idMap = {};
const root = objectpath[0];
this.root = this._copy(root);
const root = objectPath[0];
this.root = this.#copy(root);
const rootId = this._getKeystring(this.root);
const rootId = this.#getKeystring(this.root);
this.tree[rootId] = this.root;
this._write(this.root);
this.dialog = this.#openmct.overlays.progressDialog({
message:
'Do not navigate away from this page or close this browser tab while this message is displayed.',
iconClass: 'info',
title: 'Exporting'
});
this.dialog.show();
this.#write(this.root)
.then(() => {
this.exportedCount++;
this.#updateProgress();
})
.catch((error) => {
this.dialog.dismiss();
this.dialog = null;
this.#resetCounts();
this.#openmct.notifications.error({
title: 'Export as JSON failed',
message: error.message
});
});
}
/**
* @private
* @param {object} parent
* @param {import('../../api/objects/ObjectAPI').DomainObject} parent
*/
async _write(parent) {
async #write(parent) {
this.totalToExport++;
this.calls++;
//conditional object styles are not saved on the composition, so we need to check for them
const conditionSetIdentifier = this._getConditionSetIdentifier(parent);
const hasItemConditionSetIdentifiers = this._hasItemConditionSetIdentifiers(parent);
const composition = this.openmct.composition.get(parent);
const conditionSetIdentifier = this.#getConditionSetIdentifier(parent);
const hasItemConditionSetIdentifiers = this.#hasItemConditionSetIdentifiers(parent);
const composition = this.#openmct.composition.get(parent);
if (composition) {
const children = await composition.load();
const exportPromises = children.map((child) => this.#exportObject(child, parent));
children.forEach((child) => {
this._exportObject(child, parent);
});
await Promise.all(exportPromises);
}
if (!conditionSetIdentifier && !hasItemConditionSetIdentifiers) {
this._decrementCallsAndSave();
this.#decrementCallsAndSave();
} else {
const conditionSetObjects = [];
let conditionSetObjects = [];
// conditionSetIdentifiers directly in objectStyles object
if (conditionSetIdentifier) {
conditionSetObjects.push(await this.openmct.objects.get(conditionSetIdentifier));
const conditionSetObject = this.#openmct.objects.get(conditionSetIdentifier);
conditionSetObjects.push(conditionSetObject);
}
// conditionSetIdentifiers stored on item ids in the objectStyles object
if (hasItemConditionSetIdentifiers) {
const itemConditionSetIdentifiers = this._getItemConditionSetIdentifiers(parent);
const itemConditionSetIdentifiers = this.#getItemConditionSetIdentifiers(parent);
const itemConditionSetObjects = itemConditionSetIdentifiers.map((id) =>
this.#openmct.objects.get(id)
);
conditionSetObjects = conditionSetObjects.concat(itemConditionSetObjects);
for (const itemConditionSetIdentifier of itemConditionSetIdentifiers) {
conditionSetObjects.push(await this.openmct.objects.get(itemConditionSetIdentifier));
conditionSetObjects.push(this.#openmct.objects.get(itemConditionSetIdentifier));
}
}
for (const conditionSetObject of conditionSetObjects) {
this._exportObject(conditionSetObject, parent);
if (conditionSetObjects.length > 0) {
const resolvedConditionSetObjects = await Promise.all(conditionSetObjects);
const exportConditionSetPromises = resolvedConditionSetObjects.map((obj) =>
this.#exportObject(obj, parent)
);
await Promise.all(exportConditionSetPromises);
}
this._decrementCallsAndSave();
this.#decrementCallsAndSave();
}
}
_exportObject(child, parent) {
const originalKeyString = this._getKeystring(child);
const createable = this._isCreatableAndPersistable(child);
#updateProgress() {
this.progressPerc = Math.ceil((100 * this.exportedCount) / this.totalToExport);
this.dialog?.updateProgress(
this.progressPerc,
`Exporting ${this.exportedCount} / ${this.totalToExport} objects.`
);
}
#exportObject(child, parent) {
const originalKeyString = this.#getKeystring(child);
const createable = this.#isCreatableAndPersistable(child);
const isNotInfinite = !Object.prototype.hasOwnProperty.call(this.tree, originalKeyString);
if (createable && isNotInfinite) {
// for external or linked objects we generate new keys, if they don't exist already
if (this._isLinkedObject(child, parent)) {
child = this._rewriteLink(child, parent);
if (this.#isLinkedObject(child, parent)) {
child = this.#rewriteLink(child, parent);
} else {
this.tree[originalKeyString] = child;
}
this._write(child);
this.#write(child).then(() => {
this.exportedCount++;
this.#updateProgress();
});
}
}
@ -140,23 +195,23 @@ export default class ExportAsJSONAction {
* @param {object} parent
* @returns {object}
*/
_rewriteLink(child, parent) {
const originalKeyString = this._getKeystring(child);
const parentKeyString = this._getKeystring(parent);
const conditionSetIdentifier = this._getConditionSetIdentifier(parent);
const hasItemConditionSetIdentifiers = this._hasItemConditionSetIdentifiers(parent);
#rewriteLink(child, parent) {
const originalKeyString = this.#getKeystring(child);
const parentKeyString = this.#getKeystring(parent);
const conditionSetIdentifier = this.#getConditionSetIdentifier(parent);
const hasItemConditionSetIdentifiers = this.#hasItemConditionSetIdentifiers(parent);
const existingMappedKeyString = this.idMap[originalKeyString];
let copy;
if (!existingMappedKeyString) {
copy = this._copy(child);
copy = this.#copy(child);
copy.identifier.key = uuid();
if (!conditionSetIdentifier && !hasItemConditionSetIdentifiers) {
copy.location = parentKeyString;
}
let newKeyString = this._getKeystring(copy);
let newKeyString = this.#getKeystring(copy);
this.idMap[originalKeyString] = newKeyString;
this.tree[newKeyString] = copy;
} else {
@ -166,7 +221,7 @@ export default class ExportAsJSONAction {
if (conditionSetIdentifier || hasItemConditionSetIdentifiers) {
// update objectStyle object
if (conditionSetIdentifier) {
const directObjectStylesIdentifier = this.openmct.objects.areIdsEqual(
const directObjectStylesIdentifier = this.#openmct.objects.areIdsEqual(
parent.configuration.objectStyles.conditionSetIdentifier,
child.identifier
);
@ -187,7 +242,7 @@ export default class ExportAsJSONAction {
if (
itemConditionSetIdentifier &&
this.openmct.objects.areIdsEqual(itemConditionSetIdentifier, child.identifier)
this.#openmct.objects.areIdsEqual(itemConditionSetIdentifier, child.identifier)
) {
parent.configuration.objectStyles[itemId].conditionSetIdentifier = copy.identifier;
this.tree[parentKeyString].configuration.objectStyles[itemId].conditionSetIdentifier =
@ -199,7 +254,7 @@ export default class ExportAsJSONAction {
} else {
// just update parent
const index = parent.composition.findIndex((identifier) => {
return this.openmct.objects.areIdsEqual(child.identifier, identifier);
return this.#openmct.objects.areIdsEqual(child.identifier, identifier);
});
parent.composition[index] = copy.identifier;
@ -214,8 +269,8 @@ export default class ExportAsJSONAction {
* @param {object} domainObject
* @returns {string} A string representation of the given identifier, including namespace and key
*/
_getKeystring(domainObject) {
return this.openmct.objects.makeKeyString(domainObject.identifier);
#getKeystring(domainObject) {
return this.#openmct.objects.makeKeyString(domainObject.identifier);
}
/**
@ -223,9 +278,9 @@ export default class ExportAsJSONAction {
* @param {object} domainObject
* @returns {boolean}
*/
_isCreatableAndPersistable(domainObject) {
const type = this.openmct.types.get(domainObject.type);
const isPersistable = this.openmct.objects.isPersistable(domainObject.identifier);
#isCreatableAndPersistable(domainObject) {
const type = this.#openmct.types.get(domainObject.type);
const isPersistable = this.#openmct.objects.isPersistable(domainObject.identifier);
return type && type.definition.creatable && isPersistable;
}
@ -236,10 +291,10 @@ export default class ExportAsJSONAction {
* @param {object} parent
* @returns {boolean}
*/
_isLinkedObject(child, parent) {
const rootKeyString = this._getKeystring(this.root);
const childKeyString = this._getKeystring(child);
const parentKeyString = this._getKeystring(parent);
#isLinkedObject(child, parent) {
const rootKeyString = this.#getKeystring(this.root);
const childKeyString = this.#getKeystring(child);
const parentKeyString = this.#getKeystring(parent);
return (
(child.location !== parentKeyString &&
@ -249,11 +304,11 @@ export default class ExportAsJSONAction {
);
}
_getConditionSetIdentifier(object) {
#getConditionSetIdentifier(object) {
return object.configuration?.objectStyles?.conditionSetIdentifier;
}
_hasItemConditionSetIdentifiers(parent) {
#hasItemConditionSetIdentifiers(parent) {
const objectStyles = parent.configuration?.objectStyles;
for (const itemId in objectStyles) {
@ -265,7 +320,7 @@ export default class ExportAsJSONAction {
return false;
}
_getItemConditionSetIdentifiers(parent) {
#getItemConditionSetIdentifiers(parent) {
const objectStyles = parent.configuration?.objectStyles;
let identifiers = new Set();
@ -280,10 +335,15 @@ export default class ExportAsJSONAction {
return Array.from(identifiers);
}
#resetCounts() {
this.totalToExport = 0;
this.exportedCount = 0;
}
/**
* @private
*/
_rewriteReferences() {
#rewriteReferences() {
const oldKeyStrings = Object.keys(this.idMap);
let treeString = JSON.stringify(this.tree);
@ -291,8 +351,8 @@ export default class ExportAsJSONAction {
// this will cover keyStrings, identifiers and identifiers created
// by hand that may be structured differently from those created with 'makeKeyString'
const newKeyString = this.idMap[oldKeyString];
const newIdentifier = JSON.stringify(this.openmct.objects.parseKeyString(newKeyString));
const oldIdentifier = this.openmct.objects.parseKeyString(oldKeyString);
const newIdentifier = JSON.stringify(this.#openmct.objects.parseKeyString(newKeyString));
const oldIdentifier = this.#openmct.objects.parseKeyString(oldKeyString);
const oldIdentifierNamespaceFirst = JSON.stringify(oldIdentifier);
const oldIdentifierKeyFirst = JSON.stringify({
key: oldIdentifier.key,
@ -318,29 +378,35 @@ export default class ExportAsJSONAction {
* @private
* @param {object} completedTree
*/
_saveAs(completedTree) {
saveAs(completedTree) {
this.JSONExportService.export(completedTree, { filename: this.root.name + '.json' });
}
/**
* @private
* @returns {object}
*/
_wrapTree() {
#wrapTree() {
return {
openmct: this.tree,
rootId: this._getKeystring(this.root)
rootId: this.#getKeystring(this.root)
};
}
_decrementCallsAndSave() {
#decrementCallsAndSave() {
this.calls--;
this.#updateProgress();
if (this.calls === 0) {
this._rewriteReferences();
this._saveAs(this._wrapTree());
this.#rewriteReferences();
this.dialog.dismiss();
this.#resetCounts();
this.saveAs(this.#wrapTree());
this.dialog = null;
}
}
_copy(object) {
#copy(object) {
return JSON.parse(JSON.stringify(object));
}
}

View File

@ -138,7 +138,7 @@ describe('Export as JSON plugin', () => {
};
});
spyOn(exportAsJSONAction, '_saveAs').and.callFake((completedTree) => {
spyOn(exportAsJSONAction, 'saveAs').and.callFake((completedTree) => {
expect(Object.keys(completedTree).length).toBe(2);
expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy();
expect(Object.prototype.hasOwnProperty.call(completedTree, 'rootId')).toBeTruthy();
@ -195,7 +195,7 @@ describe('Export as JSON plugin', () => {
};
});
spyOn(exportAsJSONAction, '_saveAs').and.callFake((completedTree) => {
spyOn(exportAsJSONAction, 'saveAs').and.callFake((completedTree) => {
expect(Object.keys(completedTree).length).toBe(2);
expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy();
expect(Object.prototype.hasOwnProperty.call(completedTree, 'rootId')).toBeTruthy();
@ -257,7 +257,7 @@ describe('Export as JSON plugin', () => {
};
});
spyOn(exportAsJSONAction, '_saveAs').and.callFake((completedTree) => {
spyOn(exportAsJSONAction, 'saveAs').and.callFake((completedTree) => {
expect(Object.keys(completedTree).length).toBe(2);
expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy();
expect(Object.prototype.hasOwnProperty.call(completedTree, 'rootId')).toBeTruthy();
@ -318,7 +318,7 @@ describe('Export as JSON plugin', () => {
};
});
spyOn(exportAsJSONAction, '_saveAs').and.callFake((completedTree) => {
spyOn(exportAsJSONAction, 'saveAs').and.callFake((completedTree) => {
expect(Object.keys(completedTree).length).toBe(2);
expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy();
expect(Object.prototype.hasOwnProperty.call(completedTree, 'rootId')).toBeTruthy();
@ -373,7 +373,7 @@ describe('Export as JSON plugin', () => {
return Promise.resolve(child);
});
spyOn(exportAsJSONAction, '_saveAs').and.callFake((completedTree) => {
spyOn(exportAsJSONAction, 'saveAs').and.callFake((completedTree) => {
expect(Object.keys(completedTree).length).toBe(2);
const conditionSetId = Object.keys(completedTree.openmct)[1];
expect(Object.prototype.hasOwnProperty.call(completedTree, 'openmct')).toBeTruthy();

View File

@ -71,14 +71,14 @@ export default class LocalStorageObjectProvider {
* @private
*/
persistSpace(space) {
this.localStorage[this.spaceKey] = JSON.stringify(space);
this.localStorage.setItem(this.spaceKey, JSON.stringify(space));
}
/**
* @private
*/
getSpace() {
return this.localStorage[this.spaceKey];
return this.localStorage.getItem(this.spaceKey);
}
/**
@ -93,7 +93,7 @@ export default class LocalStorageObjectProvider {
*/
initializeSpace() {
if (this.isEmpty()) {
this.localStorage[this.spaceKey] = JSON.stringify({});
this.localStorage.setItem(this.spaceKey, JSON.stringify({}));
}
}
@ -101,6 +101,6 @@ export default class LocalStorageObjectProvider {
* @private
*/
isEmpty() {
return this.getSpace() === undefined;
return this.getSpace() === null;
}
}

View File

@ -1,5 +1,5 @@
import { saveAs } from 'file-saver';
import Moment from 'moment';
import { saveAs } from 'saveAs';
import { NOTEBOOK_TYPE, RESTRICTED_NOTEBOOK_TYPE } from '../notebook-constants.js';
const UNKNOWN_USER = 'Unknown';

View File

@ -23,14 +23,12 @@
<div class="c-progress-bar">
<div
class="c-progress-bar__bar"
:class="{ '--indeterminate': !model.progressPerc }"
:class="{ '--indeterminate': !progressPerc }"
:style="styleBarWidth"
></div>
<div v-if="model.progressText !== undefined" class="c-progress-bar__text">
<span v-if="model.progressPerc && model.progressPerc > 0"
>{{ model.progressPerc }}% complete.</span
>
{{ model.progressText }}
<div v-if="progressText !== ''" class="c-progress-bar__text">
<span v-if="progressPerc > 0">{{ progressPerc }}% complete.</span>
{{ progressText }}
</div>
</div>
</template>
@ -38,14 +36,18 @@
<script>
export default {
props: {
model: {
type: Object,
required: true
progressPerc: {
type: Number,
default: 0
},
progressText: {
type: String,
default: ''
}
},
computed: {
styleBarWidth() {
return this.model.progressPerc ? `width: ${this.model.progressPerc}%;` : '';
return this.progressPerc ? `width: ${this.progressPerc}%;` : '';
}
}
};

View File

@ -23,6 +23,7 @@
import mount from 'utils/mount';
import { createOpenMct, resetApplicationState } from 'utils/testing';
import { mockLocalStorage } from 'utils/testing/mockLocalStorage';
import { nextTick } from 'vue';
import StylesView from '@/plugins/condition/components/inspector/StylesView.vue';
@ -67,7 +68,7 @@ describe('the inspector', () => {
expect(savedStylesViewComponent.$refs.root.savedStyles.length).toBe(1);
});
it('should display all saved styles', () => {
it('should display all saved styles', async () => {
selection = mockTelemetryTableSelection;
stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
@ -75,9 +76,8 @@ describe('the inspector', () => {
expect(savedStylesViewComponent.$refs.root.savedStyles.length).toBe(0);
stylesViewComponent.$refs.root.saveStyle(mockStyle);
return stylesViewComponent.$nextTick().then(() => {
expect(savedStylesViewComponent.$refs.root.savedStyles.length).toBe(1);
});
await nextTick();
expect(savedStylesViewComponent.$refs.root.savedStyles.length).toBe(1);
});
xit('should allow a saved style to be applied', () => {
@ -139,55 +139,52 @@ describe('the inspector', () => {
expect(savedStylesViewComponent.$refs.root.savedStyles.length).toBe(20);
});
it('should allow styles from multi-selections to be saved', () => {
it('should allow styles from multi-selections to be saved', async () => {
spyOn(openmct.editor, 'isEditing').and.returnValue(true);
selection = mockMultiSelectionSameStyles;
stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
return stylesViewComponent.$nextTick().then(() => {
const styleEditorComponent = stylesViewComponent.$refs.root.$refs.styleEditor;
const saveStyleButton = styleEditorComponent.$refs.saveStyleButton;
await nextTick();
const styleEditorComponent = stylesViewComponent.$refs.root.$refs.styleEditor;
const saveStyleButton = styleEditorComponent.$refs.saveStyleButton;
expect(saveStyleButton).not.toBe(undefined);
expect(saveStyleButton).not.toBe(undefined);
saveStyleButton.$refs.button.click();
saveStyleButton.$refs.button.click();
expect(savedStylesViewComponent.$refs.root.$data.savedStyles.length).toBe(1);
});
expect(savedStylesViewComponent.$refs.root.$data.savedStyles.length).toBe(1);
});
it('should prevent mixed styles from being saved', () => {
it('should prevent mixed styles from being saved', async () => {
spyOn(openmct.editor, 'isEditing').and.returnValue(true);
selection = mockMultiSelectionMixedStyles;
stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
return stylesViewComponent.$nextTick().then(() => {
const styleEditorComponent = stylesViewComponent.$refs.root.$refs.styleEditor;
const saveStyleButton = styleEditorComponent.$refs.saveStyleButton;
await nextTick();
const styleEditorComponent = stylesViewComponent.$refs.root.$refs.styleEditor;
const saveStyleButton = styleEditorComponent.$refs.saveStyleButton;
// Saving should not be enabled, thus the button ref should be undefined
expect(saveStyleButton).toBe(undefined);
});
// Saving should not be enabled, thus the button ref should be undefined
expect(saveStyleButton).toBe(undefined);
});
it('should prevent non-specific styles from being saved', () => {
it('should prevent non-specific styles from being saved', async () => {
spyOn(openmct.editor, 'isEditing').and.returnValue(true);
selection = mockMultiSelectionNonSpecificStyles;
stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
return stylesViewComponent.$nextTick().then(() => {
const styleEditorComponent = stylesViewComponent.$refs.root.$refs.styleEditor;
const saveStyleButton = styleEditorComponent.$refs.saveStyleButton;
await nextTick();
const styleEditorComponent = stylesViewComponent.$refs.root.$refs.styleEditor;
const saveStyleButton = styleEditorComponent.$refs.saveStyleButton;
// Saving should not be enabled, thus the button ref should be undefined
expect(saveStyleButton).toBe(undefined);
});
// Saving should not be enabled, thus the button ref should be undefined
expect(saveStyleButton).toBe(undefined);
});
function createViewComponent(component) {

View File

@ -10,7 +10,7 @@ export function mockLocalStorage() {
store = {};
function getItem(key) {
return store[key];
return store[key] || null;
}
function setItem(key, value) {