Compare commits

...

22 Commits

Author SHA1 Message Date
3d2c9b8787 refactoring search to be more performant based on core changes 2024-06-07 12:20:34 -07:00
3b28dd6238 Refactors all bad usage of innerHTML out of our codebase (#7242) 2023-11-30 10:06:28 -08:00
cbf52eb7d5 [Plots] Fix Plots breaking on exceptionally large values (#7120) 2023-10-13 11:50:24 -07:00
d65821ca81 rename table configuration tab to Config 2023-10-12 11:28:14 -07:00
798eb4444d reimplement fix for smushed keys 2023-09-12 13:53:21 -07:00
59ae7cddc5 Merge branch 'omm-release/5.1' of https://github.com/nasa/openmct into omm-release/5.1
mergin
2023-07-07 11:14:50 -07:00
321c7a3af5 suppressing abort errors as they are expected 2023-07-07 11:13:43 -07:00
609cf72bd1 chicken needs to come first 2023-07-06 14:39:26 -07:00
a447b0ada8 removing eval-source-map, dont debuggin 2023-06-21 16:32:57 -07:00
5788f4cc69 temp source maps for debugin 2023-06-21 14:11:23 -07:00
f94b4e53c7 adding back in abort on item close in tree 2023-06-21 13:31:12 -07:00
faf71f1e67 omm-cherry-pick(#6602) : [ExportAsJson] Multiple Aliases in Export an… (#6662)
omm-cherry-pick(#6602) : [ExportAsJson] Multiple Aliases in Export and Conditional Styles Fixes (#6602)

Fixes issues that prevent import and export from being completed successfully. Specifically:

* if multiple aliases are detected, the first is created as a new object and and added to it's parent's composition, any subsequent aliases of the same object will not be recreated, but the originally created one will be added to the current parent's composition, creating an alias.

* Also, there are cases were conditionSetIdentifiers are stored in an object keyed by an item id in the configuration.objectstyles object, this fix will handle these as well.

* Replaces an errant `return` statement with a `continue` statement to prevent early exit from a recursive function.

---------

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2023-05-15 13:12:37 -07:00
23310f85ae cherry pick retain styles on url change 2023-05-02 13:38:13 -07:00
d80819634b revert opening in new tab 2023-04-12 15:01:53 -07:00
483b62c152 Only decrement and save if there is composition but no child object reference 2023-04-07 14:08:58 -07:00
1254279635 Fix ExportAsJSONAction to not lose layout configurations on linked objects (#6562) 2023-04-05 23:05:27 -07:00
c768a71656 free findSubscriptionProvider 2023-03-31 15:50:38 -07:00
678a92bd29 Revert "enable source maps in prod"
This reverts commit 34b488944a.
2023-03-31 14:43:02 -07:00
34b488944a enable source maps in prod 2023-03-31 14:42:39 -07:00
4d1dd2f51d add temporary debugging 2023-03-31 12:23:23 -07:00
080f7b8f4b pass openmct and object key to function
re-enable historical row action
2023-03-29 17:09:35 -07:00
483f2feac8 allow contextual domain objects to row actions 2023-03-29 16:58:51 -07:00
27 changed files with 496 additions and 327 deletions

View File

@ -239,9 +239,15 @@ export default class ObjectAPI {
return domainObject;
}).catch((error) => {
console.warn(`Failed to retrieve ${keystring}:`, error);
let result;
delete this.cache[keystring];
const result = this.applyGetInterceptors(identifier);
// suppress abort errors
if (error.name !== 'AbortError') {
console.warn(`Failed to retrieve ${keystring}:`, error);
result = this.applyGetInterceptors(identifier);
}
return result;
});

View File

@ -55,6 +55,13 @@ define([
*/
function parseKeyString(keyString) {
if (isIdentifier(keyString)) {
// TODO REMOVE FOR OMM-RELEASE-5.0
if (!keyString.namespace && keyString.key.includes(':')) {
console.warn(`smushed key: ${keyString.key}`);
return parseKeyString(keyString.key);
}
return keyString;
}

View File

@ -88,7 +88,7 @@ export default class TelemetryAPI {
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/
canProvideTelemetry(domainObject) {
return Boolean(this.#findSubscriptionProvider(domainObject))
return Boolean(this.findSubscriptionProvider(domainObject))
|| Boolean(this.findRequestProvider(domainObject));
}
@ -123,9 +123,10 @@ export default class TelemetryAPI {
}
/**
* @private
* Returns a telemetry subscription provider that supports
* a given domain object and options.
*/
#findSubscriptionProvider() {
findSubscriptionProvider() {
const args = Array.prototype.slice.apply(arguments);
function supportsDomainObject(provider) {
return provider.supportsSubscribe.apply(provider, args);
@ -348,7 +349,7 @@ export default class TelemetryAPI {
return () => {};
}
const provider = this.#findSubscriptionProvider(domainObject);
const provider = this.findSubscriptionProvider(domainObject);
if (!this.subscribeCache) {
this.subscribeCache = {};

View File

@ -21,16 +21,18 @@
*****************************************************************************/
<template>
<component
:is="urlDefined ? 'a' : 'span'"
<div
ref="conditionWidgetElement"
class="c-condition-widget u-style-receiver js-style-receiver"
:href="url"
:target="url ? '_BLANK' : ''"
>
<div class="c-condition-widget__label">
{{ label }}
</div>
</component>
<component
:is="urlDefined ? 'a' : 'div'"
class="c-condition-widget__label-wrapper"
:href="url"
>
<div class="c-condition-widget__label">{{ label }}</div>
</component>
</div>
</template>
<script>
@ -40,19 +42,26 @@ export default {
inject: ['openmct', 'domainObject'],
data: function () {
return {
conditionalLabel: '',
conditionSetIdentifier: null,
domainObjectLabel: '',
url: null,
urlDefined: false,
useConditionSetOutputAsLabel: false
conditionalLabel: ''
};
},
computed: {
urlDefined() {
return this.domainObject.url?.length > 0;
},
url() {
return this.urlDefined ? sanitizeUrl(this.domainObject.url) : null;
},
useConditionSetOutputAsLabel() {
return this.conditionSetIdentifier && this.domainObject.configuration.useConditionSetOutputAsLabel;
},
conditionSetIdentifier() {
return this.domainObject.configuration?.objectStyles?.conditionSetIdentifier;
},
label() {
return this.useConditionSetOutputAsLabel
? this.conditionalLabel
: this.domainObjectLabel
: this.domainObject.label
;
}
},
@ -69,20 +78,11 @@ export default {
}
},
mounted() {
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
if (this.domainObject) {
this.updateDomainObject(this.domainObject);
this.listenToConditionSetChanges();
}
},
beforeDestroy() {
this.conditionSetIdentifier = null;
if (this.unlisten) {
this.unlisten();
}
this.stopListeningToConditionSetChanges();
},
methods: {
@ -121,31 +121,6 @@ export default {
}
this.conditionalLabel = latestDatum.output || '';
},
updateDomainObject(domainObject) {
if (this.domainObjectLabel !== domainObject.label) {
this.domainObjectLabel = domainObject.label;
}
const urlDefined = domainObject.url && domainObject.url.length > 0;
if (this.urlDefined !== urlDefined) {
this.urlDefined = urlDefined;
}
const url = this.urlDefined ? sanitizeUrl(domainObject.url) : null;
if (this.url !== url) {
this.url = url;
}
const conditionSetIdentifier = domainObject.configuration?.objectStyles?.conditionSetIdentifier;
if (conditionSetIdentifier && this.conditionSetIdentifier !== conditionSetIdentifier) {
this.conditionSetIdentifier = conditionSetIdentifier;
}
const useConditionSetOutputAsLabel = this.conditionSetIdentifier && domainObject.configuration.useConditionSetOutputAsLabel;
if (this.useConditionSetOutputAsLabel !== useConditionSetOutputAsLabel) {
this.useConditionSetOutputAsLabel = useConditionSetOutputAsLabel;
}
}
}
};

View File

@ -26,31 +26,35 @@
background-color: rgba($colorBodyFg, 0.1); // Give a little presence if the user hasn't defined a fill color
border-radius: $basicCr;
border: 1px solid transparent;
display: inline-block;
padding: $interiorMarginLg $interiorMarginLg * 2;
display: block;
max-width: max-content;
a {
display: block;
color: inherit;
}
}
.c-condition-widget__label {
padding: $interiorMargin;
// Either a <div> or an <a> tag
padding: $interiorMargin $interiorMargin * 1.5;
text-align: center;
white-space: normal;
}
a.c-condition-widget {
// Widget is conditionally made into a <a> when URL property has been defined
cursor: pointer !important;
pointer-events: inherit;
}
// Make Condition Widget expand when in a hidden frame Layout context
// For both static and Flexible Layouts
.c-so-view--conditionWidget.c-so-view--no-frame {
.c-condition-widget {
@include abs();
display: flex;
align-items: center;
justify-content: center;
padding: 0;
max-width: unset;
&__label-wrapper {
@include abs();
display: flex;
align-items: center;
justify-content: center;
}
}
.c-so-view__frame-controls { display: none; }

View File

@ -36,6 +36,7 @@ export default function plugin() {
domainObject.configuration = {};
domainObject.label = 'Condition Widget';
domainObject.conditionalLabel = '';
domainObject.url = '';
},
form: [
{

View File

@ -20,8 +20,6 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import JSONExporter from '/src/exporters/JSONExporter.js';
import _ from 'lodash';
import { v4 as uuid } from 'uuid';
export default class ExportAsJSONAction {
@ -35,10 +33,9 @@ export default class ExportAsJSONAction {
this.group = "json";
this.priority = 1;
this.externalIdentifiers = [];
this.tree = {};
this.calls = 0;
this.idMap = {};
this.tree = null;
this.calls = null;
this.idMap = null;
this.JSONExportService = new JSONExporter();
}
@ -60,21 +57,164 @@ export default class ExportAsJSONAction {
*/
invoke(objectpath) {
this.tree = {};
this.calls = 0;
this.idMap = {};
const root = objectpath[0];
this.root = JSON.parse(JSON.stringify(root));
const rootId = this._getId(this.root);
this.root = this._copy(root);
const rootId = this._getKeystring(this.root);
this.tree[rootId] = this.root;
this._write(this.root);
}
/**
* @private
* @param {object} parent
*/
async _write(parent) {
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);
if (composition) {
const children = await composition.load();
children.forEach((child) => {
this._exportObject(child, parent);
});
}
if (!conditionSetIdentifier && !hasItemConditionSetIdentifiers) {
this._decrementCallsAndSave();
} else {
const conditionSetObjects = [];
// conditionSetIdentifiers directly in objectStyles object
if (conditionSetIdentifier) {
conditionSetObjects.push(await this.openmct.objects.get(conditionSetIdentifier));
}
// conditionSetIdentifiers stored on item ids in the objectStyles object
if (hasItemConditionSetIdentifiers) {
const itemConditionSetIdentifiers = this._getItemConditionSetIdentifiers(parent);
for (const itemConditionSetIdentifier of itemConditionSetIdentifiers) {
conditionSetObjects.push(await this.openmct.objects.get(itemConditionSetIdentifier));
}
}
for (const conditionSetObject of conditionSetObjects) {
this._exportObject(conditionSetObject, parent);
}
this._decrementCallsAndSave();
}
}
_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);
} else {
this.tree[originalKeyString] = child;
}
this._write(child);
}
}
/**
* @private
* @param {object} child
* @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);
const existingMappedKeyString = this.idMap[originalKeyString];
let copy;
if (!existingMappedKeyString) {
copy = this._copy(child);
copy.identifier.key = uuid();
if (!conditionSetIdentifier && !hasItemConditionSetIdentifiers) {
copy.location = parentKeyString;
}
let newKeyString = this._getKeystring(copy);
this.idMap[originalKeyString] = newKeyString;
this.tree[newKeyString] = copy;
} else {
copy = this.tree[existingMappedKeyString];
}
if (conditionSetIdentifier || hasItemConditionSetIdentifiers) {
// update objectStyle object
if (conditionSetIdentifier) {
const directObjectStylesIdentifier = this.openmct.objects.areIdsEqual(
parent.configuration.objectStyles.conditionSetIdentifier,
child.identifier
);
if (directObjectStylesIdentifier) {
parent.configuration.objectStyles.conditionSetIdentifier = copy.identifier;
this.tree[parentKeyString].configuration.objectStyles.conditionSetIdentifier = copy.identifier;
}
}
// update per item id on objectStyle object
if (hasItemConditionSetIdentifiers) {
for (const itemId in parent.configuration.objectStyles) {
if (parent.configuration.objectStyles[itemId]) {
const itemConditionSetIdentifier = parent.configuration.objectStyles[itemId].conditionSetIdentifier;
if (
itemConditionSetIdentifier
&& this.openmct.objects.areIdsEqual(itemConditionSetIdentifier, child.identifier)
) {
parent.configuration.objectStyles[itemId].conditionSetIdentifier = copy.identifier;
this.tree[parentKeyString].configuration.objectStyles[itemId].conditionSetIdentifier = copy.identifier;
}
}
}
}
} else {
// just update parent
const index = parent.composition.findIndex(identifier => {
return this.openmct.objects.areIdsEqual(child.identifier, identifier);
});
parent.composition[index] = copy.identifier;
this.tree[parentKeyString].composition[index] = copy.identifier;
}
return copy;
}
/**
* @private
* @param {object} domainObject
* @returns {string} A string representation of the given identifier, including namespace and key
*/
_getId(domainObject) {
_getKeystring(domainObject) {
return this.openmct.objects.makeKeyString(domainObject.identifier);
}
/**
* @private
* @param {object} domainObject
@ -86,6 +226,7 @@ export default class ExportAsJSONAction {
return type && type.definition.creatable && isPersistable;
}
/**
* @private
* @param {object} child
@ -93,74 +234,80 @@ export default class ExportAsJSONAction {
* @returns {boolean}
*/
_isLinkedObject(child, parent) {
if (child.location !== this._getId(parent)
&& !Object.keys(this.tree).includes(child.location)
&& this._getId(child) !== this._getId(this.root)
|| this.externalIdentifiers.includes(this._getId(child))) {
const rootKeyString = this._getKeystring(this.root);
const childKeyString = this._getKeystring(child);
const parentKeyString = this._getKeystring(parent);
return true;
return (child.location !== parentKeyString
&& !Object.keys(this.tree).includes(child.location)
&& childKeyString !== rootKeyString)
|| this.idMap[childKeyString] !== undefined;
}
_getConditionSetIdentifier(object) {
return object.configuration?.objectStyles?.conditionSetIdentifier;
}
_hasItemConditionSetIdentifiers(parent) {
const objectStyles = parent.configuration?.objectStyles;
for (const itemId in objectStyles) {
if (Object.prototype.hasOwnProperty.call(objectStyles[itemId], 'conditionSetIdentifier')) {
return true;
}
}
return false;
}
/**
* @private
* @param {object} child
* @param {object} parent
* @returns {object}
*/
_rewriteLink(child, parent) {
this.externalIdentifiers.push(this._getId(child));
const index = parent.composition.findIndex(id => {
return _.isEqual(child.identifier, id);
});
const copyOfChild = JSON.parse(JSON.stringify(child));
copyOfChild.identifier.key = uuid();
const newIdString = this._getId(copyOfChild);
const parentId = this._getId(parent);
_getItemConditionSetIdentifiers(parent) {
const objectStyles = parent.configuration?.objectStyles;
let identifiers = new Set();
this.idMap[this._getId(child)] = newIdString;
copyOfChild.location = parentId;
parent.composition[index] = copyOfChild.identifier;
this.tree[newIdString] = copyOfChild;
this.tree[parentId].composition[index] = copyOfChild.identifier;
if (objectStyles) {
Object.keys(objectStyles).forEach(itemId => {
if (objectStyles[itemId].conditionSetIdentifier) {
identifiers.add(objectStyles[itemId].conditionSetIdentifier);
}
});
}
return copyOfChild;
return Array.from(identifiers);
}
/**
* @private
* @param {object} child
* @param {object} parent
* @returns {object}
*/
_rewriteLinkForReference(child, parent) {
const childId = this._getId(child);
this.externalIdentifiers.push(childId);
const copyOfChild = JSON.parse(JSON.stringify(child));
copyOfChild.identifier.key = uuid();
const newIdString = this._getId(copyOfChild);
const parentId = this._getId(parent);
this.idMap[childId] = newIdString;
copyOfChild.location = null;
parent.configuration.objectStyles.conditionSetIdentifier = copyOfChild.identifier;
this.tree[newIdString] = copyOfChild;
this.tree[parentId].configuration.objectStyles.conditionSetIdentifier = copyOfChild.identifier;
return copyOfChild;
}
/**
* @private
*/
_rewriteReferences() {
const oldKeyStrings = Object.keys(this.idMap);
let treeString = JSON.stringify(this.tree);
Object.keys(this.idMap).forEach(function (oldId) {
const newId = this.idMap[oldId];
treeString = treeString.split(oldId).join(newId);
}.bind(this));
oldKeyStrings.forEach((oldKeyString) => {
// 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 oldIdentifierNamespaceFirst = JSON.stringify(oldIdentifier);
const oldIdentifierKeyFirst = JSON.stringify({
key: oldIdentifier.key,
namespace: oldIdentifier.namespace
});
// replace keyStrings
treeString = treeString.split(oldKeyString).join(newKeyString);
// check for namespace first identifiers, replace if necessary
if (treeString.includes(oldIdentifierNamespaceFirst)) {
treeString = treeString.split(oldIdentifierNamespaceFirst).join(newIdentifier);
}
// check for key first identifiers, replace if necessary
if (treeString.includes(oldIdentifierKeyFirst)) {
treeString = treeString.split(oldIdentifierKeyFirst).join(newIdentifier);
}
});
this.tree = JSON.parse(treeString);
}
/**
@ -180,70 +327,10 @@ export default class ExportAsJSONAction {
_wrapTree() {
return {
"openmct": this.tree,
"rootId": this._getId(this.root)
"rootId": this._getKeystring(this.root)
};
}
/**
* @private
* @param {object} parent
*/
_write(parent) {
this.calls++;
//conditional object styles are not saved on the composition, so we need to check for them
let childObjectReferenceId = parent.configuration?.objectStyles?.conditionSetIdentifier;
const composition = this.openmct.composition.get(parent);
if (composition !== undefined) {
composition.load()
.then((children) => {
children.forEach((child, index) => {
// Only export if object is creatable
if (this._isCreatableAndPersistable(child)) {
// Prevents infinite export of self-contained objs
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
// If object is a link to something absent from
// tree, generate new id and treat as new object
if (this._isLinkedObject(child, parent)) {
child = this._rewriteLink(child, parent);
} else {
this.tree[this._getId(child)] = child;
}
this._write(child);
}
}
});
this._decrementCallsAndSave();
});
} else if (!childObjectReferenceId) {
this._decrementCallsAndSave();
}
if (childObjectReferenceId) {
this.openmct.objects.get(childObjectReferenceId)
.then((child) => {
// Only export if object is creatable
if (this._isCreatableAndPersistable(child)) {
// Prevents infinite export of self-contained objs
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
// If object is a link to something absent from
// tree, generate new id and treat as new object
if (this._isLinkedObject(child, parent)) {
child = this._rewriteLinkForReference(child, parent);
} else {
this.tree[this._getId(child)] = child;
}
this._write(child);
}
}
this._decrementCallsAndSave();
});
}
}
_decrementCallsAndSave() {
this.calls--;
if (this.calls === 0) {
@ -251,4 +338,8 @@ export default class ExportAsJSONAction {
this._saveAs(this._wrapTree());
}
}
_copy(object) {
return JSON.parse(JSON.stringify(object));
}
}

View File

@ -56,6 +56,10 @@ define([
});
},
showTab: function (isEditing) {
if (isEditing) {
return true;
}
const hasPersistedFilters = Boolean(domainObject?.configuration?.filters);
const hasGlobalFilters = Boolean(domainObject?.configuration?.globalFilters);

View File

@ -154,8 +154,11 @@ export default {
let originalClassName = this.dragGhost.classList[0];
this.dragGhost.className = '';
this.dragGhost.classList.add(originalClassName, iconClass);
this.dragGhost.textContent = '';
const span = document.createElement('span');
span.textContent = this.domainObject.name;
this.dragGhost.appendChild(span);
this.dragGhost.innerHTML = `<span>${this.domainObject.name}</span>`;
event.dataTransfer.setDragImage(this.dragGhost, 0, 0);
}

View File

@ -64,6 +64,11 @@ export default class CreateAction extends PropertiesAction {
const parentDomainObject = this.openmct.objects.toMutable(parentDomainObjectPath[0]);
// TODO REMOVE FOR OMM-RELEASE-5.0
if (!parentDomainObject.identifier.namespace && parentDomainObject.key) {
console.error(`parent namespace in key: ${parentDomainObject.key}`);
}
this.domainObject.modified = Date.now();
this.domainObject.location = this.openmct.objects.makeKeyString(parentDomainObject.identifier);
this.domainObject.identifier.namespace = parentDomainObject.identifier.namespace;

View File

@ -31,6 +31,7 @@ export default class ImportAsJSONAction {
this.cssClass = "icon-import";
this.group = "json";
this.priority = 2;
this.newObjects = [];
this.openmct = openmct;
}
@ -85,22 +86,25 @@ export default class ImportAsJSONAction {
let objectIdentifiers = this._getObjectReferenceIds(parent);
if (objectIdentifiers.length) {
let newObj;
const parentId = this.openmct.objects.makeKeyString(parent.identifier);
seen.push(parentId);
seen.push(parent.id);
objectIdentifiers.forEach(async (childId) => {
for (const childId of objectIdentifiers) {
const keystring = this.openmct.objects.makeKeyString(childId);
if (!tree[keystring] || seen.includes(keystring)) {
return;
continue;
}
const newModel = tree[keystring];
delete newModel.persisted;
newObj = await this._instantiate(newModel);
this._deepInstantiate(newObj, tree, seen);
}, this);
this.newObjects.push(newModel);
// make sure there weren't any errors saving
if (newModel) {
this._deepInstantiate(newModel, tree, seen);
}
}
}
}
/**
@ -110,19 +114,32 @@ export default class ImportAsJSONAction {
*/
_getObjectReferenceIds(parent) {
let objectIdentifiers = [];
let itemObjectReferences = [];
const objectStyles = parent?.configuration?.objectStyles;
const parentComposition = this.openmct.composition.get(parent);
let parentComposition = this.openmct.composition.get(parent);
if (parentComposition) {
objectIdentifiers = Array.from(parentComposition.domainObject.composition);
objectIdentifiers = Array.from(parent.composition);
}
//conditional object styles are not saved on the composition, so we need to check for them
let parentObjectReference = parent.configuration?.objectStyles?.conditionSetIdentifier;
if (parentObjectReference) {
objectIdentifiers.push(parentObjectReference);
if (objectStyles) {
const parentObjectReference = objectStyles.conditionSetIdentifier;
if (parentObjectReference) {
objectIdentifiers.push(parentObjectReference);
}
function hasConditionSetIdentifier(item) {
return Boolean(item.conditionSetIdentifier);
}
itemObjectReferences = Object.values(objectStyles)
.filter(hasConditionSetIdentifier)
.map(item => item.conditionSetIdentifier);
}
return objectIdentifiers;
return Array.from(new Set([...objectIdentifiers, ...itemObjectReferences]));
}
/**
* @private
@ -155,13 +172,21 @@ export default class ImportAsJSONAction {
const tree = this._generateNewIdentifiers(objTree, namespace);
const rootId = tree.rootId;
const rootModel = tree.openmct[rootId];
delete rootModel.persisted;
const rootObj = tree.openmct[rootId];
delete rootObj.persisted;
this.newObjects.push(rootObj);
const rootObj = await this._instantiate(rootModel);
if (this.openmct.composition.checkPolicy(domainObject, rootObj)) {
this._deepInstantiate(rootObj, tree.openmct, []);
try {
await Promise.all(this.newObjects.map(this._instantiate, this));
} catch (error) {
this.openmct.notifications.error('Error saving objects');
throw error;
}
const compositionCollection = this.openmct.composition.get(domainObject);
let domainObjectKeyString = this.openmct.objects.makeKeyString(domainObject.identifier);
this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString);
@ -184,16 +209,11 @@ export default class ImportAsJSONAction {
}
/**
* @private
* @param {object} rootModel
* @param {object} model
* @returns {object}
*/
async _instantiate(rootModel) {
const success = await this.openmct.objects.save(rootModel);
if (success) {
return rootModel;
}
this.openmct.notifications.error('Error saving objects');
_instantiate(model) {
return this.openmct.objects.save(model);
}
/**
* @private

View File

@ -19,7 +19,7 @@
class="c-icon-button c-button--menu icon-font"
@click.prevent.stop="showFontMenu"
>
<span class="c-button__label">{{ fontTypeLable }}</span>
<span class="c-button__label">{{ fontTypeLabel }}</span>
</button>
</div>
</div>
@ -43,7 +43,7 @@ export default {
}
},
computed: {
fontTypeLable() {
fontTypeLabel() {
const fontType = FONTS.find(f => f.value === this.fontStyle.font);
if (!fontType) {
return '??';

View File

@ -20,12 +20,10 @@
at runtime from the About dialog for additional information.
-->
<!-- eslint-disable vue/no-v-html -->
<template>
<div class="gl-plot-chart-area">
<span v-html="canvasTemplate"></span>
<span v-html="canvasTemplate"></span>
<canvas :style="canvasStyle"></canvas>
<canvas :style="canvasStyle"></canvas>
<div
ref="limitArea"
class="js-limit-area"
@ -132,10 +130,15 @@ export default {
required: true
}
},
data() {
return {
canvasTemplate: '<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>'
};
computed: {
canvasStyle() {
return {
position: 'absolute',
background: 'none',
width: '100%',
height: '100%'
};
}
},
watch: {
highlights() {
@ -418,7 +421,10 @@ export default {
// Have to throw away the old canvas elements and replace with new
// canvas elements in order to get new drawing contexts.
const div = document.createElement('div');
div.innerHTML = this.canvasTemplate + this.canvasTemplate;
div.innerHTML = `
<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>
<canvas style="position: absolute; background: none; width: 100%; height: 100%;"></canvas>
`;
const mainCanvas = div.querySelectorAll("canvas")[1];
const overlayCanvas = div.querySelectorAll("canvas")[0];
this.canvas.parentNode.replaceChild(mainCanvas, this.canvas);

View File

@ -63,6 +63,10 @@ import { symlog } from '../mathUtils';
*
* @extends {Model<PlotSeriesModelType, PlotSeriesModelOptions>}
*/
const FLOAT32_MAX = 3.4e38;
const FLOAT32_MIN = -3.4e38;
export default class PlotSeries extends Model {
logMode = false;
@ -357,7 +361,7 @@ export default class PlotSeries extends Model {
let stats = this.get('stats');
let changed = false;
if (!stats) {
if ([Infinity, -Infinity].includes(value)) {
if ([Infinity, -Infinity].includes(value) || !this.isValidFloat32(value)) {
return;
}
@ -369,13 +373,13 @@ export default class PlotSeries extends Model {
};
changed = true;
} else {
if (stats.maxValue < value && value !== Infinity) {
if (stats.maxValue < value && value !== Infinity && this.isValidFloat32(value)) {
stats.maxValue = value;
stats.maxPoint = point;
changed = true;
}
if (stats.minValue > value && value !== -Infinity) {
if (stats.minValue > value && value !== -Infinity && this.isValidFloat32(value)) {
stats.minValue = value;
stats.minPoint = point;
changed = true;
@ -411,7 +415,7 @@ export default class PlotSeries extends Model {
const lastYVal = this.getYVal(data[insertIndex - 1]);
if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) {
console.warn('[Plot] Invalid Y Values detected');
console.warn(`[Plot] Invalid Y Values detected: ${currentYVal} ${lastYVal}`);
return;
}
@ -439,7 +443,15 @@ export default class PlotSeries extends Model {
* @private
*/
isValueInvalid(val) {
return Number.isNaN(val) || this.unPlottableValues.includes(val);
return Number.isNaN(val) || this.unPlottableValues.includes(val) || !this.isValidFloat32(val);
}
/**
*
* @private
*/
isValidFloat32(val) {
return val < FLOAT32_MAX && val > FLOAT32_MIN;
}
/**

View File

@ -194,7 +194,7 @@ define([
const options = this.generateSelectOptions();
newInput = document.createElement("select");
newInput.innerHTML = options;
newInput.appendChild(options);
emitChange = true;
} else {
@ -224,12 +224,15 @@ define([
Condition.prototype.generateSelectOptions = function () {
let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object);
let options = '';
let fragment = document.createDocumentFragment();
telemetryMetadata[this.config.key].enumerations.forEach(enumeration => {
options += '<option value="' + enumeration.value + '">' + enumeration.string + '</option>';
const option = document.createElement('option');
option.value = enumeration.value;
option.textContent = enumeration.string;
fragment.appendChild(option);
});
return options;
return fragment;
};
return Condition;

View File

@ -165,7 +165,8 @@ define([
document.querySelectorAll('.t-drag-indicator').forEach(indicator => {
// eslint-disable-next-line no-invalid-this
const ruleHeader = self.domElement.querySelectorAll('.widget-rule-header')[0].cloneNode(true);
indicator.innerHTML = ruleHeader;
indicator.textContent = '';
indicator.appendChild(ruleHeader);
});
self.widgetDnD.setDragImage(self.domElement.querySelectorAll('.widget-rule-header')[0].cloneNode(true));
self.widgetDnD.dragStart(self.config.id);
@ -235,8 +236,8 @@ define([
this.listenTo(this.toggleConfigButton, 'click', toggleConfig);
this.listenTo(this.trigger, 'change', onTriggerInput);
this.title.innerHTML = self.config.name;
this.description.innerHTML = self.config.description;
this.title.innerText = self.config.name;
this.description.innerText = self.config.description;
this.trigger.value = self.config.trigger;
this.listenTo(this.grippy, 'mousedown', onDragStart);
@ -445,7 +446,7 @@ define([
const lastOfType = self.conditionArea.querySelector('li:last-of-type');
lastOfType.parentNode.insertBefore($condition, lastOfType);
if (loopCnt > 0) {
$condition.querySelector('.t-condition-context').innerHTML = triggerContextStr + ' when';
$condition.querySelector('.t-condition-context').innerText = triggerContextStr + ' when';
}
loopCnt++;
@ -517,7 +518,7 @@ define([
}
description = (description === '' ? this.config.description : description);
this.description.innerHTML = self.config.description;
this.description.innerText = self.config.description;
this.config.description = description;
};

View File

@ -229,9 +229,10 @@ define([
SummaryWidget.prototype.updateWidget = function () {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
const activeRule = this.rulesById[this.activeId];
this.applyStyle(this.domElement.querySelector('#widget'), activeRule.getProperty('style'));
this.domElement.querySelector('#widget').title = activeRule.getProperty('message');
this.domElement.querySelector('#widgetLabel').innerHTML = activeRule.getProperty('label');
this.domElement.querySelector('#widgetLabel').textContent = activeRule.getProperty('label');
this.domElement.querySelector('#widgetIcon').classList = WIDGET_ICON_CLASS + ' ' + activeRule.getProperty('icon');
};

View File

@ -49,11 +49,12 @@ define([
self.setNullOption(this.nullOption);
self.items.forEach(function (item) {
const itemElement = `<div class = "c-palette__item ${item}" data-item = "${item}"></div>`;
const temp = document.createElement('div');
temp.innerHTML = itemElement;
self.itemElements[item] = temp.firstChild;
self.domElement.querySelector('.c-palette__items').appendChild(temp.firstChild);
const itemElement = document.createElement('div');
itemElement.className = 'c-palette__item ' + item;
itemElement.setAttribute('data-item', item);
self.itemElements[item] = itemElement;
self.domElement.querySelector('.c-palette__items').appendChild(itemElement);
});
self.domElement.querySelector('.c-menu').style.display = 'none';

View File

@ -1,38 +1,60 @@
define([
'./summary-widget.html',
'@braintree/sanitize-url'
], function (
summaryWidgetTemplate,
urlSanitizeLib
) {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
import * as urlSanitizeLib from '@braintree/sanitize-url';
function SummaryWidgetView(domainObject, openmct) {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
class SummaryWidgetView {
#createSummaryWidgetTemplate() {
const anchor = document.createElement('a');
anchor.classList.add(
't-summary-widget',
'c-summary-widget',
'js-sw',
'u-links',
'u-fills-container'
);
const widgetIcon = document.createElement('div');
widgetIcon.id = 'widgetIcon';
widgetIcon.classList.add('c-sw__icon', 'js-sw__icon');
anchor.appendChild(widgetIcon);
const widgetLabel = document.createElement('div');
widgetLabel.id = 'widgetLabel';
widgetLabel.classList.add('c-sw__label', 'js-sw__label');
widgetLabel.textContent = 'Loading...';
anchor.appendChild(widgetLabel);
return anchor;
}
constructor(domainObject, openmct) {
this.openmct = openmct;
this.domainObject = domainObject;
this.hasUpdated = false;
this.render = this.render.bind(this);
}
SummaryWidgetView.prototype.updateState = function (datum) {
updateState(datum) {
this.hasUpdated = true;
this.widget.style.color = datum.textColor;
this.widget.style.backgroundColor = datum.backgroundColor;
this.widget.style.borderColor = datum.borderColor;
this.widget.title = datum.message;
this.label.title = datum.message;
this.label.innerHTML = datum.ruleLabel;
this.label.textContent = datum.ruleLabel;
this.icon.className = WIDGET_ICON_CLASS + ' ' + datum.icon;
};
}
SummaryWidgetView.prototype.render = function () {
render() {
if (this.unsubscribe) {
this.unsubscribe();
}
this.hasUpdated = false;
this.container.innerHTML = summaryWidgetTemplate;
const anchor = this.#createSummaryWidgetTemplate();
this.container.appendChild(anchor);
this.widget = this.container.querySelector('a');
this.icon = this.container.querySelector('#widgetIcon');
this.label = this.container.querySelector('.js-sw__label');
@ -52,26 +74,32 @@ define([
const renderTracker = {};
this.renderTracker = renderTracker;
this.openmct.telemetry.request(this.domainObject, {
strategy: 'latest',
size: 1
}).then(function (results) {
if (this.destroyed
|| this.hasUpdated
|| this.renderTracker !== renderTracker
|| results.length === 0) {
return;
}
this.updateState(results[results.length - 1]);
}.bind(this));
this.openmct.telemetry
.request(this.domainObject, {
strategy: 'latest',
size: 1
})
.then((results) => {
if (
this.destroyed ||
this.hasUpdated ||
this.renderTracker !== renderTracker ||
results.length === 0
) {
return;
}
this.unsubscribe = this.openmct
.telemetry
.subscribe(this.domainObject, this.updateState.bind(this));
};
this.updateState(results[results.length - 1]);
});
SummaryWidgetView.prototype.show = function (container) {
this.unsubscribe = this.openmct.telemetry.subscribe(
this.domainObject,
this.updateState.bind(this)
);
}
show(container) {
this.container = container;
this.render();
this.removeMutationListener = this.openmct.objects.observe(
@ -80,14 +108,14 @@ define([
this.onMutation.bind(this)
);
this.openmct.time.on('timeSystem', this.render);
};
}
SummaryWidgetView.prototype.onMutation = function (domainObject) {
onMutation(domainObject) {
this.domainObject = domainObject;
this.render();
};
}
SummaryWidgetView.prototype.destroy = function (container) {
destroy() {
this.unsubscribe();
this.removeMutationListener();
this.openmct.time.off('timeSystem', this.render);
@ -96,8 +124,7 @@ define([
delete this.label;
delete this.openmct;
delete this.domainObject;
};
}
}
return SummaryWidgetView;
});
export default SummaryWidgetView;

View File

@ -1,4 +0,0 @@
<a class="t-summary-widget c-summary-widget js-sw u-links u-fills-container">
<div id="widgetIcon" class="c-sw__icon js-sw__icon"></div>
<div id="widgetLabel" class="c-sw__label js-sw__label">Loading...</div>
</a>

View File

@ -35,7 +35,7 @@ define([
function TableConfigurationViewProvider(openmct) {
return {
key: 'table-configuration',
name: 'Configuration',
name: 'Config',
canView: function (selection) {
if (selection.length !== 1 || selection[0].length === 0) {
return false;

View File

@ -88,7 +88,7 @@ define([], function () {
}
getContextMenuActions() {
return ['viewDatumAction'];
return ['viewDatumAction', 'viewHistoricalData'];
}
}

View File

@ -175,14 +175,22 @@ export default {
getDatum() {
return this.row.fullDatum;
},
showContextMenu: function (event) {
showContextMenu: async function (event) {
event.preventDefault();
this.updateViewContext();
this.markRow(event);
const contextualDomainObject = await this.row.getContextualDomainObject?.(this.openmct, this.row.objectKeyString);
let objectPath = this.objectPath;
if (contextualDomainObject) {
objectPath = objectPath.slice();
objectPath.unshift(contextualDomainObject);
}
const actions = this.row.getContextMenuActions().map(key => this.openmct.actions.getAction(key));
const menuItems = this.openmct.menus.actionsToMenuItems(actions, this.objectPath, this.currentView);
const menuItems = this.openmct.menus.actionsToMenuItems(actions, objectPath, this.currentView);
if (menuItems.length) {
this.openmct.menus.showMenu(event.x, event.y, menuItems);
}

View File

@ -74,7 +74,7 @@ export default {
return this.domainObject && (this.currentObjectPath || this.objectPath);
},
objectFontStyle() {
return this.domainObject && this.domainObject.configuration && this.domainObject.configuration.fontStyle;
return this.domainObject?.configuration?.fontStyle;
},
fontSize() {
return this.objectFontStyle ? this.objectFontStyle.fontSize : this.layoutFontSize;
@ -287,6 +287,8 @@ export default {
this.$nextTick(() => {
this.updateStyle(this.styleRuleManager?.currentStyle);
this.setFontSize(this.fontSize);
this.setFont(this.font);
this.getActionCollection();
});
},
@ -329,9 +331,9 @@ export default {
},
initObjectStyles() {
if (!this.styleRuleManager) {
this.styleRuleManager = new StyleRuleManager((this.domainObject.configuration && this.domainObject.configuration.objectStyles), this.openmct, this.updateStyle.bind(this), true);
this.styleRuleManager = new StyleRuleManager((this.domainObject.configuration?.objectStyles), this.openmct, this.updateStyle.bind(this), true);
} else {
this.styleRuleManager.updateObjectStyleConfig(this.domainObject.configuration && this.domainObject.configuration.objectStyles);
this.styleRuleManager.updateObjectStyleConfig(this.domainObject.configuration?.objectStyles);
}
if (this.stopListeningStyles) {
@ -343,9 +345,6 @@ export default {
this.styleRuleManager.updateObjectStyleConfig(newObjectStyle);
});
this.setFontSize(this.fontSize);
this.setFont(this.font);
this.stopListeningFontStyles = this.openmct.objects.observe(this.domainObject, 'configuration.fontStyle', (newFontStyle) => {
this.setFontSize(newFontStyle.fontSize);
this.setFont(newFontStyle.font);

View File

@ -6,11 +6,12 @@
<input
class="c-search__input"
aria-label="Search Input"
tabindex="10000"
tabindex="0"
type="search"
v-bind="$attrs"
:value="value"
v-on="inputListeners"
@click="() => $emit('click')"
@input="($event) => $emit('input', $event.target.value)"
>
<a
class="c-search__clear-input icon-x-in-circle"
@ -35,27 +36,11 @@ export default {
active: false
};
},
computed: {
inputListeners: function () {
let vm = this;
return Object.assign({},
this.$listeners,
{
input: function (event) {
vm.$emit('input', event.target.value);
vm.active = (event.target.value.length > 0);
}
}
);
}
},
watch: {
value(inputValue) {
if (!inputValue.length) {
this.clearInput();
}
this.active = inputValue.length > 0;
}
},
methods: {
clearInput() {
@ -65,4 +50,4 @@ export default {
}
}
};
</script>
</script>

View File

@ -322,10 +322,14 @@ export default {
},
async openTreeItem(parentItem) {
const parentPath = parentItem.navigationPath;
const abortSignal = this.startItemLoad(parentPath);
this.startItemLoad(parentPath);
// pass in abort signal when functional
const childrenItems = await this.loadAndBuildTreeItemsFor(parentItem.object.identifier, parentItem.objectPath);
const childrenItems = await this.loadAndBuildTreeItemsFor(
parentItem.object.identifier,
parentItem.objectPath,
abortSignal
);
const parentIndex = this.treeItems.indexOf(parentItem);
// if it's not loading, it was aborted

View File

@ -1,8 +1,17 @@
export function convertTemplateToHTML(templateString) {
const template = document.createElement('template');
template.innerHTML = templateString;
const parser = new DOMParser();
const doc = parser.parseFromString(templateString, 'text/html');
return template.content.cloneNode(true).children;
// Create a document fragment to hold the parsed content
const fragment = document.createDocumentFragment();
// Append nodes from the parsed content to the fragment
while (doc.body.firstChild) {
fragment.appendChild(doc.body.firstChild);
}
// Convert children of the fragment to an array and return
return Array.from(fragment.children);
}
export function toggleClass(element, className) {