Compare commits

...

15 Commits

Author SHA1 Message Date
7a0041b663 Add static root plugin files 2022-01-28 11:43:15 -06:00
44f5372c31 fix typo when using fallback template (#4784) 2022-01-26 13:34:22 -08:00
2f292fbd07 Follow domain object changes for Independent time conductor (#4783)
* Track if domain object changes when independent time conductor is in use.
2022-01-26 13:04:47 -08:00
205dc67809 Observe changes to sub-objects in flexible layouts. (#4780)
* Get child of flex layout as mutable if possible

* Fix bug when no default notebook defined
2022-01-25 23:22:42 -08:00
169c23dbcc update copyright (#4775) 2022-01-25 12:27:27 -08:00
457cd42987 Update version number (#4759) 2022-01-20 18:57:05 -08:00
45373c56f7 [MCT Tree] Enhance to be used as selection tree as well (#4734)
* removed selector tree, using mct-tree for selctor now, updated style view to use new forms api, update mct-tree to be a selector if need be

* added some extra calculations for height when the tree is being used as a selector in forms

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2022-01-21 02:46:40 +00:00
91e909bb4a Fix notebook sync (#4738)
* Do not keep fetching default notebook

* Fixes issue where notebooks would stop listening for remote changes.

* Fix issue with notebook merge conflicts
2022-01-20 18:43:17 -08:00
556f762d20 Fix CouchDB search duplicates - Mct4667 (#4688)
* added separate search provider
* add unit test
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-01-20 16:00:06 -08:00
e14b7cd0e2 [UserAPI] [UserIndicator Plugin] [ExampleUserProvider Plugin] New user api and related functionality (#4538)
* Implements User API and example user plugin

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-01-20 13:56:17 -08:00
b1cffd0df3 [CI] Dependabot and PR Cop have conflicts (#4756)
This updates the PR to include a "safe word" for pr cop to ignore PRs opened by the dependabot user
2022-01-20 13:25:57 -08:00
09f25bc525 Bump makaroni4/prcop from 1.0.31 to 1.0.35 (#4755)
Bumps [makaroni4/prcop](https://github.com/makaroni4/prcop) from 1.0.31 to 1.0.35.
- [Release notes](https://github.com/makaroni4/prcop/releases)
- [Commits](https://github.com/makaroni4/prcop/compare/v1.0.31...v1.0.35)

---
updated-dependencies:
- dependency-name: makaroni4/prcop
  dependency-type: direct:production
  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>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-01-20 20:56:33 +00:00
bd406d1a73 update playwright dependencies (#4749)
Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-01-20 20:33:01 +00:00
03380e1846 Snapshot tray is sometimes obscured by long notebooks #4746
also fixes one of the issue mentioned in #3529

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2022-01-20 12:05:29 -08:00
dcaad40064 release 1.8.3 - master (#4752)
* Fix object creation (#4675)

* Save the object before adding it to the parent so that transaction committing works properly
* Fix object creation - composition policy changes

* use relative path (#4683)

* Notebook Snapshotting to the default Notebook isn't working (#4475)

* Notebook Snapshotting to the default Notebook isn't working #4469

* Check for ref when trying to get the style receiver. (#4687)

Also ensure that the property being displayed in Properties is actually a part of the domainObject.

* Mct 4555 rebased v3 (#4689)

* Preserve the previousFocusedImage for subscription updates, bound change for local and fixed time

* Only preserve previous focused image if paused

* Forcibly reset imageContainer size to prevent aspect ratio distortion

* Remove unneccesary mixin invocation

* Use image history instead of imagehistory size for watcher. Revert other changes

* Added check if last image index is selected

* isPaused instead of paused

Co-authored-by: Michael Rogers <contact@mhrogers.com>

* Snapshot notice link not navigating as expected #4194 (#4686)

* Fix Link Action to accept the right input for validation (#4696)

* Remove snapshot from release version (#4721)

Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
Co-authored-by: Michael Rogers <contact@mhrogers.com>
2022-01-20 11:09:46 -08:00
56 changed files with 1203 additions and 734 deletions

View File

@ -2,7 +2,7 @@ version: 2.1
executors:
pw-focal-development:
docker:
- image: mcr.microsoft.com/playwright:v1.17.2-focal
- image: mcr.microsoft.com/playwright:v1.18.0-focal
environment:
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
parameters:

View File

@ -25,3 +25,4 @@ updates:
labels:
- "type:maintenance"
- "dependencies"
- "prcop:disable"

View File

@ -17,7 +17,7 @@ jobs:
name: PRcop
steps:
- name: Linting Pull Request
uses: makaroni4/prcop@v1.0.31
uses: makaroni4/prcop@v1.0.35
with:
config-file: ".github/workflows/prcop-config.json"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,110 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import uuid from 'uuid';
import createExampleUser from './exampleUserCreator';
export default class ExampleUserProvider extends EventEmitter {
constructor(openmct) {
super();
this.openmct = openmct;
this.user = undefined;
this.loggedIn = false;
this.autoLoginUser = undefined;
this.ExampleUser = createExampleUser(this.openmct.user.User);
}
isLoggedIn() {
return this.loggedIn;
}
autoLogin(username) {
this.autoLoginUser = username;
}
getCurrentUser() {
if (this.loggedIn) {
return Promise.resolve(this.user);
}
return this._login().then(() => this.user);
}
hasRole(roleId) {
if (!this.loggedIn) {
Promise.resolve(undefined);
}
return Promise.resolve(this.user.getRoles().includes(roleId));
}
_login() {
const id = uuid();
// for testing purposes, this will skip the form, this wouldn't be used in
// a normal authentication process
if (this.autoLoginUser) {
this.user = new this.ExampleUser(id, this.autoLoginUser, ['example-role']);
this.loggedIn = true;
return Promise.resolve();
}
const formStructure = {
title: "Login",
sections: [
{
rows: [
{
key: "username",
control: "textfield",
name: "Username",
pattern: "\\S+",
required: true,
cssClass: "l-input-lg",
value: ''
}
]
}
],
buttons: {
submit: {
label: 'Login'
}
}
};
return this.openmct.forms.showForm(formStructure).then(
(info) => {
this.user = new this.ExampleUser(id, info.username, ['example-role']);
this.loggedIn = true;
},
() => { // user canceled, setting a default username
this.user = new this.ExampleUser(id, 'Pat', ['example-role']);
this.loggedIn = true;
}
);
}
}

View File

@ -0,0 +1,36 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function createExampleUser(UserClass) {
return class ExampleUser extends UserClass {
constructor(id, name, roles) {
super(id, name);
this.roles = roles;
this.getRoles = this.getRoles.bind(this);
}
getRoles() {
return this.roles;
}
};
}

View File

@ -0,0 +1,29 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ExampleUserProvider from './ExampleUserProvider';
export default function ExampleUserPlugin() {
return function install(openmct) {
openmct.user.setProvider(new ExampleUserProvider(openmct));
};
}

View File

@ -0,0 +1,55 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createOpenMct,
resetApplicationState
} from '../../src/utils/testing';
import ExampleUserProvider from './ExampleUserProvider';
describe("The Example User Plugin", () => {
let openmct;
beforeEach(() => {
openmct = createOpenMct();
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('is not installed by default', () => {
expect(openmct.user.hasProvider()).toBeFalse();
});
it('can be installed', () => {
openmct.user.on('providerAdded', (provider) => {
expect(provider).toBeInstanceOf(ExampleUserProvider);
});
openmct.install(openmct.plugins.example.ExampleUser());
});
// The rest of the functionality of the ExampleUser Plugin is
// tested in both the UserAPISpec.js and in the UserIndicatorPlugin spec.
// If that changes, those tests can be moved here.
});

View File

@ -77,12 +77,13 @@
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.example.Generator());
openmct.install(openmct.plugins.example.EventGeneratorPlugin());
openmct.install(openmct.plugins.example.ExampleImagery());
openmct.install(openmct.plugins.Espresso());
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.Generator());
openmct.install(openmct.plugins.EventGeneratorPlugin());
openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.PlanLayout());
openmct.install(openmct.plugins.Timeline());
openmct.install(openmct.plugins.Hyperlink());
@ -194,6 +195,7 @@
));
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
openmct.install(openmct.plugins.Timer());
openmct.install(openmct.plugins.StaticRootPlugin('root', './dist/static-root.json'));
openmct.start();
</script>
</html>

View File

@ -1,12 +1,12 @@
{
"name": "openmct",
"version": "1.8.4-SNAPSHOT",
"version": "1.8.4",
"description": "The Open MCT core platform",
"devDependencies": {
"@braintree/sanitize-url": "^5.0.2",
"@percy/cli": "^1.0.0-beta.71",
"@percy/cli": "^1.0.0-beta.73",
"@percy/playwright": "^1.0.1",
"@playwright/test": "^1.17.2",
"@playwright/test": "^1.18.0",
"allure-playwright": "^2.0.0-beta.14",
"angular": ">=1.8.0",
"angular-route": "1.4.14",
@ -58,7 +58,7 @@
"moment-timezone": "0.5.28",
"node-bourbon": "^4.2.3",
"painterro": "^1.2.56",
"playwright": "^1.17.2",
"playwright": "^1.18.0",
"plotly.js-basic-dist": "^2.5.0",
"plotly.js-gl2d-dist": "^2.5.0",
"printj": "^1.2.1",

View File

@ -212,6 +212,15 @@ define([
*/
this.indicators = new api.IndicatorAPI(this);
/**
* MCT's user awareness management, to enable user and
* role specific functionality.
* @type {module:openmct.UserAPI}
* @memberof module:openmct.MCT#
* @name user
*/
this.user = new api.UserAPI(this);
this.notifications = new api.NotificationAPI();
this.editor = new api.EditorAPI.default(this);
@ -262,6 +271,7 @@ define([
this.install(this.plugins.ObjectInterceptors());
this.install(this.plugins.NonEditableFolder());
this.install(this.plugins.DeviceClassifier());
this.install(this.plugins.UserIndicator());
}
MCT.prototype = Object.create(EventEmitter.prototype);

View File

@ -33,7 +33,8 @@ define([
'./status/StatusAPI',
'./telemetry/TelemetryAPI',
'./time/TimeAPI',
'./types/TypeRegistry'
'./types/TypeRegistry',
'./user/UserAPI'
], function (
ActionsAPI,
CompositionAPI,
@ -47,7 +48,8 @@ define([
StatusAPI,
TelemetryAPI,
TimeAPI,
TypeRegistry
TypeRegistry,
UserAPI
) {
return {
ActionsAPI: ActionsAPI.default,
@ -62,6 +64,7 @@ define([
StatusAPI: StatusAPI.default,
TelemetryAPI: TelemetryAPI,
TimeAPI: TimeAPI.default,
TypeRegistry: TypeRegistry
TypeRegistry: TypeRegistry,
UserAPI: UserAPI.default
};
});

View File

@ -60,13 +60,13 @@
class="c-button c-button--major"
@click="onSave"
>
OK
{{ submitLabel }}
</button>
<button tabindex="0"
class="c-button"
@click="onDismiss"
>
Cancel
{{ cancelLabel }}
</button>
</div>
</div>
@ -105,6 +105,28 @@ export default {
.some(([key, value]) => {
return value;
});
},
submitLabel() {
if (
this.model.buttons
&& this.model.buttons.submit
&& this.model.buttons.submit.label
) {
return this.model.buttons.submit.label;
}
return 'OK';
},
cancelLabel() {
if (
this.model.buttons
&& this.model.buttons.cancel
&& this.model.buttons.cancel.label
) {
return this.model.buttons.submit.label;
}
return 'Cancel';
}
},
mounted() {

View File

@ -21,19 +21,19 @@
*****************************************************************************/
<template>
<SelectorDialogTree :ignore-type-check="true"
:css-class="`form-locator c-form-control--locator`"
:parent="model.parent"
@treeItemSelected="handleItemSelection"
<mct-tree
:is-selector-tree="true"
:initial-selection="model.parent"
@tree-item-selection="handleItemSelection"
/>
</template>
<script>
import SelectorDialogTree from '@/ui/components/SelectorDialogTree.vue';
import MctTree from '@/ui/layout/mct-tree.vue';
export default {
components: {
SelectorDialogTree
MctTree
},
inject: ['openmct'],
props: {
@ -43,10 +43,10 @@ export default {
}
},
methods: {
handleItemSelection({ parentObjectPath }) {
handleItemSelection(item) {
const data = {
model: this.model,
value: parentObjectPath
value: item.objectPath
};
this.$emit('onChange', data);

View File

@ -465,23 +465,6 @@ ObjectAPI.prototype.mutate = function (domainObject, path, value) {
}
};
/**
* Updates a domain object based on its latest persisted state. Note that this will mutate the provided object.
* @param {module:openmct.DomainObject} domainObject an object to refresh from its persistence store
* @returns {Promise} the provided object, updated to reflect the latest persisted state of the object.
*/
ObjectAPI.prototype.refresh = async function (domainObject) {
const refreshedObject = await this.get(domainObject.identifier);
if (domainObject.isMutable) {
domainObject.$refresh(refreshedObject);
} else {
utils.refresh(domainObject, refreshedObject);
}
return domainObject;
};
/**
* @private
*/

39
src/api/user/User.js Normal file
View File

@ -0,0 +1,39 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default class User {
constructor(id, name) {
this.id = id;
this.name = name;
this.getId = this.getId.bind(this);
this.getName = this.getName.bind(this);
}
getId() {
return this.id;
}
getName() {
return this.name;
}
}

137
src/api/user/UserAPI.js Normal file
View File

@ -0,0 +1,137 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import {
MULTIPLE_PROVIDER_ERROR,
NO_PROVIDER_ERROR
} from './constants';
import User from './User';
class UserAPI extends EventEmitter {
constructor(openmct) {
super();
this._openmct = openmct;
this._provider = undefined;
this.User = User;
}
/**
* Set the user provider for the user API. This allows you
* to specifiy ONE user provider to be used with Open MCT.
* @method setProvider
* @memberof module:openmct.UserAPI#
* @param {module:openmct.UserAPI~UserProvider} provider the new
* user provider
*/
setProvider(provider) {
if (this.hasProvider()) {
this._error(MULTIPLE_PROVIDER_ERROR);
}
this._provider = provider;
this.emit('providerAdded', this._provider);
}
/**
* Return true if the user provider has been set.
*
* @memberof module:openmct.UserAPI#
* @returns {boolean} true if the user provider exists
*/
hasProvider() {
return this._provider !== undefined;
}
/**
* If a user provider is set, it will return a copy of a user object from
* the provider. If the user is not logged in, it will return undefined;
*
* @memberof module:openmct.UserAPI#
* @returns {Function|Promise} user provider 'getCurrentUser' method
* @throws Will throw an error if no user provider is set
*/
getCurrentUser() {
this._noProviderCheck();
return this._provider.getCurrentUser();
}
/**
* If a user provider is set, it will return the user provider's
* 'isLoggedIn' method
*
* @memberof module:openmct.UserAPI#
* @returns {Function|Boolean} user provider 'isLoggedIn' method
* @throws Will throw an error if no user provider is set
*/
isLoggedIn() {
if (!this.hasProvider()) {
return false;
}
return this._provider.isLoggedIn();
}
/**
* If a user provider is set, it will return a call to it's
* 'hasRole' method
*
* @memberof module:openmct.UserAPI#
* @returns {Function|Boolean} user provider 'isLoggedIn' method
* @param {string} roleId id of role to check for
* @throws Will throw an error if no user provider is set
*/
hasRole(roleId) {
this._noProviderCheck();
return this._provider.hasRole(roleId);
}
/**
* Checks if a provider is set and if not, will throw error
*
* @private
* @throws Will throw an error if no user provider is set
*/
_noProviderCheck() {
if (!this.hasProvider()) {
this._error(NO_PROVIDER_ERROR);
}
}
/**
* Utility function for throwing errors
*
* @private
* @param {string} error description of error
* @throws Will throw error passed in
*/
_error(error) {
throw new Error(error);
}
}
export default UserAPI;

112
src/api/user/UserAPISpec.js Normal file
View File

@ -0,0 +1,112 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createOpenMct,
resetApplicationState
} from '../../utils/testing';
import {
MULTIPLE_PROVIDER_ERROR
} from './constants';
import ExampleUserProvider from '../../../example/exampleUser/ExampleUserProvider';
const USERNAME = 'Test User';
const EXAMPLE_ROLE = 'example-role';
describe("The User API", () => {
let openmct;
beforeEach(() => {
openmct = createOpenMct();
});
afterEach(() => {
return resetApplicationState(openmct);
});
describe('with regard to user providers', () => {
it('allows you to specify a user provider', () => {
openmct.user.on('providerAdded', (provider) => {
expect(provider).toBeInstanceOf(ExampleUserProvider);
});
openmct.user.setProvider(new ExampleUserProvider(openmct));
});
it('prevents more than one user provider from being set', () => {
openmct.user.setProvider(new ExampleUserProvider(openmct));
expect(() => {
openmct.user.setProvider({});
}).toThrow(new Error(MULTIPLE_PROVIDER_ERROR));
});
it('provides a check for an existing user provider', () => {
expect(openmct.user.hasProvider()).toBeFalse();
openmct.user.setProvider(new ExampleUserProvider(openmct));
expect(openmct.user.hasProvider()).toBeTrue();
});
});
describe('provides the ability', () => {
let provider;
beforeEach(() => {
provider = new ExampleUserProvider(openmct);
provider.autoLogin(USERNAME);
});
it('to check if a user (not specific) is loged in', (done) => {
expect(openmct.user.isLoggedIn()).toBeFalse();
openmct.user.on('providerAdded', () => {
expect(openmct.user.isLoggedIn()).toBeTrue();
done();
});
// this will trigger the user indicator plugin,
// which will in turn login the user
openmct.user.setProvider(provider);
});
it('to get the current user', (done) => {
openmct.user.setProvider(provider);
openmct.user.getCurrentUser().then((apiUser) => {
expect(apiUser.name).toEqual(USERNAME);
}).finally(done);
});
it('to check if a user has a specific role (by id)', (done) => {
openmct.user.setProvider(provider);
let junkIdCheckPromise = openmct.user.hasRole('junk-id').then((hasRole) => {
expect(hasRole).toBeFalse();
});
let realIdCheckPromise = openmct.user.hasRole(EXAMPLE_ROLE).then((hasRole) => {
expect(hasRole).toBeTrue();
});
Promise.all([junkIdCheckPromise, realIdCheckPromise]).finally(done);
});
});
});

24
src/api/user/constants.js Normal file
View File

@ -0,0 +1,24 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
export const MULTIPLE_PROVIDER_ERROR = 'Only one user provider may be set at a time.';
export const NO_PROVIDER_ERROR = 'No user provider has been set.';

View File

@ -39,8 +39,10 @@ export default class ConditionSetViewProvider {
return isConditionSet && this.openmct.router.isNavigatedObject(objectPath);
}
canEdit(domainObject) {
return domainObject.type === 'conditionSet';
canEdit(domainObject, objectPath) {
const isConditionSet = domainObject.type === 'conditionSet';
return isConditionSet && this.openmct.router.isNavigatedObject(objectPath);
}
view(domainObject, objectPath) {

View File

@ -148,10 +148,8 @@ import FontStyleEditor from '@/ui/inspector/styles/FontStyleEditor.vue';
import StyleEditor from "./StyleEditor.vue";
import PreviewAction from "@/ui/preview/PreviewAction.js";
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionSetIdentifierForItem } from "@/plugins/condition/utils/styleUtils";
import SelectorDialogTree from '@/ui/components/SelectorDialogTree.vue';
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
import Vue from 'vue';
const NON_SPECIFIC = '??';
const NON_STYLEABLE_CONTAINER_TYPES = [
@ -551,53 +549,28 @@ export default {
return this.conditions ? this.conditions[id] : {};
},
addConditionSet() {
let conditionSetDomainObject;
let self = this;
function handleItemSelection({ item }) {
if (item) {
conditionSetDomainObject = item;
}
}
const conditionWidgetParent = this.openmct.router.path[1];
const formStructure = {
title: 'Select Condition Set',
sections: [{
name: 'Location',
cssClass: 'grows',
rows: [{
key: 'location',
name: 'Condition Set',
cssClass: 'grows',
control: 'locator',
required: true,
parent: conditionWidgetParent,
validate: data => data.value[0].type === 'conditionSet'
}]
}]
};
function dismissDialog(overlay, initialize) {
overlay.dismiss();
if (initialize && conditionSetDomainObject) {
self.conditionSetDomainObject = conditionSetDomainObject;
self.conditionalStyles = [];
self.initializeConditionalStyles();
}
}
let vm = new Vue({
components: { SelectorDialogTree },
provide: {
openmct: this.openmct
},
data() {
return {
handleItemSelection,
title: 'Select Condition Set'
};
},
template: '<SelectorDialogTree :title="title" @treeItemSelected="handleItemSelection"></SelectorDialogTree>'
}).$mount();
let overlay = this.openmct.overlays.overlay({
element: vm.$el,
size: 'small',
buttons: [
{
label: 'OK',
emphasis: 'true',
callback: () => dismissDialog(overlay, true)
},
{
label: 'Cancel',
callback: () => dismissDialog(overlay, false)
}
],
onDestroy: () => vm.$destroy()
this.openmct.forms.showForm(formStructure).then(data => {
this.conditionSetDomainObject = data.location[0];
this.conditionalStyles = [];
this.initializeConditionalStyles();
});
},
removeConditionSet() {

View File

@ -97,7 +97,14 @@ export default {
},
mounted() {
if (this.frame.domainObjectIdentifier) {
this.openmct.objects.get(this.frame.domainObjectIdentifier).then((object) => {
let domainObjectPromise;
if (this.openmct.objects.supportsMutation(this.frame.domainObjectIdentifier)) {
domainObjectPromise = this.openmct.objects.getMutable(this.frame.domainObjectIdentifier);
} else {
domainObjectPromise = this.openmct.objects.get(this.frame.domainObjectIdentifier);
}
domainObjectPromise.then((object) => {
this.setDomainObject(object);
});
}
@ -105,6 +112,10 @@ export default {
this.dragGhost = document.getElementById('js-fl-drag-ghost');
},
beforeDestroy() {
if (this.domainObject.isMutable) {
this.openmct.objects.destroyMutable(this.domainObject);
}
if (this.unsubscribeSelection) {
this.unsubscribeSelection();
}

View File

@ -99,6 +99,7 @@ export default class CreateAction extends PropertiesAction {
*/
async _navigateAndEdit(domainObject, parentDomainObjectpath) {
let objectPath;
let self = this;
if (parentDomainObjectpath) {
objectPath = parentDomainObjectpath && [domainObject].concat(parentDomainObjectpath);
} else {
@ -110,13 +111,18 @@ export default class CreateAction extends PropertiesAction {
.reverse()
.join('/');
this.openmct.router.navigate(url);
function editObject() {
const objectView = self.openmct.objectViews.get(domainObject, objectPath)[0];
const canEdit = objectView && objectView.canEdit && objectView.canEdit(domainObject, objectPath);
const objectView = this.openmct.objectViews.get(domainObject, objectPath)[0];
const canEdit = objectView && objectView.canEdit && objectView.canEdit(domainObject, objectPath);
if (canEdit) {
this.openmct.editor.edit();
if (canEdit) {
self.openmct.editor.edit();
}
}
this.openmct.router.once('afterNavigation', editObject);
this.openmct.router.navigate(url);
}
/**

View File

@ -65,13 +65,8 @@ export default {
keyString: undefined
};
},
computed: {
imageHistorySize() {
return this.imageHistory.length;
}
},
watch: {
imageHistorySize(newSize, oldSize) {
imageHistory(newHistory, oldHistory) {
this.updatePlotImagery();
}
},

View File

@ -240,9 +240,6 @@ export default {
};
},
computed: {
imageHistorySize() {
return this.imageHistory.length;
},
compassRoseSizingClasses() {
let compassRoseSizingClasses = '';
if (this.sizedImageDimensions.width < 300) {
@ -409,19 +406,23 @@ export default {
}
},
watch: {
imageHistorySize(newSize, oldSize) {
let imageIndex;
if (this.focusedImageTimestamp !== undefined) {
const foundImageIndex = this.imageHistory.findIndex(image => {
return image.time === this.focusedImageTimestamp;
});
imageIndex = foundImageIndex > -1 ? foundImageIndex : newSize - 1;
} else {
imageIndex = newSize > 0 ? newSize - 1 : undefined;
}
imageHistory: {
handler(newHistory, oldHistory) {
const newSize = newHistory.length;
let imageIndex;
if (this.focusedImageTimestamp !== undefined) {
const foundImageIndex = this.imageHistory.findIndex(image => {
return image.time === this.focusedImageTimestamp;
});
imageIndex = foundImageIndex > -1 ? foundImageIndex : newSize - 1;
} else {
imageIndex = newSize > 0 ? newSize - 1 : undefined;
}
this.setFocusedImage(imageIndex, false);
this.scrollToRight();
this.setFocusedImage(imageIndex, false);
this.scrollToRight();
},
deep: true
},
focusedImageIndex() {
this.trackDuration();
@ -510,12 +511,6 @@ export default {
this.timeContext.off("clock", this.trackDuration);
}
},
boundsChange(bounds, isTick) {
if (!isTick) {
this.previousFocusedImage = this.focusedImage ? JSON.parse(JSON.stringify(this.focusedImage)) : undefined;
this.requestHistory();
}
},
expand() {
const actionCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
const visibleActions = actionCollection.getVisibleActions();
@ -690,22 +685,32 @@ export default {
return;
}
if (this.previousFocusedImage) {
// determine if the previous image exists in the new bounds of imageHistory
const matchIndex = this.matchIndexOfPreviousImage(
this.previousFocusedImage,
this.imageHistory
);
focusedIndex = matchIndex > -1 ? matchIndex : this.imageHistory.length - 1;
delete this.previousFocusedImage;
}
if (thumbnailClick) {
//We use the props till the user changes what they want to see
this.focusedImageTimestamp = undefined;
//set the previousFocusedImage when a user chooses an image
this.previousFocusedImage = this.imageHistory[focusedIndex] ? JSON.parse(JSON.stringify(this.imageHistory[focusedIndex])) : undefined;
}
if (this.previousFocusedImage) {
// determine if the previous image exists in the new bounds of imageHistory
if (!thumbnailClick) {
const matchIndex = this.matchIndexOfPreviousImage(
this.previousFocusedImage,
this.imageHistory
);
focusedIndex = matchIndex > -1 ? matchIndex : this.imageHistory.length - 1;
}
if (!(this.isPaused || thumbnailClick)
|| focusedIndex === this.imageHistory.length - 1) {
delete this.previousFocusedImage;
}
}
this.focusedImageIndex = focusedIndex;
//TODO: do we even need this anymore?
if (this.isPaused && !thumbnailClick && this.focusedImageTimestamp === undefined) {
this.nextImageIndex = focusedIndex;
//this could happen if bounds changes
@ -716,8 +721,6 @@ export default {
return;
}
this.focusedImageIndex = focusedIndex;
if (thumbnailClick && !this.isPaused) {
this.paused(true);
}

View File

@ -120,9 +120,15 @@ export default {
return this.timeFormatter.parse(datum);
},
boundsChange(bounds, isTick) {
if (!isTick) {
this.requestHistory();
if (isTick) {
return;
}
// forcibly reset the imageContainer size to prevent an aspect ratio distortion
delete this.imageContainerWidth;
delete this.imageContainerHeight;
return this.requestHistory();
},
async requestHistory() {
let bounds = this.timeContext.bounds();

View File

@ -91,11 +91,11 @@ export default class LinkAction {
}
validate(currentParent) {
return (object, data) => {
const parentCandidate = data.value;
return (data) => {
const parentCandidate = data.value[0];
const currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
const parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
const objectKeystring = this.openmct.objects.makeKeyString(object.identifier);
const objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
if (!parentCandidateKeystring || !currentParentKeystring) {
return false;
@ -114,7 +114,7 @@ export default class LinkAction {
return false;
}
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, object);
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, this.object);
};
}
}

View File

@ -142,7 +142,6 @@ import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaul
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
import { saveNotebookImageDomainObject, updateNamespaceOfDomainObject } from '../utils/notebook-image';
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
import objectUtils from 'objectUtils';
import { debounce } from 'lodash';
import objectLink from '../../../ui/mixins/object-link';
@ -323,7 +322,7 @@ export default {
cleanupDefaultNotebook() {
this.defaultPageId = undefined;
this.defaultSectionId = undefined;
this.removeDefaultClass(this.domainObject);
this.removeDefaultClass(this.domainObject.identifier);
clearDefaultNotebook();
},
setSectionAndPageFromUrl() {
@ -461,11 +460,6 @@ export default {
? getDefaultNotebook().defaultSectionId
: undefined;
},
getDefaultNotebookObject() {
const defaultNotebook = getDefaultNotebook();
return defaultNotebook && this.openmct.objects.get(defaultNotebook.identifier);
},
getLinktoNotebook() {
const objectPath = this.openmct.router.path;
const link = objectLink.computed.objectLink.call({
@ -625,12 +619,8 @@ export default {
this.sectionsChanged({ sections });
},
removeDefaultClass(domainObject) {
if (!domainObject) {
return;
}
this.openmct.status.delete(domainObject.identifier);
removeDefaultClass(identifier) {
this.openmct.status.delete(identifier);
},
resetSearch() {
this.search = '';
@ -639,26 +629,25 @@ export default {
toggleNav() {
this.showNav = !this.showNav;
},
async updateDefaultNotebook(notebookStorage) {
const defaultNotebookObject = await this.getDefaultNotebookObject();
const isSameNotebook = defaultNotebookObject
&& objectUtils.makeKeyString(defaultNotebookObject.identifier) === objectUtils.makeKeyString(notebookStorage.identifier);
if (!isSameNotebook) {
this.removeDefaultClass(defaultNotebookObject);
updateDefaultNotebook(updatedNotebookStorageObject) {
if (!this.isDefaultNotebook()) {
const persistedNotebookStorageObject = getDefaultNotebook();
if (persistedNotebookStorageObject
&& persistedNotebookStorageObject.identifier !== undefined) {
this.removeDefaultClass(persistedNotebookStorageObject.identifier);
}
setDefaultNotebook(this.openmct, updatedNotebookStorageObject, this.domainObject);
}
if (!defaultNotebookObject || !isSameNotebook) {
setDefaultNotebook(this.openmct, notebookStorage, this.domainObject);
if (this.defaultSectionId !== updatedNotebookStorageObject.defaultSectionId) {
setDefaultNotebookSectionId(updatedNotebookStorageObject.defaultSectionId);
this.defaultSectionId = updatedNotebookStorageObject.defaultSectionId;
}
if (this.defaultSectionId !== notebookStorage.defaultSectionId) {
setDefaultNotebookSectionId(notebookStorage.defaultSectionId);
this.defaultSectionId = notebookStorage.defaultSectionId;
}
if (this.defaultPageId !== notebookStorage.defaultPageId) {
setDefaultNotebookPageId(notebookStorage.defaultPageId);
this.defaultPageId = notebookStorage.defaultPageId;
if (this.defaultPageId !== updatedNotebookStorageObject.defaultPageId) {
setDefaultNotebookPageId(updatedNotebookStorageObject.defaultPageId);
this.defaultPageId = updatedNotebookStorageObject.defaultPageId;
}
},
updateDefaultNotebookSection(sections, id) {
@ -676,7 +665,7 @@ export default {
if (defaultNotebookSectionId === id) {
const section = sections.find(s => s.id === id);
if (!section) {
this.removeDefaultClass(this.domainObject);
this.removeDefaultClass(this.domainObject.identifier);
clearDefaultNotebook();
return;

View File

@ -8,6 +8,7 @@ export default function (openmct) {
return apiSave(domainObject);
}
const isNewMutable = !domainObject.isMutable;
const localMutable = openmct.objects._toMutable(domainObject);
let result;
@ -20,7 +21,9 @@ export default function (openmct) {
result = Promise.reject(error);
}
} finally {
openmct.objects.destroyMutable(localMutable);
if (isNewMutable) {
openmct.objects.destroyMutable(localMutable);
}
}
return result;
@ -28,10 +31,10 @@ export default function (openmct) {
}
function resolveConflicts(localMutable, openmct) {
const localEntries = JSON.parse(JSON.stringify(localMutable.configuration.entries));
return openmct.objects.getMutable(localMutable.identifier).then((remoteMutable) => {
const localEntries = localMutable.configuration.entries;
remoteMutable.$refresh(remoteMutable);
applyLocalEntries(remoteMutable, localEntries);
applyLocalEntries(remoteMutable, localEntries, openmct);
openmct.objects.destroyMutable(remoteMutable);
@ -39,7 +42,7 @@ function resolveConflicts(localMutable, openmct) {
});
}
function applyLocalEntries(mutable, entries) {
function applyLocalEntries(mutable, entries, openmct) {
Object.entries(entries).forEach(([sectionKey, pagesInSection]) => {
Object.entries(pagesInSection).forEach(([pageKey, localEntries]) => {
const remoteEntries = mutable.configuration.entries[sectionKey][pageKey];
@ -58,14 +61,15 @@ function applyLocalEntries(mutable, entries) {
locallyModifiedEntries.forEach((locallyModifiedEntry) => {
let mergedEntry = mergedEntries.find(entry => entry.id === locallyModifiedEntry.id);
if (mergedEntry !== undefined) {
if (mergedEntry !== undefined
&& locallyModifiedEntry.text.match(/\S/)) {
mergedEntry.text = locallyModifiedEntry.text;
shouldMutate = true;
}
});
if (shouldMutate) {
mutable.$set(`configuration.entries.${sectionKey}.${pageKey}`, mergedEntries);
openmct.objects.mutate(mutable, `configuration.entries.${sectionKey}.${pageKey}`, mergedEntries);
}
});
});

View File

@ -110,7 +110,8 @@ export default class Snapshot {
}
return () => {
window.location.href = window.location.origin + url;
const path = window.location.href.split('#');
window.location.href = path[0] + url;
};
}
}

View File

@ -105,11 +105,6 @@ export function addNotebookEntry(openmct, domainObject, notebookStorage, embed =
const date = Date.now();
const configuration = domainObject.configuration;
const entries = configuration.entries || {};
if (!entries) {
return;
}
const embeds = embed
? [embed]
: [];
@ -125,7 +120,7 @@ export function addNotebookEntry(openmct, domainObject, notebookStorage, embed =
const newEntries = addEntryIntoPage(notebookStorage, entries, entry);
addDefaultClass(domainObject, openmct);
domainObject.configuration.entries = newEntries;
mutateObject(openmct, domainObject, 'configuration.entries', newEntries);
return id;
}

View File

@ -298,18 +298,12 @@ class CouchObjectProvider {
return Array.from(new Set(array));
}
search(query, abortSignal) {
const filter = {
"selector": {
"model": {
"name": {
"$regex": `(?i)${query}`
}
}
}
};
return this.getObjectsByFilter(filter, abortSignal);
search() {
// Dummy search function. It has to appear to support search,
// otherwise the in-memory indexer will index all of its objects,
// but actually search results will be provided by a separate search provider
// see CoucheSearchProvider.js
return Promise.resolve([]);
}
async getObjectsByFilter(filter, abortSignal) {

View File

@ -0,0 +1,49 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
// This provider exists because due to legacy reasons, we need to install
// two plugins for two namespaces for CouchDB: one for "mct", and one for "".
// Because of this, we need to separate out the search provider from the object
// provider so we don't return two results for each found object.
// If the above namespace is ever resolved, we can fold this search provider
// back into the object provider.
class CouchSearchProvider {
constructor(couchObjectProvider) {
this.couchObjectProvider = couchObjectProvider;
}
search(query, abortSignal) {
const filter = {
"selector": {
"model": {
"name": {
"$regex": `(?i)${query}`
}
}
}
};
return this.couchObjectProvider.getObjectsByFilter(filter, abortSignal);
}
}
export default CouchSearchProvider;

View File

@ -21,8 +21,10 @@
*****************************************************************************/
import CouchObjectProvider from './CouchObjectProvider';
import CouchSearchProvider from './CouchSearchProvider';
const NAMESPACE = '';
const LEGACY_SPACE = 'mct';
const COUCH_SEARCH_ONLY_NAMESPACE = `COUCH_SEARCH_${Date.now()}`;
export default function CouchPlugin(options) {
return function install(openmct) {
@ -32,5 +34,6 @@ export default function CouchPlugin(options) {
// Installing the same provider under both namespaces means that it can respond to object gets for both namespaces.
openmct.objects.addProvider(LEGACY_SPACE, install.couchProvider);
openmct.objects.addProvider(NAMESPACE, install.couchProvider);
openmct.objects.addProvider(COUCH_SEARCH_ONLY_NAMESPACE, new CouchSearchProvider(install.couchProvider));
};
}

View File

@ -262,7 +262,9 @@ describe('the plugin', () => {
await Promise.all(openmct.objects.search('test'));
const requestUrl = fetch.calls.mostRecent().args[0];
expect(fetch).toHaveBeenCalled();
// we only want one call to fetch, not 2!
// see https://github.com/nasa/openmct/issues/4667
expect(fetch).toHaveBeenCalledTimes(1);
expect(requestUrl.endsWith('_find')).toBeTrue();
});

View File

@ -278,7 +278,7 @@ 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.TEMPLATE;
div.innerHTML = this.canvasTemplate + this.canvasTemplate;
const mainCanvas = div.querySelectorAll("canvas")[1];
const overlayCanvas = div.querySelectorAll("canvas")[0];
this.canvas.parentNode.replaceChild(mainCanvas, this.canvas);

View File

@ -75,6 +75,8 @@ define([
'./clock/plugin',
'./DeviceClassifier/plugin',
'./timer/plugin',
'./userIndicator/plugin',
'../../example/exampleUser/plugin',
'./localStorage/plugin',
'./legacySupport/plugin.js',
'../adapter/indicators/legacy-indicators-plugin'
@ -133,6 +135,8 @@ define([
Clock,
DeviceClassifier,
Timer,
UserIndicator,
ExampleUser,
LocalStorage,
LegacySupportPlugin,
LegacyIndicatorsPlugin
@ -149,6 +153,12 @@ define([
};
});
plugins.example = {};
plugins.example.ExampleUser = ExampleUser.default;
plugins.example.ExampleImagery = ExampleImagery.default;
plugins.example.EventGeneratorPlugin = EventGeneratorPlugin.default;
plugins.example.Generator = () => GeneratorPlugin;
plugins.UTCTimeSystem = UTCTimeSystem.default;
plugins.LocalTimeSystem = LocalTimeSystem;
plugins.RemoteClock = RemoteClock.default;
@ -194,12 +204,6 @@ define([
};
};
plugins.Generator = function () {
return GeneratorPlugin;
};
plugins.EventGeneratorPlugin = EventGeneratorPlugin.default;
plugins.ExampleImagery = ExampleImagery.default;
plugins.ImageryPlugin = ImageryPlugin;
plugins.Plot = PlotPlugin.default;
plugins.Chart = ChartPlugin.default;
@ -243,6 +247,7 @@ define([
plugins.Clock = Clock.default;
plugins.Timer = Timer.default;
plugins.DeviceClassifier = DeviceClassifier.default;
plugins.UserIndicator = UserIndicator.default;
plugins.LocalStorage = LocalStorage.default;
plugins.LegacySupport = LegacySupportPlugin.default;
plugins.LegacyIndicators = LegacyIndicatorsPlugin;

View File

@ -32,6 +32,8 @@ define([
}
});
console.log(JSON.parse(objectString));
return JSON.parse(objectString);
}

View File

@ -29,7 +29,9 @@ define(
}
SummaryWidgetsCompositionPolicy.prototype.allow = function (parent, child) {
if (parent.type === 'summary-widget' && !this.openmct.telemetry.isTelemetryObject(child)) {
const parentType = parent.type;
if (parentType === 'summary-widget' && !this.openmct.telemetry.isTelemetryObject(child)) {
return false;
}

View File

@ -95,6 +95,11 @@ export default {
isUTCBased: timeSystem.isUTCBased
};
},
watch: {
keyString() {
this.setTimeContext();
}
},
mounted() {
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));

View File

@ -115,6 +115,11 @@ export default {
isUTCBased: timeSystem.isUTCBased
};
},
watch: {
keyString() {
this.setTimeContext();
}
},
mounted() {
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));

View File

@ -105,42 +105,49 @@ export default {
watch: {
domainObject: {
handler(domainObject) {
this.independentTCEnabled = domainObject.configuration.useIndependentTime === true;
if (!domainObject.configuration.timeOptions || !this.independentTCEnabled) {
const key = this.openmct.objects.makeKeyString(domainObject.identifier);
if (key !== this.keyString) {
//domain object has changed
this.destroyIndependentTime();
return;
}
this.independentTCEnabled = domainObject.configuration.useIndependentTime === true;
this.timeOptions = domainObject.configuration.timeOptions || {
clockOffsets: this.openmct.time.clockOffsets(),
fixedOffsets: this.openmct.time.bounds()
};
if (this.timeOptions.start !== domainObject.configuration.timeOptions.start
|| this.timeOptions.end !== domainObject.configuration.timeOptions.end) {
this.timeOptions = domainObject.configuration.timeOptions;
this.destroyIndependentTime();
this.registerIndependentTimeOffsets();
this.initialize();
}
},
deep: true
}
},
mounted() {
this.setTimeContext();
if (this.timeOptions.mode) {
this.mode = this.timeOptions.mode;
} else {
this.timeOptions.mode = this.mode = this.timeContext.clock() === undefined ? { key: 'fixed' } : { key: Object.create(this.timeContext.clock()).key};
}
if (this.independentTCEnabled) {
this.registerIndependentTimeOffsets();
}
this.initialize();
},
beforeDestroy() {
this.stopFollowingTimeContext();
this.destroyIndependentTime();
},
methods: {
initialize() {
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.setTimeContext();
if (this.timeOptions.mode) {
this.mode = this.timeOptions.mode;
} else {
if (this.timeContext.clock() === undefined) {
this.timeOptions.mode = this.mode = { key: 'fixed' };
} else {
this.timeOptions.mode = this.mode = { key: Object.create(this.timeContext.clock()).key};
}
}
if (this.independentTCEnabled) {
this.registerIndependentTimeOffsets();
}
},
toggleIndependentTC() {
this.independentTCEnabled = !this.independentTCEnabled;
if (this.independentTCEnabled) {
@ -213,10 +220,9 @@ export default {
offsets = this.timeOptions.clockOffsets;
}
const key = this.openmct.objects.makeKeyString(this.domainObject.identifier);
const timeContext = this.openmct.time.getIndependentContext(key);
const timeContext = this.openmct.time.getIndependentContext(this.keyString);
if (!timeContext.hasOwnContext()) {
this.unregisterIndependentTime = this.openmct.time.addIndependentContext(key, offsets, this.isFixed ? undefined : this.mode.key);
this.unregisterIndependentTime = this.openmct.time.addIndependentContext(this.keyString, offsets, this.isFixed ? undefined : this.mode.key);
} else {
if (this.isFixed) {
timeContext.stopClock();

View File

@ -0,0 +1,54 @@
<!--
Open MCT, Copyright (c) 2014-2022, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div class="c-indicator icon-person c-indicator--clickable">
<span class="label c-indicator__label">
{{ userName }}
</span>
</div>
</template>
<script>
export default {
inject: ['openmct'],
data() {
return {
userName: undefined,
loggedIn: false
};
},
mounted() {
this.getUserInfo();
},
methods: {
getUserInfo() {
this.openmct.user.getCurrentUser().then((user) => {
this.userName = user.getName();
this.loggedIn = this.openmct.user.isLoggedIn();
});
}
}
};
</script>

View File

@ -0,0 +1,56 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import UserIndicator from './components/UserIndicator.vue';
import Vue from 'vue';
export default function UserIndicatorPlugin() {
function addIndicator(openmct) {
const userIndicator = new Vue ({
components: {
UserIndicator
},
provide: {
openmct: openmct
},
template: '<UserIndicator />'
});
openmct.indicators.add({
key: 'user-indicator',
element: userIndicator.$mount().$el,
priority: openmct.priority.HIGH
});
}
return function install(openmct) {
if (openmct.user.hasProvider()) {
addIndicator(openmct);
} else {
// back up if user provider added after indicator installed
openmct.user.on('providerAdded', () => {
addIndicator(openmct);
});
}
};
}

View File

@ -0,0 +1,100 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* 'License'); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
import Vue from 'vue';
import ExampleUserProvider from '../../../example/exampleUser/ExampleUserProvider';
const USERNAME = 'Coach McGuirk';
describe('The User Indicator plugin', () => {
let openmct;
let element;
let child;
let appHolder;
let userIndicator;
let provider;
beforeEach((done) => {
appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
document.body.appendChild(appHolder);
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct = createOpenMct();
openmct.on('start', done);
openmct.start(appHolder);
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('will not show, if there is no user provider', () => {
userIndicator = openmct.indicators.indicatorObjects
.find(indicator => indicator.key === 'user-indicator');
expect(userIndicator).toBe(undefined);
});
describe('with a user provider installed', () => {
beforeEach(() => {
provider = new ExampleUserProvider(openmct);
provider.autoLogin(USERNAME);
openmct.user.setProvider(provider);
return Vue.nextTick();
});
it('exists', () => {
userIndicator = openmct.indicators.indicatorObjects
.find(indicator => indicator.key === 'user-indicator').element;
const hasClockIndicator = userIndicator !== null && userIndicator !== undefined;
expect(hasClockIndicator).toBe(true);
});
it('contains the logged in user name', (done) => {
openmct.user.getCurrentUser().then(async (user) => {
await Vue.nextTick();
userIndicator = openmct.indicators.indicatorObjects
.find(indicator => indicator.key === 'user-indicator').element;
const userName = userIndicator.textContent.trim();
expect(user.name).toEqual(USERNAME);
expect(userName).toContain(USERNAME);
}).finally(done);
});
});
});

View File

@ -273,6 +273,7 @@ input, textarea {
input[type=text],
input[type=search],
input[type=number],
input[type=password],
input[type=date],
textarea {
@include reactive-input();

View File

@ -152,11 +152,15 @@ export default {
this.openmct.objectViews.off('clearData', this.clearData);
},
getStyleReceiver() {
let styleReceiver = this.$refs.objectViewWrapper.querySelector('.js-style-receiver')
|| this.$refs.objectViewWrapper.querySelector(':first-child');
let styleReceiver;
if (styleReceiver === null) {
styleReceiver = undefined;
if (this.$refs.objectViewWrapper !== undefined) {
styleReceiver = this.$refs.objectViewWrapper.querySelector('.js-style-receiver')
|| this.$refs.objectViewWrapper.querySelector(':first-child');
if (styleReceiver === null) {
styleReceiver = undefined;
}
}
return styleReceiver;

View File

@ -1,240 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="u-contents">
<div v-if="title.length"
class="c-overlay__top-bar"
>
<div class="c-overlay__dialog-title">{{ title }}</div>
</div>
<div class="c-selector c-tree-and-search"
:class="cssClass"
>
<div class="c-tree-and-search__search">
<Search ref="shell-search"
class="c-search"
:value="searchValue"
@input="searchTree"
@clear="searchTree"
/>
</div>
<div v-if="isLoading"
class="c-tree-and-search__loading loading"
></div>
<div v-if="shouldDisplayNoResultsText"
class="c-tree-and-search__no-results"
>
No results found
</div>
<ul v-if="!isLoading"
v-show="!searchValue"
class="c-tree-and-search__tree c-tree"
>
<SelectorDialogTreeItem
v-for="treeItem in allTreeItems"
:key="treeItem.id"
:node="treeItem"
:selected-item="selectedItem"
:handle-item-selected="handleItemSelection"
:navigate-to-parent="navigateToParent"
/>
</ul>
<ul v-if="searchValue && !isLoading"
class="c-tree-and-search__tree c-tree"
>
<SelectorDialogTreeItem
v-for="treeItem in filteredTreeItems"
:key="treeItem.id"
:node="treeItem"
:selected-item="selectedItem"
:handle-item-selected="handleItemSelection"
/>
</ul>
</div>
</div>
</template>
<script>
import debounce from 'lodash/debounce';
import Search from '@/ui/components/search.vue';
import SelectorDialogTreeItem from './SelectorDialogTreeItem.vue';
export default {
name: 'SelectorDialogTree',
components: {
Search,
SelectorDialogTreeItem
},
inject: ['openmct'],
props: {
cssClass: {
type: String,
required: false,
default() {
return '';
}
},
title: {
type: String,
required: false,
default() {
return '';
}
},
ignoreTypeCheck: {
type: Boolean,
required: false,
default() {
return false;
}
},
parent: {
type: Object,
required: false,
default() {
return undefined;
}
}
},
data() {
return {
allTreeItems: [],
expanded: false,
filteredTreeItems: [],
isLoading: false,
navigateToParent: undefined,
searchValue: '',
selectedItem: undefined
};
},
computed: {
shouldDisplayNoResultsText() {
if (this.isLoading) {
return false;
}
return this.allTreeItems.length === 0
|| (this.searchValue && this.filteredTreeItems.length === 0);
}
},
created() {
this.getDebouncedFilteredChildren = debounce(this.getFilteredChildren, 400);
},
mounted() {
if (this.parent) {
(async () => {
const objectPath = await this.openmct.objects.getOriginalPath(this.parent.identifier);
this.navigateToParent = '/browse/'
+ objectPath
.map(parent => this.openmct.objects.makeKeyString(parent.identifier))
.reverse()
.join('/');
this.getAllChildren(this.navigateToParent);
})();
} else {
this.getAllChildren();
}
},
methods: {
async aggregateFilteredChildren(results) {
for (const object of results) {
const objectPath = await this.openmct.objects.getOriginalPath(object.identifier);
const navigateToParent = '/browse/'
+ objectPath.slice(1)
.map(parent => this.openmct.objects.makeKeyString(parent.identifier))
.join('/');
const filteredChild = {
id: this.openmct.objects.makeKeyString(object.identifier),
object,
objectPath,
navigateToParent
};
this.filteredTreeItems.push(filteredChild);
}
},
getAllChildren(navigateToParent) {
this.isLoading = true;
this.openmct.objects.get('ROOT')
.then(root => {
return this.openmct.composition.get(root).load();
})
.then(children => {
this.isLoading = false;
this.allTreeItems = children.map(c => {
return {
id: this.openmct.objects.makeKeyString(c.identifier),
object: c,
objectPath: [c],
navigateToParent: navigateToParent || '/browse'
};
});
});
},
getFilteredChildren() {
// clear any previous search results
this.filteredTreeItems = [];
const promises = this.openmct.objects.search(this.searchValue)
.map(promise => promise
.then(results => this.aggregateFilteredChildren(results)));
Promise.all(promises).then(() => {
this.isLoading = false;
});
},
handleItemSelection(item, node) {
if (item && (this.ignoreTypeCheck || item.type === 'conditionSet')) {
const parentId = (node.objectPath && node.objectPath.length > 1) ? node.objectPath[1].identifier : undefined;
this.selectedItem = {
itemId: item.identifier,
parentId
};
this.$emit('treeItemSelected',
{
item,
parentObjectPath: node.objectPath
});
}
},
searchTree(value) {
this.searchValue = value;
this.isLoading = true;
if (this.searchValue !== '') {
this.getDebouncedFilteredChildren();
} else {
this.isLoading = false;
}
}
}
};
</script>

View File

@ -1,206 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<li class="c-tree__item-h">
<div
class="c-tree__item"
:class="{ 'is-alias': isAlias, 'is-navigated-object': navigated }"
@click="handleItemSelected(node.object, node)"
>
<view-control
v-model="expanded"
class="c-tree__item__view-control"
:enabled="hasChildren"
/>
<div class="c-tree__item__label c-object-label">
<div
class="c-tree__item__type-icon c-object-label__type-icon"
:class="typeClass"
></div>
<div class="c-tree__item__name c-object-label__name">{{ node.object.name }}</div>
</div>
</div>
<ul
v-if="expanded && !isLoading"
class="c-tree"
>
<li
v-if="isLoading && !loaded"
class="c-tree__item-h"
>
<div class="c-tree__item loading">
<span class="c-tree__item__label">Loading...</span>
</div>
</li>
<SelectorDialogTreeItem
v-for="child in children"
:key="child.id"
:node="child"
:selected-item="selectedItem"
:handle-item-selected="handleItemSelected"
:navigate-to-parent="navigateToParent"
/>
</ul>
</li>
</template>
<script>
import viewControl from '@/ui/components/viewControl.vue';
export default {
name: 'SelectorDialogTreeItem',
components: {
viewControl
},
inject: ['openmct'],
props: {
node: {
type: Object,
required: true
},
selectedItem: {
type: Object,
default() {
return undefined;
}
},
handleItemSelected: {
type: Function,
default() {
return (item) => {};
}
},
navigateToParent: {
type: String,
default() {
return undefined;
}
}
},
data() {
return {
hasChildren: false,
isLoading: false,
loaded: false,
children: [],
expanded: false
};
},
computed: {
navigated() {
const itemId = this.selectedItem && this.selectedItem.itemId;
const isSelectedObject = itemId && this.openmct.objects.areIdsEqual(this.node.object.identifier, itemId);
if (isSelectedObject && this.node.objectPath && this.node.objectPath.length > 1) {
const isParent = this.openmct.objects.areIdsEqual(this.node.objectPath[1].identifier, this.selectedItem.parentId);
return isSelectedObject && isParent;
}
return isSelectedObject;
},
isAlias() {
let parent = this.node.objectPath[1];
if (!parent) {
return false;
}
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
return parentKeyString !== this.node.object.location;
},
typeClass() {
let type = this.openmct.types.get(this.node.object.type);
if (!type) {
return 'icon-object-unknown';
}
return type.definition.cssClass;
}
},
watch: {
expanded() {
if (!this.hasChildren) {
return;
}
if (!this.loaded && !this.isLoading) {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);
this.composition.load().then(this.finishLoading);
this.isLoading = true;
}
}
},
mounted() {
this.domainObject = this.node.object;
if (this.navigateToParent && this.navigateToParent.includes(this.openmct.objects.makeKeyString(this.domainObject.identifier))) {
this.expanded = true;
}
if (this.navigateToParent && this.navigateToParent.endsWith(this.openmct.objects.makeKeyString(this.domainObject.identifier))) {
this.handleItemSelected(this.node.object, this.node);
}
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
this.domainObject = newObject;
});
this.$once('hook:destroyed', removeListener);
if (this.openmct.composition.get(this.node.object)) {
this.hasChildren = true;
}
},
beforeDestroy() {
this.expanded = false;
},
destroyed() {
if (this.composition) {
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
delete this.composition;
}
},
methods: {
addChild(child) {
this.children.push({
id: this.openmct.objects.makeKeyString(child.identifier),
object: child,
objectPath: [child].concat(this.node.objectPath),
navigateToParent: this.navigateToPath
});
},
removeChild(identifier) {
let removeId = this.openmct.objects.makeKeyString(identifier);
this.children = this.children
.filter(c => c.id !== removeId);
},
finishLoading() {
this.isLoading = false;
this.loaded = true;
}
}
};
</script>

View File

@ -186,6 +186,10 @@ export default {
return {
name: field.name,
value: field.path.reduce((object, key) => {
if (object === undefined) {
return object;
}
return object[key];
}, this.domainObject)
};

View File

@ -38,7 +38,7 @@
transition: $transIn;
&.is-expanded {
height: max-content;
min-height: 100px;
}
}

View File

@ -280,38 +280,5 @@
border: 1px solid $colorFormLines;
border-radius: $controlCr;
padding: $interiorMargin;
> .c-tree {
overflow: auto;
}
}
}
// TRANSITIONS
.children-enter-active {
&.down {
animation: animSlideLeft 500ms;
}
&.up {
animation: animSlideRight 500ms;
}
}
@keyframes animSlideLeft {
0% {opacity: 0; transform: translateX(100%);}
10% {opacity: 1;}
100% {transform: translateX(0);}
}
@keyframes animSlideRight {
0% {opacity: 0; transform: translateX(-100%);}
10% {opacity: 1;}
100% {transform: translateX(0);}
}
@keyframes animTemporaryHighlight {
from { background: transparent; }
30% { background: $colorItemTreeNewNode; }
100% { background: transparent; }
}

View File

@ -1,6 +1,12 @@
<template>
<div class="c-tree-and-search">
<div
ref="treeContainer"
class="c-tree-and-search"
:class="{
'c-selector': isSelectorTree
}"
:style="treeHeight"
>
<div
ref="search"
class="c-tree-and-search__search"
@ -72,6 +78,8 @@
v-for="(treeItem, index) in visibleItems"
:key="treeItem.navigationPath"
:node="treeItem"
:is-selector-tree="isSelectorTree"
:selected-item="selectedItem"
:active-search="activeSearch"
:left-offset="!activeSearch ? treeItem.leftOffset : '0px'"
:is-new="treeItem.isNew"
@ -82,7 +90,8 @@
:loading-items="treeItemLoading"
@tree-item-mounted="scrollToCheck($event)"
@tree-item-destroyed="removeCompositionListenerFor($event)"
@navigation-click="treeItemAction(treeItem, $event)"
@tree-item-action="treeItemAction(treeItem, $event)"
@tree-item-selection="treeItemSelection(treeItem)"
/>
<!-- main loading -->
<div
@ -115,6 +124,7 @@ const ITEM_BUFFER = 25;
const LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded';
const SORT_MY_ITEMS_ALPH_ASC = true;
const TREE_ITEM_INDENT_PX = 18;
const LOCATOR_ITEM_COUNT_HEIGHT = 10; // how many tree items to make the locator selection box show
export default {
name: 'MctTree',
@ -124,13 +134,27 @@ export default {
},
inject: ['openmct'],
props: {
isSelectorTree: {
type: Boolean,
required: false,
default() {
return false;
}
},
initialSelection: {
type: Object,
required: false,
default() {
return {};
}
},
syncTreeNavigation: {
type: Boolean,
required: true
required: false
},
resetTreeNavigation: {
type: Boolean,
required: true
required: false
}
},
data() {
@ -149,7 +173,8 @@ export default {
itemHeight: 27,
itemOffset: 0,
activeSearch: false,
mainTreeTopMargin: undefined
mainTreeTopMargin: undefined,
selectedItem: {}
};
},
computed: {
@ -179,6 +204,13 @@ export default {
},
showNoSearchResults() {
return this.searchValue && this.searchResultItems.length === 0 && !this.searchLoading;
},
treeHeight() {
if (!this.isSelectorTree) {
return {};
} else {
return { height: this.itemHeight * LOCATOR_ITEM_COUNT_HEIGHT + 'px' };
}
}
},
watch: {
@ -223,7 +255,14 @@ export default {
await this.loadRoot();
this.isLoading = false;
await this.syncTreeOpenItems();
if (!this.isSelectorTree) {
await this.syncTreeOpenItems();
} else {
const objectPath = await this.openmct.objects.getOriginalPath(this.initialSelection.identifier);
const navigationPath = this.buildNavigationPath(objectPath);
this.openAndScrollTo(navigationPath);
}
},
created() {
this.getSearchResults = _.debounce(this.getSearchResults, 400);
@ -265,6 +304,10 @@ export default {
this.openTreeItem(parentItem);
}
},
treeItemSelection(item) {
this.selectedItem = item;
this.$emit('tree-item-selection', item);
},
async openTreeItem(parentItem) {
let parentPath = parentItem.navigationPath;
@ -358,6 +401,10 @@ export default {
}
},
openAndScrollTo(navigationPath) {
if (navigationPath.includes('/ROOT')) {
navigationPath = navigationPath.split('/ROOT').join('');
}
let idArray = navigationPath.split('/');
let fullPathArray = [];
let pathsToOpen;
@ -367,7 +414,6 @@ export default {
// skip root
idArray.splice(0, 2);
idArray[0] = 'browse/' + idArray[0];
idArray.reduce((parentPath, childPath) => {
let fullPath = [parentPath, childPath].join('/');
@ -383,7 +429,11 @@ export default {
return this.openTreeItem(this.getTreeItemByPath(childPath));
}, Promise.resolve());
}, Promise.resolve()).then(() => {
if (this.isSelectorTree) {
this.treeItemSelection(this.getTreeItemByPath(navigationPath));
}
});
},
scrollToCheck(navigationPath) {
if (this.scrollToPath && this.scrollToPath === navigationPath) {
@ -445,6 +495,10 @@ export default {
}
// sorting composition items
if (!a.name || !b.name) {
return 0;
}
if (a.name.toLowerCase()
> b.name.toLowerCase()) {
return 1;
@ -717,18 +771,26 @@ export default {
let checkHeights = () => {
let treeTopMargin = this.getElementStyleValue(this.$refs.mainTree, 'marginTop');
let paddingOffset = 0;
if (
this.$el
&& this.$refs.search
&& this.$refs.mainTree
&& this.$refs.treeContainer
&& this.$refs.dummyItem
&& this.$el.offsetHeight !== 0
&& treeTopMargin > 0
) {
if (this.isSelectorTree) {
paddingOffset = this.getElementStyleValue(this.$refs.treeContainer, 'padding');
}
this.mainTreeTopMargin = treeTopMargin;
this.mainTreeHeight = this.$el.offsetHeight
- this.$refs.search.offsetHeight
- this.mainTreeTopMargin;
- this.mainTreeTopMargin
- (paddingOffset * 2);
this.itemHeight = this.getElementStyleValue(this.$refs.dummyItem, 'height');
resolve();
@ -779,10 +841,18 @@ export default {
return Number(styleString.slice(0, index));
},
getSavedOpenItems() {
if (this.isSelectorTree) {
return;
}
let openItems = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED);
this.openTreeItems = openItems ? JSON.parse(openItems) : [];
},
setSavedOpenItems() {
if (this.isSelectorTree) {
return;
}
localStorage.setItem(LOCAL_STORAGE_KEY__TREE_EXPANDED, JSON.stringify(this.openTreeItems));
},
handleTreeResize() {

View File

@ -7,19 +7,19 @@
class="c-tree__item"
:class="{
'is-alias': isAlias,
'is-navigated-object': navigated,
'is-navigated-object': shouldHightlight,
'is-context-clicked': contextClickActive,
'is-new': isNewItem
}"
@click.capture="handleClick"
@click.capture="itemClick"
@contextmenu.capture="handleContextMenu"
>
<view-control
ref="navigate"
ref="action"
class="c-tree__item__view-control"
:value="isOpen || isLoading"
:enabled="!activeSearch && hasComposition"
@input="navigationClick()"
@input="itemAction()"
/>
<object-label
ref="objectLabel"
@ -52,6 +52,14 @@ export default {
type: Object,
required: true
},
isSelectorTree: {
type: Boolean,
required: true
},
selectedItem: {
type: Object,
required: true
},
activeSearch: {
type: Boolean,
default: false
@ -109,6 +117,9 @@ export default {
return parentKeyString !== this.node.object.location;
},
isSelectedItem() {
return this.selectedItem.objectPath === this.node.objectPath;
},
isNewItem() {
return this.isNew;
},
@ -118,6 +129,13 @@ export default {
isOpen() {
return this.openItems.includes(this.navigationPath);
},
shouldHightlight() {
if (this.isSelectorTree) {
return this.isSelectedItem;
} else {
return this.navigated;
}
},
treeItemStyles() {
let itemTop = (this.itemOffset + this.itemIndex) * this.itemHeight + 'px';
@ -144,20 +162,30 @@ export default {
this.$emit('tree-item-destoyed', this.navigationPath);
},
methods: {
navigationClick() {
this.$emit('navigation-click', this.isOpen || this.isLoading ? 'close' : 'open');
itemAction() {
this.$emit('tree-item-action', this.isOpen || this.isLoading ? 'close' : 'open');
},
handleClick(event) {
itemClick(event) {
// skip for navigation, let viewControl handle click
if (this.$refs.navigate.$el === event.target) {
if (this.$refs.action.$el === event.target) {
return;
}
event.stopPropagation();
this.$refs.objectLabel.navigateOrPreview(event);
if (!this.isSelectorTree) {
this.$refs.objectLabel.navigateOrPreview(event);
} else {
this.$emit('tree-item-selection', this.node);
}
},
handleContextMenu(event) {
event.stopPropagation();
if (this.isSelectorTree) {
return;
}
this.$refs.objectLabel.showContextMenu(event);
},
isNavigated() {

View File

@ -1,4 +1,4 @@
import objectPathToUrl from '/src/tools/url';
import objectPathToUrl from '../../tools/url';
export default {
inject: ['openmct'],

1
static-root.json Normal file
View File

@ -0,0 +1 @@
{"openmct":{"c1b7f449-459e-4f37-ac00-07b91936d079":{"identifier":{"key":"c1b7f449-459e-4f37-ac00-07b91936d079","namespace":""},"name":"A Folder","type":"folder","composition":[{"key":"3e1b49d7-4b95-47a6-9744-93d18c1f7d86","namespace":""},{"key":"da052eaf-1631-48e4-944f-c4688276181b","namespace":""}],"modified":1641506166404,"location":"mine","persisted":1641506166404},"3e1b49d7-4b95-47a6-9744-93d18c1f7d86":{"identifier":{"key":"3e1b49d7-4b95-47a6-9744-93d18c1f7d86","namespace":""},"name":"Test Clock","type":"clock","configuration":{"baseFormat":"YYYY/MM/DD hh:mm:ss","use24":"clock12","timezone":"UTC"},"modified":1641506137466,"location":"c1b7f449-459e-4f37-ac00-07b91936d079","persisted":1641506137466},"da052eaf-1631-48e4-944f-c4688276181b":{"identifier":{"key":"da052eaf-1631-48e4-944f-c4688276181b","namespace":""},"name":"B Hyperlink","type":"hyperlink","displayFormat":"link","linkTarget":"_self","url":"www.google.com","displayText":"Google","modified":1641506166402,"location":"c1b7f449-459e-4f37-ac00-07b91936d079","persisted":1641506166402}},"rootId":"c1b7f449-459e-4f37-ac00-07b91936d079"}

View File

@ -1,5 +1,6 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
@ -14,6 +15,14 @@ module.exports = merge(common, {
plugins: [
new webpack.DefinePlugin({
__OPENMCT_ROOT_RELATIVE__: '"dist/"'
}),
new CopyWebpackPlugin({
patterns: [
{
from: './static-root.json',
to: '.'
}
]
})
],
devtool: 'eval-source-map'