mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 02:29:24 +00:00
Compare commits
15 Commits
fix-notebo
...
static-roo
Author | SHA1 | Date | |
---|---|---|---|
7a0041b663 | |||
44f5372c31 | |||
2f292fbd07 | |||
205dc67809 | |||
169c23dbcc | |||
457cd42987 | |||
45373c56f7 | |||
91e909bb4a | |||
556f762d20 | |||
e14b7cd0e2 | |||
b1cffd0df3 | |||
09f25bc525 | |||
bd406d1a73 | |||
03380e1846 | |||
dcaad40064 |
@ -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:
|
||||
|
1
.github/dependabot.yml
vendored
1
.github/dependabot.yml
vendored
@ -25,3 +25,4 @@ updates:
|
||||
labels:
|
||||
- "type:maintenance"
|
||||
- "dependencies"
|
||||
- "prcop:disable"
|
||||
|
2
.github/workflows/prcop.yml
vendored
2
.github/workflows/prcop.yml
vendored
@ -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 }}
|
||||
|
110
example/exampleUser/ExampleUserProvider.js
Normal file
110
example/exampleUser/ExampleUserProvider.js
Normal 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;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
36
example/exampleUser/exampleUserCreator.js
Normal file
36
example/exampleUser/exampleUserCreator.js
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
29
example/exampleUser/plugin.js
Normal file
29
example/exampleUser/plugin.js
Normal 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));
|
||||
};
|
||||
}
|
55
example/exampleUser/pluginSpec.js
Normal file
55
example/exampleUser/pluginSpec.js
Normal 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.
|
||||
|
||||
});
|
@ -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>
|
||||
|
@ -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",
|
||||
|
10
src/MCT.js
10
src/MCT.js
@ -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);
|
||||
|
@ -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
|
||||
};
|
||||
});
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
@ -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
39
src/api/user/User.js
Normal 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
137
src/api/user/UserAPI.js
Normal 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
112
src/api/user/UserAPISpec.js
Normal 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
24
src/api/user/constants.js
Normal 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.';
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,13 +65,8 @@ export default {
|
||||
keyString: undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
imageHistorySize() {
|
||||
return this.imageHistory.length;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
imageHistorySize(newSize, oldSize) {
|
||||
imageHistory(newHistory, oldHistory) {
|
||||
this.updatePlotImagery();
|
||||
}
|
||||
},
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
49
src/plugins/persistence/couch/CouchSearchProvider.js
Normal file
49
src/plugins/persistence/couch/CouchSearchProvider.js
Normal 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;
|
@ -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));
|
||||
};
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -32,6 +32,8 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
console.log(JSON.parse(objectString));
|
||||
|
||||
return JSON.parse(objectString);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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())));
|
||||
|
@ -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())));
|
||||
|
@ -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();
|
||||
|
54
src/plugins/userIndicator/components/UserIndicator.vue
Normal file
54
src/plugins/userIndicator/components/UserIndicator.vue
Normal 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>
|
56
src/plugins/userIndicator/plugin.js
Normal file
56
src/plugins/userIndicator/plugin.js
Normal 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);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
100
src/plugins/userIndicator/pluginSpec.js
Normal file
100
src/plugins/userIndicator/pluginSpec.js
Normal 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);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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>
|
@ -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>
|
@ -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)
|
||||
};
|
||||
|
@ -38,7 +38,7 @@
|
||||
transition: $transIn;
|
||||
|
||||
&.is-expanded {
|
||||
height: max-content;
|
||||
min-height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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
1
static-root.json
Normal 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"}
|
@ -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'
|
||||
|
Reference in New Issue
Block a user