Compare commits

..

32 Commits

Author SHA1 Message Date
Jamie V
d42b2dd023 mappin 2023-08-17 11:23:42 -07:00
Jamie V
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
Jamie V
321c7a3af5 suppressing abort errors as they are expected 2023-07-07 11:13:43 -07:00
David Tsay
609cf72bd1 chicken needs to come first 2023-07-06 14:39:26 -07:00
Jamie V
a447b0ada8 removing eval-source-map, dont debuggin 2023-06-21 16:32:57 -07:00
Jamie V
5788f4cc69 temp source maps for debugin 2023-06-21 14:11:23 -07:00
Jamie V
f94b4e53c7 adding back in abort on item close in tree 2023-06-21 13:31:12 -07:00
Jamie V
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
David Tsay
23310f85ae cherry pick retain styles on url change 2023-05-02 13:38:13 -07:00
David Tsay
d80819634b revert opening in new tab 2023-04-12 15:01:53 -07:00
Shefali
483b62c152 Only decrement and save if there is composition but no child object reference 2023-04-07 14:08:58 -07:00
Jamie V
1254279635 Fix ExportAsJSONAction to not lose layout configurations on linked objects (#6562) 2023-04-05 23:05:27 -07:00
David Tsay
c768a71656 free findSubscriptionProvider 2023-03-31 15:50:38 -07:00
David Tsay
678a92bd29 Revert "enable source maps in prod"
This reverts commit 34b488944a.
2023-03-31 14:43:02 -07:00
David Tsay
34b488944a enable source maps in prod 2023-03-31 14:42:39 -07:00
David Tsay
4d1dd2f51d add temporary debugging 2023-03-31 12:23:23 -07:00
David Tsay
080f7b8f4b pass openmct and object key to function
re-enable historical row action
2023-03-29 17:09:35 -07:00
David Tsay
483f2feac8 allow contextual domain objects to row actions 2023-03-29 16:58:51 -07:00
Charles Hacskaylo
7ec2c4475b LAD Tables now disallow user-select (#6322)
* Closes #6321
- Set `user-select: none` on LAD Table td elements.
- Added hover effect to LAD Table rows as affordance of the Action
menu's availability.

* Add test to ensure rows cannot be selected but do show context menus

---------

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2023-03-28 14:38:52 -07:00
dependabot[bot]
8f59b16465 chore(deps-dev): bump @types/lodash from 4.14.191 to 4.14.192 (#6512)
Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.14.191 to 4.14.192.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

---
updated-dependencies:
- dependency-name: "@types/lodash"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 16:09:39 +00:00
Jamie V
36cfb1d515 [Condition Widgets] Keep styles for widgets with a URL (#6515)
* call update style after view is update with current style rule manager styles for components (namely condition widget) that have DOM changes after the element is grabbed to style

* target blank yo

---------

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2023-03-28 15:58:15 +00:00
David Tsay
2ff7132e90 use relative path (#6518) 2023-03-27 23:52:54 +00:00
Khalid Adil
d0ca398e01 [Plots, Multiple-Y Axis] Fix for rectangle drawn on each axis (#6490)
* Change logic so that the rectangle is only drawn on one axis

* Filter firstDrawableAxis before the rest of the logic

* Update to filter yAxisIds by the canDraw function
2023-03-24 13:46:10 -07:00
Jesse Mazzella
59278e8a06 chore: bump version to 2.2.1-SNAPSHOT (#6501) 2023-03-23 16:27:31 -07:00
dependabot[bot]
c8377f392b chore(deps-dev): bump eslint-plugin-vue from 9.9.0 to 9.10.0 (#6500)
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 9.9.0 to 9.10.0.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v9.9.0...v9.10.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-vue
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-03-23 15:54:10 -07:00
Vitor Henckel
29df748f2b fix(#6457): Tags disappear after resizing then dismissing the Add Tag select (#6499) 2023-03-23 14:48:56 -07:00
Vitor Henckel
665ba6dae1 fix(#6347): Searching a Notebook with whitelisted links for '.' exposes some html (#6396) 2023-03-23 21:23:49 +00:00
David Tsay
f39f8df4e2 Suppress annotations tab if no annotations defined (#6498)
* fix bad copy paste

* suppress annotations inspector tab if no tags
2023-03-23 14:00:46 -07:00
Marcelo Arias
4aa572d489 Button to clear the recent objects list (#6327) 2023-03-23 19:53:01 +00:00
Jesse Mazzella
0b24c4f2c5 fix(#6488): better determination of child tree items when collapsing a parent (#6489)
Splits the parent and child navigationPaths into arrays of keystrings and then checks to ensure that every keystring in the parent path is included in the child path in order
2023-03-23 19:02:44 +00:00
dependabot[bot]
e4657f79cd chore(deps-dev): bump plotly.js-basic-dist from 2.17.0 to 2.20.0 (#6438)
Bumps [plotly.js-basic-dist](https://github.com/plotly/plotly.js) from 2.17.0 to 2.20.0.
- [Release notes](https://github.com/plotly/plotly.js/releases)
- [Changelog](https://github.com/plotly/plotly.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.js/compare/v2.17.0...v2.20.0)

---
updated-dependencies:
- dependency-name: plotly.js-basic-dist
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-03-22 22:09:15 +00:00
dependabot[bot]
f2059406e0 chore(deps-dev): bump webpack from 5.76.2 to 5.76.3 (#6494)
Bumps [webpack](https://github.com/webpack/webpack) from 5.76.2 to 5.76.3.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.76.2...v5.76.3)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-22 13:35:24 -07:00
30 changed files with 530 additions and 298 deletions

View File

@@ -23,5 +23,5 @@ module.exports = merge(common, {
__OPENMCT_ROOT_RELATIVE__: '""'
})
],
devtool: "source-map"
devtool: "eval-source-map"
});

View File

@@ -27,26 +27,29 @@ test.describe('Testing LAD table configuration', () => {
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
// Create Sine Wave Generator
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: "Test Sine Wave Generator"
});
// Create LAD table
await createDomainObjectWithDefaults(page, {
const ladTable = await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
name: "Test LAD Table"
});
// Create Sine Wave Generator
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: "Test Sine Wave Generator",
parent: ladTable.uuid
});
await page.goto(ladTable.url);
});
test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => {
// Edit LAD table
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the LAD table and save changes
await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
// // Expand the 'My Items' folder in the left tree
// await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// // Add the Sine Wave Generator to the LAD table and save changes
// await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
// select configuration tab in inspector
await selectInspectorTab(page, 'LAD Table Configuration');
@@ -113,6 +116,24 @@ test.describe('Testing LAD table configuration', () => {
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
});
test('LAD Tables don\'t allow selection of rows but does show context click menus', async ({ page }) => {
const cell = await page.locator('.js-first-data');
const userSelectable = await cell.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue('user-select');
});
expect(userSelectable).toBe('none');
// Right-click on the LAD table row
await cell.click({
button: 'right'
});
const menuOptions = page.locator('.c-menu ul');
await expect.soft(menuOptions).toContainText('View Full Datum');
await expect.soft(menuOptions).toContainText('View Historical Data');
await expect.soft(menuOptions).toContainText('Remove');
// await page.locator('li[title="Remove this object from its containing object."]').click();
});
});
test.describe('Testing LAD table @unstable', () => {

View File

@@ -191,7 +191,7 @@ test.describe('Recent Objects', () => {
expect(await clockBreadcrumbs.count()).toBe(2);
expect(await clockBreadcrumbs.nth(0).innerText()).not.toEqual(await clockBreadcrumbs.nth(1).innerText());
});
test("Enforces a limit of 20 recent objects", async ({ page }) => {
test("Enforces a limit of 20 recent objects and clears the recent objects", async ({ page }) => {
// Creating 21 objects takes a while, so increase the timeout
test.slow();
@@ -242,6 +242,15 @@ test.describe('Recent Objects', () => {
// Assert that the Clock treeitem is no longer highlighted
await expect(lastClockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
// Click the aria-label="Clear Recently Viewed" button
await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
// Click on the "OK" button in the confirmation dialog
await page.getByRole('button', { name: 'OK' }).click();
// Assert that the list is empty
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
});
function assertInitialRecentObjectsListState() {

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "2.2.0-SNAPSHOT",
"version": "2.2.1-SNAPSHOT",
"description": "The Open MCT core platform",
"devDependencies": {
"@babel/eslint-parser": "7.18.9",
@@ -10,7 +10,7 @@
"@playwright/test": "1.29.0",
"@types/eventemitter3": "1.2.0",
"@types/jasmine": "4.3.1",
"@types/lodash": "4.14.191",
"@types/lodash": "4.14.192",
"babel-loader": "9.1.0",
"babel-plugin-istanbul": "6.1.1",
"codecov": "3.8.3",
@@ -23,7 +23,7 @@
"eslint": "8.36.0",
"eslint-plugin-compat": "4.1.1",
"eslint-plugin-playwright": "0.12.0",
"eslint-plugin-vue": "9.9.0",
"eslint-plugin-vue": "9.10.0",
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
"file-saver": "2.0.5",
@@ -51,7 +51,7 @@
"nyc": "15.1.0",
"painterro": "1.2.78",
"playwright-core": "1.29.0",
"plotly.js-basic-dist": "2.17.0",
"plotly.js-basic-dist": "2.20.0",
"plotly.js-gl2d-dist": "2.20.0",
"printj": "1.3.1",
"resolve-url-loader": "5.0.0",
@@ -66,7 +66,7 @@
"vue-eslint-parser": "9.1.0",
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.76.2",
"webpack": "5.76.3",
"webpack-cli": "5.0.0",
"webpack-dev-server": "4.11.1",
"webpack-merge": "5.8.0"

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,11 @@ define([
*/
function parseKeyString(keyString) {
if (isIdentifier(keyString)) {
// TODO REMOVE FOR OMM-RELEASE-5.0
if (!keyString.namespace && keyString.key.includes(':')) {
console.error(`smushed key: ${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,15 +21,18 @@
*****************************************************************************/
<template>
<component
:is="urlDefined ? 'a' : 'span'"
<div
ref="conditionWidgetElement"
class="c-condition-widget u-style-receiver js-style-receiver"
:href="url"
>
<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>
@@ -39,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
;
}
},
@@ -68,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: {
@@ -120,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

@@ -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

@@ -23,11 +23,17 @@
import Annotations from './AnnotationsInspectorView.vue';
import Vue from 'vue';
export default function ElementsViewProvider(openmct) {
export default function AnnotationsViewProvider(openmct) {
return {
key: 'annotationsView',
name: 'Annotations',
canView: function (selection) {
const availableTags = openmct.annotation.getAvailableTags();
if (availableTags.length < 1) {
return false;
}
return selection.length;
},
view: function (selection) {

View File

@@ -177,10 +177,9 @@ export default {
if (this.$refs.TagEditor) {
const clickedInsideTagEditor = this.$refs.TagEditor.contains(event.target);
if (!clickedInsideTagEditor) {
// Remove last tag when user clicks outside of TagSelection
this.addedTags.pop();
// Hide TagSelection and show "Add Tag" button
this.userAddingTag = false;
this.tagsChanged();
}
}
},

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

@@ -603,19 +603,20 @@ export default {
const mainYAxisId = this.config.yAxis.get('id');
//There has to be at least one yAxis
const yAxisIds = [mainYAxisId].concat(this.config.additionalYAxes.map(yAxis => yAxis.get('id')));
// Repeat drawing for all yAxes
yAxisIds.forEach((id) => {
if (this.canDraw(id)) {
this.updateViewport(id);
this.drawSeries(id);
this.drawRectangles(id);
this.drawHighlights(id);
// only draw these in fixed time mode or plot is paused
if (this.annotationViewingAndEditingAllowed) {
this.drawAnnotatedPoints(id);
this.drawAnnotationSelections(id);
}
// Repeat drawing for all yAxes
yAxisIds.filter(this.canDraw).forEach((id, yAxisIndex) => {
this.updateViewport(id);
this.drawSeries(id);
if (yAxisIndex === 0) {
this.drawRectangles(id);
}
this.drawHighlights(id);
// only draw these in fixed time mode or plot is paused
if (this.annotationViewingAndEditingAllowed) {
this.drawAnnotatedPoints(id);
this.drawAnnotationSelections(id);
}
});
},

View File

@@ -46,7 +46,6 @@ export default class RemoteClock extends DefaultClock {
this.timeTelemetryObject = undefined;
this.parseTime = undefined;
this.formatTime = undefined;
this.metadata = undefined;
this.lastTick = 0;
@@ -138,10 +137,6 @@ export default class RemoteClock extends DefaultClock {
this.parseTime = (datum) => {
return timeFormatter.parse(datum);
};
this.formatTime = (datum) => {
return timeFormatter.format(datum);
};
}
/**

View File

@@ -29,7 +29,7 @@ define([
'./TelemetryTableColumn',
'./TelemetryTableUnitColumn',
'./TelemetryTableConfiguration',
'@/utils/staleness'
'../../utils/staleness'
], function (
EventEmitter,
_,

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

@@ -38,8 +38,9 @@
import {getValidatedData} from "../plan/util";
import ListView from '../../ui/components/List/ListView.vue';
import {getPreciseDuration} from "../../utils/duration";
import ticker from 'utils/clock/Ticker';
import {SORT_ORDER_OPTIONS} from "./constants";
import _ from 'lodash';
import moment from "moment";
import { v4 as uuid } from 'uuid';
@@ -52,26 +53,16 @@ const headerItems = [
isSortable: true,
property: 'start',
name: 'Start Time',
format: function (value, object, key, openmct) {
const clock = openmct.time.clock();
if (clock && clock.formatTime) {
return clock.formatTime(value);
} else {
return `${moment(value).format(TIME_FORMAT)}Z`;
}
format: function (value, object) {
return `${moment(value).format(TIME_FORMAT)}Z`;
}
}, {
defaultDirection: true,
isSortable: true,
property: 'end',
name: 'End Time',
format: function (value, object, key, openmct) {
const clock = openmct.time.clock();
if (clock && clock.formatTime) {
return clock.formatTime(value);
} else {
return `${moment(value).format(TIME_FORMAT)}Z`;
}
format: function (value, object) {
return `${moment(value).format(TIME_FORMAT)}Z`;
}
}, {
defaultDirection: false,
@@ -128,7 +119,7 @@ export default {
this.unlistenConfig = this.openmct.objects.observe(this.domainObject, 'configuration', this.setViewFromConfig);
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
this.status = this.openmct.status.get(this.domainObject.identifier);
this.unlistenTicker = ticker.listen(this.clearPreviousActivities);
this.openmct.time.on('bounds', this.updateTimestamp);
this.openmct.editor.on('isEditing', this.setEditState);
@@ -153,6 +144,10 @@ export default {
this.unlistenConfig();
}
if (this.unlistenTicker) {
this.unlistenTicker();
}
if (this.removeStatusListener) {
this.removeStatusListener();
}
@@ -197,8 +192,8 @@ export default {
}
},
updateTimestamp(_bounds, isTick) {
if (isTick === true && this.openmct.time.clock() !== undefined) {
this.updateTimeStampAndListActivities(this.openmct.time.clock().currentValue());
if (isTick === true) {
this.timestamp = this.openmct.time.clock().currentValue();
}
},
setViewFromClock(newClock) {
@@ -207,11 +202,12 @@ export default {
if (isFixedTime) {
this.hideAll = false;
this.showAll = true;
this.updateTimeStampAndListActivities(this.openmct.time.bounds()?.start);
// clear invokes listActivities
this.clearPreviousActivities(this.openmct.time.bounds()?.start);
} else {
this.setSort();
this.setViewBounds();
this.updateTimeStampAndListActivities(this.openmct.time.clock().currentValue());
this.listActivities();
}
},
addItem(domainObject) {
@@ -350,8 +346,12 @@ export default {
// sort by start time
this.planActivities = activities.sort(this.sortByStartTime);
},
updateTimeStampAndListActivities(time) {
this.timestamp = time;
clearPreviousActivities(time) {
if (time instanceof Date) {
this.timestamp = time.getTime();
} else {
this.timestamp = time;
}
this.listActivities();
},

View File

@@ -212,7 +212,15 @@ div.c-table {
text-overflow: ellipsis;
}
tbody tr {
&:hover {
background: $colorItemTreeHoverBg;
}
}
td {
user-select: none; // Table supports context-click to display Actions menu, don't allow text selection.
&.is-stale {
@include isStaleElement();
}

View File

@@ -37,7 +37,7 @@ export default {
// eslint-disable-next-line you-dont-need-lodash-underscore/get
let value = _.get(this.item, property.key);
if (property.format) {
value = property.format(value, this.item, property.key, this.openmct);
value = property.format(value, this.item, property.key);
}
values.push({

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;
@@ -286,6 +286,9 @@ export default {
this.openmct.objectViews.on('clearData', this.clearData);
this.$nextTick(() => {
this.updateStyle(this.styleRuleManager?.currentStyle);
this.setFontSize(this.fontSize);
this.setFont(this.font);
this.getActionCollection();
});
},
@@ -328,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) {
@@ -342,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

@@ -93,9 +93,18 @@
:persist-position="true"
>
<RecentObjectsList
ref="recentObjectsList"
class="l-shell__tree"
@openAndScrollTo="openAndScrollTo($event)"
/>
<button
slot="controls"
class="c-icon-button icon-clear-data"
aria-label="Clear Recently Viewed"
title="Clear Recently Viewed"
@click="handleClearRecentObjects"
>
</button>
</pane>
</multipane>
</pane>
@@ -279,6 +288,9 @@ export default {
handleTreeReset() {
this.triggerReset = !this.triggerReset;
},
handleClearRecentObjects() {
this.$refs.recentObjectsList.clearRecentObjects();
},
onStartResizing() {
this.isResizing = true;
},

View File

@@ -191,6 +191,33 @@ export default {
shouldTrackCompositionFor(domainObject, navigationPath) {
return this.compositionCollections[navigationPath] === undefined
&& this.openmct.composition.supportsComposition(domainObject);
},
/**
* Clears the Recent Objects list in localStorage and in the component.
* Before clearing, prompts the user to confirm the action with a dialog.
*/
clearRecentObjects() {
const dialog = this.openmct.overlays.dialog({
title: 'Clear Recently Viewed Objects',
iconClass: 'alert',
message: 'This action will clear the Recently Viewed Objects list. Are you sure you want to continue?',
buttons: [
{
label: 'OK',
callback: () => {
localStorage.removeItem(LOCAL_STORAGE_KEY__RECENT_OBJECTS);
this.recents = [];
dialog.dismiss();
}
},
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
}
}
]
});
}
}
};

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
@@ -355,17 +359,18 @@ export default {
this.abortItemLoad(path);
}
let pathIndex = this.openTreeItems.indexOf(path);
const pathIndex = this.openTreeItems.indexOf(path);
if (pathIndex === -1) {
return;
}
this.treeItems = this.treeItems.filter((checkItem) => {
if (checkItem.navigationPath !== path
&& checkItem.navigationPath.includes(path)) {
this.destroyObserverByPath(checkItem.navigationPath);
this.destroyMutableByPath(checkItem.navigationPath);
this.treeItems = this.treeItems.filter((item) => {
const otherPath = item.navigationPath;
if (otherPath !== path
&& this.isTreeItemAChildOf(otherPath, path)) {
this.destroyObserverByPath(otherPath);
this.destroyMutableByPath(otherPath);
return false;
}
@@ -960,6 +965,24 @@ export default {
isTreeItemPathOpen(path) {
return this.openTreeItems.includes(path);
},
isTreeItemAChildOf(childNavigationPath, parentNavigationPath) {
const childPathKeys = childNavigationPath.split('/');
const parentPathKeys = parentNavigationPath.split('/');
// If child path is shorter than or same length as
// the parent path, then it's not a child.
if (childPathKeys.length <= parentPathKeys.length) {
return false;
}
for (let i = 0; i < parentPathKeys.length; i++) {
if (childPathKeys[i] !== parentPathKeys[i]) {
return false;
}
}
return true;
},
getElementStyleValue(el, style) {
if (!el) {
return;

View File

@@ -49,9 +49,19 @@ export default {
},
computed: {
highlightedText() {
let regex = new RegExp(`(?<!<[^>]*)(${this.highlight})`, 'gi');
const highlight = this.highlight;
return this.text.replace(regex, `<span class="${this.highlightClass}">${this.highlight}</span>`);
const normalCharsRegex = /^[^A-Za-z0-9]+$/g;
const newHighLight = normalCharsRegex.test(highlight)
? `\\${highlight}`
: highlight;
const highlightRegex = new RegExp(`(?<!<[^>]*)(${newHighLight})`, 'gi');
const replacement = `<span class="${this.highlightClass}">${highlight}</span>`;
return this.text.replace(highlightRegex, replacement);
}
}
};