mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 21:27:52 +00:00
Merge branch 'master' of https://github.com/nasa/openmct into activity-states-and-compact-view
This commit is contained in:
commit
50de0f6f27
@ -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',
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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()
|
||||
]);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
import CSV from 'comma-separated-values';
|
||||
import { saveAs } from 'saveAs';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
class CSVExporter {
|
||||
export(rows, options) {
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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}%;` : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -10,7 +10,7 @@ export function mockLocalStorage() {
|
||||
store = {};
|
||||
|
||||
function getItem(key) {
|
||||
return store[key];
|
||||
return store[key] || null;
|
||||
}
|
||||
|
||||
function setItem(key, value) {
|
||||
|
Loading…
Reference in New Issue
Block a user