Compare commits
32 Commits
omm-r5.2.0
...
new-link-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7da48d493 | ||
|
|
4ba9bcb8f3 | ||
|
|
9fbacfd023 | ||
|
|
affa934766 | ||
|
|
f203806406 | ||
|
|
6540518daa | ||
|
|
730efb75ed | ||
|
|
a2aa0b8ab0 | ||
|
|
8a3864edf6 | ||
|
|
f1b748b46e | ||
|
|
a2a6d3020c | ||
|
|
6813f2083a | ||
|
|
a641996a3f | ||
|
|
3cae138b6a | ||
|
|
a44d8ab451 | ||
|
|
af4db1f799 | ||
|
|
38416b9434 | ||
|
|
d27d4aba16 | ||
|
|
354b590852 | ||
|
|
99427b4bed | ||
|
|
3b3b9368b5 | ||
|
|
a829a14463 | ||
|
|
f42e37930c | ||
|
|
aeb9dbeb33 | ||
|
|
f9393f80e0 | ||
|
|
28fe4688db | ||
|
|
50192b1aca | ||
|
|
c5e3838353 | ||
|
|
a66e77821f | ||
|
|
068d804a79 | ||
|
|
05558c02aa | ||
|
|
cc6c57398d |
@@ -20,7 +20,7 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<mct-container key="c-overlay__contents">
|
||||
<div class=c-overlay__top-bar">
|
||||
<div class="c-overlay__top-bar">
|
||||
<div class="c-overlay__dialog-title">{{ngModel.dialog.title}}</div>
|
||||
<div class="c-overlay__dialog-hint hint">{{ngModel.dialog.hint}}</div>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,6 @@ define([
|
||||
"./src/controllers/EditObjectController",
|
||||
"./src/actions/EditAndComposeAction",
|
||||
"./src/actions/EditAction",
|
||||
"./src/actions/PropertiesAction",
|
||||
"./src/actions/SaveAction",
|
||||
"./src/actions/SaveAndStopEditingAction",
|
||||
"./src/actions/SaveAsAction",
|
||||
@@ -55,7 +54,6 @@ define([
|
||||
EditObjectController,
|
||||
EditAndComposeAction,
|
||||
EditAction,
|
||||
PropertiesAction,
|
||||
SaveAction,
|
||||
SaveAndStopEditingAction,
|
||||
SaveAsAction,
|
||||
@@ -143,22 +141,6 @@ define([
|
||||
"group": "action",
|
||||
"priority": 10
|
||||
},
|
||||
{
|
||||
"key": "properties",
|
||||
"category": [
|
||||
"contextual",
|
||||
"view-control"
|
||||
],
|
||||
"implementation": PropertiesAction,
|
||||
"cssClass": "major icon-pencil",
|
||||
"name": "Edit Properties...",
|
||||
"group": "action",
|
||||
"priority": 10,
|
||||
"description": "Edit properties of this object.",
|
||||
"depends": [
|
||||
"dialogService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "save-and-stop-editing",
|
||||
"category": "save",
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'../creation/CreateWizard',
|
||||
// '../creation/CreateWizard',
|
||||
'./SaveInProgressDialog'
|
||||
],
|
||||
function (
|
||||
CreateWizard,
|
||||
// CreateWizard,
|
||||
SaveInProgressDialog
|
||||
) {
|
||||
|
||||
@@ -100,7 +100,8 @@ function (
|
||||
toUndirty = [];
|
||||
|
||||
function doWizardSave(parent) {
|
||||
var wizard = self.createWizard(parent);
|
||||
console.log('SaveAsAction');
|
||||
// var wizard = self.createWizard(parent);
|
||||
|
||||
return self.dialogService
|
||||
.getUserInput(wizard.getFormStructure(true),
|
||||
|
||||
@@ -21,25 +21,21 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/actions/LinkAction",
|
||||
"./src/actions/SetPrimaryLocationAction",
|
||||
"./src/services/LocatingCreationDecorator",
|
||||
"./src/services/LocatingObjectDecorator",
|
||||
"./src/policies/CopyPolicy",
|
||||
"./src/policies/CrossSpacePolicy",
|
||||
"./src/capabilities/LocationCapability",
|
||||
"./src/services/LinkService",
|
||||
"./src/services/CopyService",
|
||||
"./src/services/LocationService"
|
||||
], function (
|
||||
LinkAction,
|
||||
SetPrimaryLocationAction,
|
||||
LocatingCreationDecorator,
|
||||
LocatingObjectDecorator,
|
||||
CopyPolicy,
|
||||
CrossSpacePolicy,
|
||||
LocationCapability,
|
||||
LinkService,
|
||||
CopyService,
|
||||
LocationService
|
||||
) {
|
||||
@@ -52,21 +48,6 @@ define([
|
||||
"configuration": {},
|
||||
"extensions": {
|
||||
"actions": [
|
||||
{
|
||||
"key": "link",
|
||||
"name": "Create Link",
|
||||
"description": "Create Link to object in another location.",
|
||||
"cssClass": "icon-link",
|
||||
"category": "contextual",
|
||||
"group": "action",
|
||||
"priority": 7,
|
||||
"implementation": LinkAction,
|
||||
"depends": [
|
||||
"policyService",
|
||||
"locationService",
|
||||
"linkService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "locate",
|
||||
"name": "Set Primary Location",
|
||||
@@ -115,15 +96,6 @@ define([
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "linkService",
|
||||
"name": "Link Service",
|
||||
"description": "Provides a service for linking objects",
|
||||
"implementation": LinkService,
|
||||
"depends": [
|
||||
"openmct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "copyService",
|
||||
"name": "Copy Service",
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
function () {
|
||||
|
||||
/**
|
||||
* LinkService provides an interface for linking objects to additional
|
||||
* locations. It also provides a method for determining if an object
|
||||
* can be copied to a specific location.
|
||||
* @constructor
|
||||
* @memberof platform/entanglement
|
||||
* @implements {platform/entanglement.AbstractComposeService}
|
||||
*/
|
||||
function LinkService(openmct) {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
LinkService.prototype.validate = function (object, parentCandidate) {
|
||||
if (!parentCandidate || !parentCandidate.getId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentCandidate.getId() === object.getId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parentCandidate.hasCapability('composition')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter'));
|
||||
};
|
||||
|
||||
LinkService.prototype.perform = function (object, parentObject) {
|
||||
if (!this.validate(object, parentObject)) {
|
||||
throw new Error(
|
||||
"Tried to link objects without validating first."
|
||||
);
|
||||
}
|
||||
|
||||
return parentObject.getCapability('composition').add(object);
|
||||
};
|
||||
|
||||
return LinkService;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
'../../src/actions/LinkAction',
|
||||
'../services/MockLinkService',
|
||||
'../DomainObjectFactory'
|
||||
],
|
||||
function (LinkAction, MockLinkService, domainObjectFactory) {
|
||||
|
||||
describe("Link Action", function () {
|
||||
|
||||
var linkAction,
|
||||
policyService,
|
||||
locationService,
|
||||
locationServicePromise,
|
||||
linkService,
|
||||
context,
|
||||
selectedObject,
|
||||
selectedObjectContextCapability,
|
||||
currentParent,
|
||||
newParent;
|
||||
|
||||
beforeEach(function () {
|
||||
policyService = jasmine.createSpyObj(
|
||||
'policyService',
|
||||
['allow']
|
||||
);
|
||||
policyService.allow.and.returnValue(true);
|
||||
|
||||
selectedObjectContextCapability = jasmine.createSpyObj(
|
||||
'selectedObjectContextCapability',
|
||||
[
|
||||
'getParent'
|
||||
]
|
||||
);
|
||||
|
||||
selectedObject = domainObjectFactory({
|
||||
name: 'selectedObject',
|
||||
model: {
|
||||
name: 'selectedObject'
|
||||
},
|
||||
capabilities: {
|
||||
context: selectedObjectContextCapability
|
||||
}
|
||||
});
|
||||
|
||||
currentParent = domainObjectFactory({
|
||||
name: 'currentParent'
|
||||
});
|
||||
|
||||
selectedObjectContextCapability
|
||||
.getParent
|
||||
.and.returnValue(currentParent);
|
||||
|
||||
newParent = domainObjectFactory({
|
||||
name: 'newParent'
|
||||
});
|
||||
|
||||
locationService = jasmine.createSpyObj(
|
||||
'locationService',
|
||||
[
|
||||
'getLocationFromUser'
|
||||
]
|
||||
);
|
||||
|
||||
locationServicePromise = jasmine.createSpyObj(
|
||||
'locationServicePromise',
|
||||
[
|
||||
'then'
|
||||
]
|
||||
);
|
||||
|
||||
locationService
|
||||
.getLocationFromUser
|
||||
.and.returnValue(locationServicePromise);
|
||||
|
||||
linkService = new MockLinkService();
|
||||
});
|
||||
|
||||
describe("with context from context-action", function () {
|
||||
beforeEach(function () {
|
||||
context = {
|
||||
domainObject: selectedObject
|
||||
};
|
||||
|
||||
linkAction = new LinkAction(
|
||||
policyService,
|
||||
locationService,
|
||||
linkService,
|
||||
context
|
||||
);
|
||||
});
|
||||
|
||||
it("initializes happily", function () {
|
||||
expect(linkAction).toBeDefined();
|
||||
});
|
||||
|
||||
describe("when performed it", function () {
|
||||
beforeEach(function () {
|
||||
linkAction.perform();
|
||||
});
|
||||
|
||||
it("prompts for location", function () {
|
||||
expect(locationService.getLocationFromUser)
|
||||
.toHaveBeenCalledWith(
|
||||
"Link selectedObject To a New Location",
|
||||
"Link To",
|
||||
jasmine.any(Function),
|
||||
currentParent
|
||||
);
|
||||
});
|
||||
|
||||
it("waits for location and handles cancellation by user", function () {
|
||||
expect(locationServicePromise.then)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("links object to selected location", function () {
|
||||
locationServicePromise
|
||||
.then
|
||||
.calls.mostRecent()
|
||||
.args[0](newParent);
|
||||
|
||||
expect(linkService.perform)
|
||||
.toHaveBeenCalledWith(selectedObject, newParent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with context from drag-drop", function () {
|
||||
beforeEach(function () {
|
||||
context = {
|
||||
selectedObject: selectedObject,
|
||||
domainObject: newParent
|
||||
};
|
||||
|
||||
linkAction = new LinkAction(
|
||||
policyService,
|
||||
locationService,
|
||||
linkService,
|
||||
context
|
||||
);
|
||||
});
|
||||
|
||||
it("initializes happily", function () {
|
||||
expect(linkAction).toBeDefined();
|
||||
});
|
||||
|
||||
it("performs link immediately", function () {
|
||||
linkAction.perform();
|
||||
expect(linkService.perform)
|
||||
.toHaveBeenCalledWith(selectedObject, newParent);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,215 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
'../../src/services/LinkService',
|
||||
'../DomainObjectFactory',
|
||||
'../ControlledPromise'
|
||||
],
|
||||
function (LinkService, domainObjectFactory, ControlledPromise) {
|
||||
|
||||
xdescribe("LinkService", function () {
|
||||
|
||||
var linkService,
|
||||
mockPolicyService;
|
||||
|
||||
beforeEach(function () {
|
||||
mockPolicyService = jasmine.createSpyObj(
|
||||
'policyService',
|
||||
['allow']
|
||||
);
|
||||
mockPolicyService.allow.and.returnValue(true);
|
||||
linkService = new LinkService(mockPolicyService);
|
||||
});
|
||||
|
||||
describe("validate", function () {
|
||||
|
||||
var object,
|
||||
parentCandidate,
|
||||
validate;
|
||||
|
||||
beforeEach(function () {
|
||||
object = domainObjectFactory({
|
||||
name: 'object'
|
||||
});
|
||||
parentCandidate = domainObjectFactory({
|
||||
name: 'parentCandidate',
|
||||
capabilities: {
|
||||
composition: jasmine.createSpyObj(
|
||||
'composition',
|
||||
['invoke', 'add']
|
||||
)
|
||||
}
|
||||
});
|
||||
validate = function () {
|
||||
return linkService.validate(object, parentCandidate);
|
||||
};
|
||||
});
|
||||
|
||||
it("does not allow invalid parentCandidate", function () {
|
||||
parentCandidate = undefined;
|
||||
expect(validate()).toBe(false);
|
||||
parentCandidate = {};
|
||||
expect(validate()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not allow parent to be object", function () {
|
||||
parentCandidate.id = object.id = 'abc';
|
||||
expect(validate()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not allow parent that contains object", function () {
|
||||
object.id = 'abc';
|
||||
parentCandidate.id = 'xyz';
|
||||
parentCandidate.model.composition = ['abc'];
|
||||
expect(validate()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not allow parents without composition", function () {
|
||||
parentCandidate = domainObjectFactory({
|
||||
name: 'parentCandidate'
|
||||
});
|
||||
object.id = 'abc';
|
||||
parentCandidate.id = 'xyz';
|
||||
parentCandidate.hasCapability.and.callFake(function (c) {
|
||||
return c !== 'composition';
|
||||
});
|
||||
expect(validate()).toBe(false);
|
||||
});
|
||||
|
||||
describe("defers to policyService", function () {
|
||||
beforeEach(function () {
|
||||
object.id = 'abc';
|
||||
object.capabilities.type = { type: 'object' };
|
||||
parentCandidate.id = 'xyz';
|
||||
parentCandidate.capabilities.type = {
|
||||
type: 'parentCandidate'
|
||||
};
|
||||
parentCandidate.model.composition = [];
|
||||
});
|
||||
|
||||
it("calls policy service with correct args", function () {
|
||||
validate();
|
||||
expect(mockPolicyService.allow).toHaveBeenCalledWith(
|
||||
"composition",
|
||||
parentCandidate,
|
||||
object
|
||||
);
|
||||
});
|
||||
|
||||
it("and returns false", function () {
|
||||
mockPolicyService.allow.and.returnValue(true);
|
||||
expect(validate()).toBe(true);
|
||||
expect(mockPolicyService.allow).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("and returns true", function () {
|
||||
mockPolicyService.allow.and.returnValue(false);
|
||||
expect(validate()).toBe(false);
|
||||
expect(mockPolicyService.allow).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("perform", function () {
|
||||
|
||||
var object,
|
||||
linkedObject,
|
||||
parentModel,
|
||||
parentObject,
|
||||
compositionPromise,
|
||||
addPromise,
|
||||
compositionCapability;
|
||||
|
||||
beforeEach(function () {
|
||||
compositionPromise = new ControlledPromise();
|
||||
addPromise = new ControlledPromise();
|
||||
compositionCapability = jasmine.createSpyObj(
|
||||
'compositionCapability',
|
||||
['invoke', 'add']
|
||||
);
|
||||
compositionCapability.invoke.and.returnValue(compositionPromise);
|
||||
compositionCapability.add.and.returnValue(addPromise);
|
||||
parentModel = {
|
||||
composition: []
|
||||
};
|
||||
parentObject = domainObjectFactory({
|
||||
name: 'parentObject',
|
||||
model: parentModel,
|
||||
capabilities: {
|
||||
mutation: {
|
||||
invoke: function (mutator) {
|
||||
mutator(parentModel);
|
||||
|
||||
return new ControlledPromise();
|
||||
}
|
||||
},
|
||||
composition: compositionCapability
|
||||
}
|
||||
});
|
||||
|
||||
object = domainObjectFactory({
|
||||
name: 'object',
|
||||
id: 'xyz'
|
||||
});
|
||||
|
||||
linkedObject = domainObjectFactory({
|
||||
name: 'object-link',
|
||||
id: 'xyz'
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("adds to the parent's composition", function () {
|
||||
expect(compositionCapability.add).not.toHaveBeenCalled();
|
||||
linkService.perform(object, parentObject);
|
||||
expect(compositionCapability.add)
|
||||
.toHaveBeenCalledWith(object);
|
||||
});
|
||||
|
||||
it("returns object representing new link", function () {
|
||||
var returnPromise, whenComplete;
|
||||
returnPromise = linkService.perform(object, parentObject);
|
||||
whenComplete = jasmine.createSpy('whenComplete');
|
||||
returnPromise.then(whenComplete);
|
||||
|
||||
addPromise.resolve(linkedObject);
|
||||
compositionPromise.resolve([linkedObject]);
|
||||
expect(whenComplete).toHaveBeenCalledWith(linkedObject);
|
||||
});
|
||||
|
||||
it("throws an error when performed on invalid inputs", function () {
|
||||
function perform() {
|
||||
linkService.perform(object, parentObject);
|
||||
}
|
||||
|
||||
spyOn(linkService, 'validate');
|
||||
linkService.validate.and.returnValue(true);
|
||||
expect(perform).not.toThrow();
|
||||
linkService.validate.and.returnValue(false);
|
||||
expect(perform).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -282,7 +282,7 @@ define([
|
||||
}
|
||||
],
|
||||
"model": {
|
||||
"timerFormat": "DDD hh:mm:ss"
|
||||
"timerFormat": "long"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -47,6 +47,7 @@ define([
|
||||
'./plugins/licenses/plugin',
|
||||
'./plugins/remove/plugin',
|
||||
'./plugins/move/plugin',
|
||||
'./plugins/linkAction/plugin',
|
||||
'./plugins/duplicate/plugin',
|
||||
'vue'
|
||||
], function (
|
||||
@@ -76,6 +77,7 @@ define([
|
||||
LicensesPlugin,
|
||||
RemoveActionPlugin,
|
||||
MoveActionPlugin,
|
||||
LinkActionPlugin,
|
||||
DuplicateActionPlugin,
|
||||
Vue
|
||||
) {
|
||||
@@ -253,6 +255,7 @@ define([
|
||||
this.status = new api.StatusAPI(this);
|
||||
|
||||
this.router = new ApplicationRouter(this);
|
||||
this.forms = new api.FormsAPI.default(this);
|
||||
|
||||
this.branding = BrandingAPI.default;
|
||||
|
||||
@@ -268,6 +271,7 @@ define([
|
||||
this.install(LicensesPlugin.default());
|
||||
this.install(RemoveActionPlugin.default());
|
||||
this.install(MoveActionPlugin.default());
|
||||
this.install(LinkActionPlugin.default());
|
||||
this.install(DuplicateActionPlugin.default());
|
||||
this.install(this.plugins.FolderView());
|
||||
this.install(this.plugins.Tabs());
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
import _ from 'lodash';
|
||||
const INSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "link", "locate", "move", "link"];
|
||||
const OUTSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "properties", "move", "link", "remove", "locate"];
|
||||
const OUTSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "move", "link", "remove", "locate"];
|
||||
|
||||
export default class LegacyContextMenuAction {
|
||||
constructor(openmct, LegacyAction) {
|
||||
|
||||
@@ -21,41 +21,44 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./time/TimeAPI',
|
||||
'./objects/ObjectAPI',
|
||||
'./composition/CompositionAPI',
|
||||
'./types/TypeRegistry',
|
||||
'./telemetry/TelemetryAPI',
|
||||
'./indicators/IndicatorAPI',
|
||||
'./notifications/NotificationAPI',
|
||||
'./Editor',
|
||||
'./menu/MenuAPI',
|
||||
'./actions/ActionsAPI',
|
||||
'./status/StatusAPI'
|
||||
'./composition/CompositionAPI',
|
||||
'./Editor',
|
||||
'./forms/FormsAPI',
|
||||
'./indicators/IndicatorAPI',
|
||||
'./menu/MenuAPI',
|
||||
'./notifications/NotificationAPI',
|
||||
'./objects/ObjectAPI',
|
||||
'./status/StatusAPI',
|
||||
'./telemetry/TelemetryAPI',
|
||||
'./time/TimeAPI',
|
||||
'./types/TypeRegistry'
|
||||
], function (
|
||||
TimeAPI,
|
||||
ObjectAPI,
|
||||
CompositionAPI,
|
||||
TypeRegistry,
|
||||
TelemetryAPI,
|
||||
IndicatorAPI,
|
||||
NotificationAPI,
|
||||
EditorAPI,
|
||||
MenuAPI,
|
||||
ActionsAPI,
|
||||
StatusAPI
|
||||
CompositionAPI,
|
||||
EditorAPI,
|
||||
FormsAPI,
|
||||
IndicatorAPI,
|
||||
MenuAPI,
|
||||
NotificationAPI,
|
||||
ObjectAPI,
|
||||
StatusAPI,
|
||||
TelemetryAPI,
|
||||
TimeAPI,
|
||||
TypeRegistry
|
||||
) {
|
||||
return {
|
||||
TimeAPI: TimeAPI,
|
||||
ObjectAPI: ObjectAPI,
|
||||
CompositionAPI: CompositionAPI,
|
||||
TypeRegistry: TypeRegistry,
|
||||
TelemetryAPI: TelemetryAPI,
|
||||
IndicatorAPI: IndicatorAPI,
|
||||
NotificationAPI: NotificationAPI.default,
|
||||
EditorAPI: EditorAPI,
|
||||
MenuAPI: MenuAPI.default,
|
||||
ActionsAPI: ActionsAPI.default,
|
||||
StatusAPI: StatusAPI.default
|
||||
CompositionAPI: CompositionAPI,
|
||||
EditorAPI: EditorAPI,
|
||||
FormsAPI: FormsAPI,
|
||||
IndicatorAPI: IndicatorAPI,
|
||||
MenuAPI: MenuAPI.default,
|
||||
NotificationAPI: NotificationAPI.default,
|
||||
ObjectAPI: ObjectAPI,
|
||||
StatusAPI: StatusAPI.default,
|
||||
TelemetryAPI: TelemetryAPI,
|
||||
TimeAPI: TimeAPI,
|
||||
TypeRegistry: TypeRegistry,
|
||||
};
|
||||
});
|
||||
|
||||
135
src/api/forms/CreateWizard.js
Normal file
135
src/api/forms/CreateWizard.js
Normal file
@@ -0,0 +1,135 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 CreateWizard {
|
||||
constructor(openmct, domainObject, parent) {
|
||||
this.openmct = openmct;
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.type = openmct.types.get(domainObject.type);
|
||||
|
||||
this.model = domainObject;
|
||||
this.parent = parent;
|
||||
this.properties = this.type.definition.form || [];
|
||||
}
|
||||
|
||||
addNotes(sections) {
|
||||
const row = {
|
||||
control: 'textarea',
|
||||
cssClass: 'l-textarea-sm',
|
||||
key: 'notes',
|
||||
name: 'Notes',
|
||||
required: false,
|
||||
value: this.domainObject.notes
|
||||
};
|
||||
|
||||
sections.forEach(section => {
|
||||
if (section.name !== 'Properties') {
|
||||
return;
|
||||
}
|
||||
|
||||
section.rows.unshift(row);
|
||||
});
|
||||
}
|
||||
|
||||
addTitle(sections) {
|
||||
const row = {
|
||||
control: 'textfield',
|
||||
cssClass: 'l-input-lg',
|
||||
key: 'name',
|
||||
name: 'Title',
|
||||
pattern: `\\S+`,
|
||||
required: true,
|
||||
value: this.domainObject.name
|
||||
};
|
||||
|
||||
sections.forEach(section => {
|
||||
if (section.name !== 'Properties') {
|
||||
return;
|
||||
}
|
||||
|
||||
section.rows.unshift(row);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form model for this wizard; this is a description
|
||||
* that will be rendered to an HTML form. See the
|
||||
* platform/forms bundle
|
||||
* @param {boolean} includeLocation if true, a 'location' section
|
||||
* will be included that will allow the user to select the location
|
||||
* of the newly created object, otherwise the .location property of
|
||||
* the model will be used.
|
||||
*/
|
||||
getFormStructure(includeLocation) {
|
||||
let sections = [];
|
||||
let domainObject = this.domainObject;
|
||||
let self = this;
|
||||
|
||||
sections.push({
|
||||
name: 'Properties',
|
||||
rows: this.properties.map(property => {
|
||||
const row = JSON.parse(JSON.stringify(property));
|
||||
row.value = this.getValue(row);
|
||||
|
||||
return row;
|
||||
}).filter(row => row && row.control)
|
||||
});
|
||||
|
||||
this.addNotes(sections);
|
||||
this.addTitle(sections);
|
||||
|
||||
// Ensure there is always a 'save in' section
|
||||
if (includeLocation) {
|
||||
function validateLocation(object, data) {
|
||||
return self.openmct.composition.checkPolicy(data.value, object);
|
||||
}
|
||||
|
||||
sections.push({
|
||||
name: 'Location',
|
||||
cssClass: 'grows',
|
||||
rows: [{
|
||||
name: 'Save In',
|
||||
cssClass: 'grows',
|
||||
control: 'locator',
|
||||
domainObject,
|
||||
required: true,
|
||||
parent: this.parent,
|
||||
validate: validateLocation.bind(this),
|
||||
key: 'location'
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sections
|
||||
};
|
||||
}
|
||||
|
||||
getValue(row) {
|
||||
if (row.property) {
|
||||
return row.property.reduce((acc, property) => acc && acc[property], this.domainObject);
|
||||
} else {
|
||||
return this.domainObject[row.key];
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/api/forms/FormsAPI.js
Normal file
159
src/api/forms/FormsAPI.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 EditPropertiesAction from './actions/EditPropertiesAction';
|
||||
import FormProperties from './components/FormProperties.vue';
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
export const CONTROLS = [
|
||||
"autocomplete",
|
||||
"button",
|
||||
"checkbox",
|
||||
"color",
|
||||
"composite",
|
||||
"datetime",
|
||||
"dialog-button",
|
||||
"file-input",
|
||||
"menu-button",
|
||||
"numberfield",
|
||||
"radio",
|
||||
"select",
|
||||
"textarea",
|
||||
"textfield"
|
||||
];
|
||||
|
||||
export default class FormsAPI {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.controls = {};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
addControl(name, actions) {
|
||||
const control = this.controls[name];
|
||||
if (control) {
|
||||
this.openmct.notifications.error(`Error: provided form control '${name}', already exists`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.controls[name] = actions;
|
||||
}
|
||||
|
||||
getAllControls() {
|
||||
return this.controls;
|
||||
}
|
||||
|
||||
getControl(name) {
|
||||
const control = this.controls[name];
|
||||
if (control) {
|
||||
console.error(`Error: form control '${name}', does not exist`);
|
||||
}
|
||||
|
||||
return control;
|
||||
}
|
||||
|
||||
showForm(formStructure, options) {
|
||||
const changes = {};
|
||||
let overlay;
|
||||
|
||||
let parentDomainObject = options.parentDomainObject || {};
|
||||
const domainObject = options.domainObject;
|
||||
const onSave = () => {
|
||||
overlay.dismiss();
|
||||
|
||||
if(options.onSave) {
|
||||
options.onSave(domainObject, changes, parentDomainObject);
|
||||
}
|
||||
};
|
||||
|
||||
const onDismiss = () => {
|
||||
overlay.dismiss();
|
||||
|
||||
if (options.onDismiss) {
|
||||
options.onDismiss();
|
||||
};
|
||||
};
|
||||
|
||||
const vm = new Vue({
|
||||
components: { FormProperties },
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
domainObject
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formStructure,
|
||||
onChange,
|
||||
onDismiss,
|
||||
onSave
|
||||
};
|
||||
},
|
||||
template: '<FormProperties :model="formStructure" @onChange="onChange" @onDismiss="onDismiss" @onSave="onSave"></FormProperties>'
|
||||
}).$mount();
|
||||
|
||||
overlay = this.openmct.overlays.overlay({
|
||||
element: vm.$el,
|
||||
size: 'small',
|
||||
onDestroy: () => vm.$destroy()
|
||||
});
|
||||
|
||||
function onChange(data) {
|
||||
if (options.onChange) {
|
||||
options.onChange(data);
|
||||
}
|
||||
|
||||
if (data.model) {
|
||||
const property = data.model.property;
|
||||
let key = data.model.key;
|
||||
if (key === 'location') {
|
||||
parentDomainObject = data.value;
|
||||
}
|
||||
|
||||
if (property && property.length) {
|
||||
key = property.join('.');
|
||||
}
|
||||
|
||||
changes[key] = data.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_addDefaultFormControls() {
|
||||
CONTROLS.forEach(control => {
|
||||
this.addControl(control);
|
||||
});
|
||||
}
|
||||
|
||||
// Init
|
||||
init() {
|
||||
this.openmct.actions.register(new EditPropertiesAction(this.openmct));
|
||||
|
||||
this._addDefaultFormControls();
|
||||
}
|
||||
}
|
||||
131
src/api/forms/actions/CreateAction.js
Normal file
131
src/api/forms/actions/CreateAction.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 PropertiesAction from './PropertiesAction';
|
||||
import CreateWizard from '../CreateWizard';
|
||||
|
||||
import uuid from 'uuid';
|
||||
|
||||
export default class CreateAction extends PropertiesAction {
|
||||
constructor(openmct, type, parentDomainObject) {
|
||||
super(openmct);
|
||||
|
||||
this.type = type;
|
||||
this.parentDomainObject = parentDomainObject;
|
||||
}
|
||||
|
||||
invoke() {
|
||||
this._showCreateForm(this.type);
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
async _onSave(domainObject, changes, parentDomainObject) {
|
||||
Object.entries(changes).forEach(([key, value]) => {
|
||||
const properties = key.split('.');
|
||||
let object = this.domainObject;
|
||||
const propertiesLength = properties.length;
|
||||
properties.forEach((property, index) => {
|
||||
const isComplexProperty = propertiesLength > 1 && index != propertiesLength - 1;
|
||||
if (isComplexProperty && object[property] !== null) {
|
||||
object = object[property];
|
||||
} else {
|
||||
object[property] = value;
|
||||
}
|
||||
});
|
||||
|
||||
object = value;
|
||||
});
|
||||
|
||||
this.domainObject.modified = Date.now();
|
||||
this.domainObject.location = this.openmct.objects.makeKeyString(parentDomainObject.identifier);
|
||||
this.domainObject.identifier.namespace = parentDomainObject.identifier.namespace;
|
||||
|
||||
// Show saving progress dialog
|
||||
let dialog = this.openmct.overlays.progressDialog({
|
||||
progressPerc: 'unknown',
|
||||
message: 'Do not navigate away from this page or close this browser tab while this message is displayed.',
|
||||
iconClass: 'info',
|
||||
title: 'Saving'
|
||||
});
|
||||
|
||||
const success = await this.openmct.objects.save(this.domainObject);
|
||||
if (success) {
|
||||
const compositionCollection = await openmct.composition.get(parentDomainObject);
|
||||
compositionCollection.add(this.domainObject);
|
||||
|
||||
this._navigateAndEdit(this.domainObject);
|
||||
|
||||
this.openmct.notifications.info('Save successful');
|
||||
} else {
|
||||
this.openmct.notifications.error('Error saving objects');
|
||||
console.error(error);
|
||||
}
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
async _navigateAndEdit(domainObject) {
|
||||
const objectPath = await this.openmct.objects.getOriginalPath(domainObject.identifier);
|
||||
|
||||
const url = '#/browse/' + objectPath
|
||||
.map(object => object && this.openmct.objects.makeKeyString(object.identifier.key))
|
||||
.reverse()
|
||||
.join('/');
|
||||
|
||||
window.location.href = url;
|
||||
|
||||
const objectView = openmct.objectViews.get(domainObject, objectPath)[0];
|
||||
const canEdit = objectView && objectView.canEdit && objectView.canEdit(domainObject, objectPath);
|
||||
if (canEdit) {
|
||||
openmct.editor.edit();
|
||||
}
|
||||
}
|
||||
|
||||
_showCreateForm(type) {
|
||||
const typeDefinition = this.openmct.types.get(type);
|
||||
const definition = typeDefinition.definition;
|
||||
const domainObject = {
|
||||
name: `Unnamed ${definition.name}`,
|
||||
type,
|
||||
identifier: {
|
||||
key: uuid(),
|
||||
namespace: this.parentDomainObject.identifier.namespace
|
||||
}
|
||||
};
|
||||
|
||||
this.domainObject = domainObject;
|
||||
|
||||
if (definition.initialize) {
|
||||
definition.initialize(domainObject);
|
||||
}
|
||||
|
||||
const createWizard = new CreateWizard(this.openmct, domainObject, this.parentDomainObject);
|
||||
const formStructure = createWizard.getFormStructure(true);
|
||||
formStructure.title = 'Create a New ' + definition.name;
|
||||
|
||||
const options = {
|
||||
domainObject,
|
||||
onSave: this._onSave.bind(this)
|
||||
};
|
||||
|
||||
this.openmct.forms.showForm(formStructure, options);
|
||||
}
|
||||
}
|
||||
102
src/api/forms/actions/EditPropertiesAction.js
Normal file
102
src/api/forms/actions/EditPropertiesAction.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 PropertiesAction from './PropertiesAction';
|
||||
import CreateWizard from '../CreateWizard';
|
||||
export default class EditPropertiesAction extends PropertiesAction {
|
||||
constructor(openmct) {
|
||||
super(openmct);
|
||||
|
||||
this.name = 'Edit Properties...';
|
||||
this.key = 'properties';
|
||||
this.description = 'Edit properties of this object.';
|
||||
this.cssClass = 'major icon-pencil';
|
||||
this.hideInDefaultMenu = true;
|
||||
this.group = 'action';
|
||||
this.priority = 10;
|
||||
this.formProperties = {}
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
const definition = this._getTypeDefinition(objectPath[0].type);
|
||||
|
||||
return definition && definition.creatable;
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
this._showEditForm(objectPath);
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
async _onSave(domainObject, changes, parentDomainObject) {
|
||||
Object.entries(changes).forEach(([key, value]) => {
|
||||
const properties = key.split('.');
|
||||
let object = this.domainObject;
|
||||
const propertiesLength = properties.length;
|
||||
properties.forEach((property, index) => {
|
||||
const isComplexProperty = propertiesLength > 1 && index != propertiesLength - 1;
|
||||
if (isComplexProperty && object[property] !== null) {
|
||||
object = object[property];
|
||||
} else {
|
||||
object[property] = value;
|
||||
}
|
||||
});
|
||||
|
||||
object = value;
|
||||
});
|
||||
|
||||
this.domainObject.modified = Date.now();
|
||||
|
||||
// Show saving progress dialog
|
||||
let dialog = this.openmct.overlays.progressDialog({
|
||||
progressPerc: 'unknown',
|
||||
message: 'Do not navigate away from this page or close this browser tab while this message is displayed.',
|
||||
iconClass: 'info',
|
||||
title: 'Saving'
|
||||
});
|
||||
|
||||
const success = await this.openmct.objects.save(this.domainObject);
|
||||
if (success) {
|
||||
this.openmct.notifications.info('Save successful');
|
||||
} else {
|
||||
this.openmct.notifications.error('Error saving objects');
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
_showEditForm(objectPath) {
|
||||
this.domainObject = objectPath[0];
|
||||
|
||||
const createWizard = new CreateWizard(this.openmct, this.domainObject, objectPath[1]);
|
||||
const formStructure = createWizard.getFormStructure(false);
|
||||
formStructure.title = 'Edit ' + this.domainObject.name;
|
||||
|
||||
const options = {
|
||||
domainObject: this.domainObject,
|
||||
onSave: this._onSave.bind(this)
|
||||
};
|
||||
|
||||
this.openmct.forms.showForm(formStructure, options);
|
||||
}
|
||||
}
|
||||
35
src/api/forms/actions/PropertiesAction.js
Normal file
35
src/api/forms/actions/PropertiesAction.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 PropertiesAction {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getTypeDefinition(type) {
|
||||
const TypeDefinition = this.openmct.types.get(type);
|
||||
|
||||
return TypeDefinition.definition;
|
||||
}
|
||||
}
|
||||
99
src/api/forms/components/FormProperties.vue
Normal file
99
src/api/forms/components/FormProperties.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div>
|
||||
<form name="mctForm"
|
||||
class="form c-form mct-form"
|
||||
autocomplete="off"
|
||||
@submit.prevent
|
||||
>
|
||||
<div class="mct-form__title c-overlay__top-bar">
|
||||
<div class="c-overlay__dialog-title">{{ model.title }}</div>
|
||||
<div class="c-overlay__dialog-hint hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
|
||||
</div>
|
||||
<span v-for="section in model.sections"
|
||||
:key="section.name"
|
||||
class="mct-form__sections l-form-section c-form__section"
|
||||
:class="section.cssClass"
|
||||
>
|
||||
<h2 class="c-form__header"
|
||||
v-if="section.name"
|
||||
>
|
||||
{{ section.name }}
|
||||
</h2>
|
||||
<div v-for="(row, index) in section.rows"
|
||||
:key="row.name"
|
||||
>
|
||||
<FormRow :css-class="section.cssClass"
|
||||
:first="index < 1"
|
||||
:row="row"
|
||||
@onChange="onChange"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</form>
|
||||
|
||||
<div class="mct-form__controls c-overlay__button-bar">
|
||||
<button tabindex="0"
|
||||
:disabled="isInvalid"
|
||||
class="c-button c-button--major"
|
||||
@click="onSave"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
<button tabindex="0"
|
||||
class="c-button"
|
||||
@click="onDismiss"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormRow from "@/api/forms/components/FormRow.vue";
|
||||
export default {
|
||||
components: {
|
||||
FormRow
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inValidProperties: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isInvalid() {
|
||||
return Object.entries(this.inValidProperties)
|
||||
.some(([key, value]) => {
|
||||
return value;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(data) {
|
||||
this.$set(this.inValidProperties, data.model.key, data.invalid);
|
||||
|
||||
this.$emit('onChange', data);
|
||||
},
|
||||
onDismiss() {
|
||||
this.$emit('onDismiss');
|
||||
},
|
||||
onSave() {
|
||||
this.$emit('onSave');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
156
src/api/forms/components/FormRow.vue
Normal file
156
src/api/forms/components/FormRow.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div class="form-row c-form__row"
|
||||
:class="rowClass"
|
||||
@onChange="onChange"
|
||||
>
|
||||
<div v-if="row.name"
|
||||
class="c-form__row__label label flex-elem"
|
||||
:title="row.description"
|
||||
>
|
||||
{{ row.name }}
|
||||
</div>
|
||||
<div class="c-form__row__controls controls flex-elem">
|
||||
<div v-if="row.control"
|
||||
class="c-form__controls-wrapper wrapper"
|
||||
>
|
||||
<component
|
||||
:is="getComponent"
|
||||
:key="row.key"
|
||||
:ref="`form-control-${row.key}`"
|
||||
:model="row"
|
||||
:value="row.value"
|
||||
:required="isRequired"
|
||||
@onChange="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AutoCompleteField from "@/api/forms/components/controls/AutoCompleteField.vue";
|
||||
import Composite from "@/api/forms/components/controls/Composite.vue";
|
||||
import FileInput from "@/api/forms/components/controls/FileInput.vue"
|
||||
import Locator from "@/api/forms/components/controls/Locator.vue";
|
||||
import NumberField from "@/api/forms/components/controls/NumberField.vue";
|
||||
import SelectField from '@/api/forms/components/controls/SelectField.vue';
|
||||
import TextArea from "@/api/forms/components/controls/TextArea.vue";
|
||||
import TextField from "@/api/forms/components/controls/TextField.vue";
|
||||
|
||||
const CONTROL_TYPE_VIEW_MAP = {
|
||||
'autocomplete': AutoCompleteField,
|
||||
'composite': Composite,
|
||||
'file-input': FileInput,
|
||||
'locator': Locator,
|
||||
'numberfield': NumberField,
|
||||
'select': SelectField,
|
||||
'textarea': TextArea,
|
||||
'textfield': TextField,
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'FormRow',
|
||||
components: CONTROL_TYPE_VIEW_MAP,
|
||||
inject: ['openmct', 'domainObject'],
|
||||
props: {
|
||||
cssClass: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
first: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: true
|
||||
},
|
||||
row: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valid: undefined,
|
||||
visited: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
getComponent() {
|
||||
return CONTROL_TYPE_VIEW_MAP[this.row.control];
|
||||
},
|
||||
isRequired() {
|
||||
//TODO: Check if field is required
|
||||
return false;
|
||||
},
|
||||
rowClass() {
|
||||
let cssClass = this.cssClass;
|
||||
|
||||
if (this.first === true) {
|
||||
cssClass = `${cssClass} first`;
|
||||
}
|
||||
|
||||
if (this.row.required) {
|
||||
cssClass = `${cssClass} req`;
|
||||
}
|
||||
|
||||
if (this.row.layout === 'controls-first') {
|
||||
cssClass = `${cssClass} l-controls-first`;
|
||||
}
|
||||
|
||||
if (this.row.layout === 'controls-under') {
|
||||
cssClass = `${cssClass} l-controls-under`;
|
||||
}
|
||||
|
||||
if (this.visited && this.valid !== undefined) {
|
||||
if (this.valid === true) {
|
||||
cssClass = `${cssClass} valid`;
|
||||
} else {
|
||||
cssClass = `${cssClass} invalid`;
|
||||
}
|
||||
}
|
||||
|
||||
return cssClass;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.row.required) {
|
||||
const data = {
|
||||
model: this.row,
|
||||
value: this.row.value
|
||||
};
|
||||
|
||||
this.onChange(data, false);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(data, visited = true) {
|
||||
this.visited = visited;
|
||||
|
||||
const valid = this.validateRow(data);
|
||||
this.valid = valid;
|
||||
data.invalid = !valid;
|
||||
|
||||
this.$emit('onChange', data);
|
||||
},
|
||||
validateRow(data) {
|
||||
let valid = true;
|
||||
if (this.row.required) {
|
||||
valid = data.value !== undefined && data.value !== null && data.value !== '';
|
||||
}
|
||||
|
||||
const pattern = data.model.pattern;
|
||||
if (valid && pattern) {
|
||||
const regex = new RegExp(pattern);
|
||||
valid = regex.test(data.value);
|
||||
}
|
||||
|
||||
const validate = data.model.validate;
|
||||
if (valid && validate) {
|
||||
valid = validate(this.domainObject, data);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
175
src/api/forms/components/controls/AutoCompleteField.vue
Normal file
175
src/api/forms/components/controls/AutoCompleteField.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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="form-control autocomplete">
|
||||
<input v-model="field"
|
||||
class="autocompleteInput"
|
||||
type="text"
|
||||
@click="inputClicked()"
|
||||
@keydown="keyDown($event)"
|
||||
>
|
||||
<span class="icon-arrow-down"
|
||||
@click="arrowClicked()"
|
||||
></span>
|
||||
<div
|
||||
class="autocompleteOptions"
|
||||
@blur="hideOptions = true"
|
||||
>
|
||||
<ul v-if="!hideOptions">
|
||||
<li v-for="opt in filteredOptions"
|
||||
:key="opt.optionId"
|
||||
:class="{'optionPreSelected': optionIndex === opt.optionId}"
|
||||
@click="fillInputWithString(opt.name)"
|
||||
@mouseover="optionMouseover(opt.optionId)"
|
||||
>
|
||||
<span class="optionText">{{ opt.name }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const key = {
|
||||
down: 40,
|
||||
up: 38,
|
||||
enter: 13
|
||||
};
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hideOptions: true,
|
||||
optionIndex: 0,
|
||||
field: this.model.value
|
||||
};
|
||||
},
|
||||
computed : {
|
||||
filteredOptions() {
|
||||
const options = this.optionNames || [];
|
||||
return options
|
||||
.filter(option => {
|
||||
return option.toLowerCase().indexOf(this.field.toLowerCase()) >= 0;
|
||||
}).map((option, index) => {
|
||||
return {
|
||||
optionId: index,
|
||||
name: option
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.options = this.model.options;
|
||||
this.autocompleteInputElement = this.$el.getElementsByClassName('autocompleteInput')[0];
|
||||
if (this.options[0].name) {
|
||||
// If "options" include name, value pair
|
||||
this.optionNames = this.options.map((opt) => {
|
||||
return opt.name;
|
||||
});
|
||||
} else {
|
||||
// If options is only an array of string.
|
||||
this.optionNames = this.options;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
decrementOptionIndex() {
|
||||
if (this.optionIndex === 0) {
|
||||
this.optionIndex = this.filteredOptions.length;
|
||||
}
|
||||
|
||||
this.optionIndex--;
|
||||
this.scrollIntoView();
|
||||
},
|
||||
incrementOptionIndex() {
|
||||
if (this.optionIndex === this.filteredOptions.length - 1) {
|
||||
this.optionIndex = -1;
|
||||
}
|
||||
|
||||
this.optionIndex++;
|
||||
this.scrollIntoView();
|
||||
},
|
||||
fillInputWithString(string) {
|
||||
this.hideOptions = true;
|
||||
this.field = string;
|
||||
},
|
||||
showOptions(string) {
|
||||
this.hideOptions = false;
|
||||
this.optionIndex = 0;
|
||||
},
|
||||
keyDown($event) {
|
||||
if (this.filteredOptions) {
|
||||
let keyCode = $event.keyCode;
|
||||
switch (keyCode) {
|
||||
case key.down:
|
||||
this.incrementOptionIndex();
|
||||
break;
|
||||
case key.up:
|
||||
$event.preventDefault(); // Prevents cursor jumping back and forth
|
||||
this.decrementOptionIndex();
|
||||
break;
|
||||
case key.enter:
|
||||
if (this.filteredOptions[this.optionIndex]) {
|
||||
this.fillInputWithString(this.filteredOptions[this.optionIndex].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
inputClicked() {
|
||||
this.autocompleteInputElement.select();
|
||||
this.showOptions(this.autocompleteInputElement.value);
|
||||
},
|
||||
arrowClicked() {
|
||||
this.autocompleteInputElement.select();
|
||||
this.showOptions('');
|
||||
},
|
||||
optionMouseover(optionId) {
|
||||
this.optionIndex = optionId;
|
||||
},
|
||||
scrollIntoView() {
|
||||
setTimeout(() => {
|
||||
const element = this.$el.querySelector('.optionPreSelected');
|
||||
|
||||
element && element.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'});
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
field(newValue, oldValue) {
|
||||
if (newValue !== oldValue) {
|
||||
|
||||
const data = {
|
||||
model: this.model,
|
||||
value: newValue
|
||||
};
|
||||
|
||||
this.$emit('onChange', data);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
56
src/api/forms/components/controls/Composite.vue
Normal file
56
src/api/forms/components/controls/Composite.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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>
|
||||
<span>
|
||||
<CompositeItem v-for="(item, index) in model.items"
|
||||
:key="item.name"
|
||||
:first="index < 1"
|
||||
:value="JSON.stringify(model.value[index])"
|
||||
:item="item"
|
||||
@onChange="onChange"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CompositeItem from "@/api/forms/components/controls/CompositeItem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CompositeItem
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(data) {
|
||||
this.$emit('onChange', data);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.model.items.forEach((item, index) => item.key = `${this.model.key}.${index}`);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
71
src/api/forms/components/controls/CompositeItem.vue
Normal file
71
src/api/forms/components/controls/CompositeItem.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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="compositeCssClass">
|
||||
<FormRow :css-class="item.cssClass"
|
||||
:first="first"
|
||||
:row="row"
|
||||
@onChange="onChange"
|
||||
/>
|
||||
<span class="composite-control-label">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormRow: () => import('@/api/forms/components/FormRow.vue')
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
first: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
compositeCssClass() {
|
||||
return `l-composite-control l-${this.item.control}`;
|
||||
},
|
||||
row() {
|
||||
const row = this.item;
|
||||
row.value = JSON.parse(this.value);
|
||||
|
||||
return row;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(data) {
|
||||
this.$emit('onChange', data);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
71
src/api/forms/components/controls/FileInput.vue
Normal file
71
src/api/forms/components/controls/FileInput.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<span class="form-control shell">
|
||||
<span class="field control"
|
||||
:class="model.cssClass"
|
||||
>
|
||||
<input ref="fileInput" id="fileElem" type="file" accept=".json" style="display:none">
|
||||
<button id="fileSelect"
|
||||
class="c-button"
|
||||
@click="selectFile"
|
||||
>{{ name }}</button>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fileInfo: undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
name() {
|
||||
const fileInfo = this.fileInfo || this.model.value;
|
||||
|
||||
return fileInfo && fileInfo.name || this.model.text;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.fileInput.addEventListener("change", this.handleFiles, false);
|
||||
},
|
||||
methods: {
|
||||
handleFiles() {
|
||||
const fileList = this.$refs.fileInput.files;
|
||||
this.readFile(fileList[0]);
|
||||
},
|
||||
readFile(file) {
|
||||
const self = this;
|
||||
const fileReader = new FileReader();
|
||||
const fileInfo = {};
|
||||
fileInfo.name = file.name;
|
||||
fileReader.onload = function (event) {
|
||||
fileInfo.body = event.target.result;
|
||||
self.fileInfo = fileInfo;
|
||||
|
||||
const data = {
|
||||
model: self.model,
|
||||
value: fileInfo
|
||||
};
|
||||
self.$emit('onChange', data);
|
||||
};
|
||||
|
||||
fileReader.onerror = function (error) {
|
||||
console.error('fileReader error', error);
|
||||
};
|
||||
|
||||
fileReader.readAsText(file);
|
||||
},
|
||||
selectFile() {
|
||||
this.$refs.fileInput.click();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
50
src/api/forms/components/controls/Locator.vue
Normal file
50
src/api/forms/components/controls/Locator.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<ConditionSetSelectorDialog
|
||||
:hideTitle="true"
|
||||
:ignoreTypeCheck="true"
|
||||
:cssClass="`form-locator`"
|
||||
:parent="model.parent"
|
||||
@conditionSetSelected="handleItemSelection"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConditionSetSelectorDialog from '@/plugins/condition/components/inspector/ConditionSetSelectorDialog.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ConditionSetSelectorDialog
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
watch: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
// remove following after css fix
|
||||
setTimeout(() => {
|
||||
document.querySelector('.c-overlay__contents-main').style.height = '200px';
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
},
|
||||
methods: {
|
||||
handleItemSelection(parentDomainObject) {
|
||||
const data = { model: this.model, value: parentDomainObject };
|
||||
this.$emit('onChange', data);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
62
src/api/forms/components/controls/NumberField.vue
Normal file
62
src/api/forms/components/controls/NumberField.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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>
|
||||
<span class="form-control shell">
|
||||
<span class="field control"
|
||||
:class="model.cssClass"
|
||||
>
|
||||
<input v-model="field"
|
||||
type="number"
|
||||
:min="model.min"
|
||||
:max="model.max"
|
||||
:step="model.step"
|
||||
@blur="blur()"
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
field: this.model.value
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
blur() {
|
||||
const data = {
|
||||
model :this.model,
|
||||
value: this.field
|
||||
};
|
||||
|
||||
this.$emit('onChange', data);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
42
src/api/forms/components/controls/SelectField.vue
Normal file
42
src/api/forms/components/controls/SelectField.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class='form-control SelectField'>
|
||||
<select required="model.required"
|
||||
name="mctControl"
|
||||
@change="onChange($event)"
|
||||
v-model="selected"
|
||||
>
|
||||
<option v-for="option in model.options"
|
||||
:key="option.name"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selected: this.model.value
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onChange() {
|
||||
const data = {
|
||||
model: this.model,
|
||||
value: this.selected
|
||||
};
|
||||
|
||||
this.$emit('onChange', data);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
60
src/api/forms/components/controls/TextArea.vue
Normal file
60
src/api/forms/components/controls/TextArea.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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>
|
||||
<span class="form-control shell">
|
||||
<span class="field control"
|
||||
:class="model.cssClass"
|
||||
>
|
||||
<textarea v-model="field"
|
||||
type="text"
|
||||
:size="model.size"
|
||||
@blur="blur()"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
field: this.model.value
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
blur() {
|
||||
const data = {
|
||||
model :this.model,
|
||||
value: this.field
|
||||
};
|
||||
|
||||
this.$emit('onChange', data);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
60
src/api/forms/components/controls/TextField.vue
Normal file
60
src/api/forms/components/controls/TextField.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2021, 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>
|
||||
<span class="form-control shell">
|
||||
<span class="field control"
|
||||
:class="model.cssClass"
|
||||
>
|
||||
<input v-model="field"
|
||||
type="text"
|
||||
:size="model.size"
|
||||
@blur="blur()"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
field: this.model.value
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
blur() {
|
||||
const data = {
|
||||
model :this.model,
|
||||
value: this.field
|
||||
};
|
||||
|
||||
this.$emit('onChange', data);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -40,7 +40,6 @@ const DEFAULTS = [
|
||||
'platform/features/clock',
|
||||
'platform/features/hyperlink',
|
||||
'platform/features/timeline',
|
||||
'platform/forms',
|
||||
'platform/identity',
|
||||
'platform/persistence/aggregator',
|
||||
'platform/persistence/queue',
|
||||
@@ -85,7 +84,6 @@ define([
|
||||
'../platform/features/hyperlink/bundle',
|
||||
'../platform/features/static-markup/bundle',
|
||||
'../platform/features/timeline/bundle',
|
||||
'../platform/forms/bundle',
|
||||
'../platform/framework/bundle',
|
||||
'../platform/framework/src/load/Bundle',
|
||||
'../platform/identity/bundle',
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
:node="child"
|
||||
:selected-item="selectedItem"
|
||||
:handle-item-selected="handleItemSelected"
|
||||
:navigateToParent="navigateToParent"
|
||||
/>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -88,7 +89,13 @@ export default {
|
||||
default() {
|
||||
return (item) => {};
|
||||
}
|
||||
}
|
||||
},
|
||||
navigateToParent: {
|
||||
type: String,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -147,6 +154,16 @@ export default {
|
||||
},
|
||||
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)
|
||||
this.$el.scrollIntoView();
|
||||
}
|
||||
|
||||
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
|
||||
this.domainObject = newObject;
|
||||
});
|
||||
|
||||
@@ -22,10 +22,14 @@
|
||||
|
||||
<template>
|
||||
<div class="u-contents">
|
||||
<div class="c-overlay__top-bar">
|
||||
<div v-if="!hideTitle"
|
||||
class="c-overlay__top-bar"
|
||||
>
|
||||
<div class="c-overlay__dialog-title">Select Condition Set</div>
|
||||
</div>
|
||||
<div class="c-overlay__contents-main c-selector c-tree-and-search">
|
||||
<div class="c-overlay__contents-main c-selector c-tree-and-search"
|
||||
:class="cssClass"
|
||||
>
|
||||
<div class="c-tree-and-search__search">
|
||||
<search ref="shell-search"
|
||||
class="c-search"
|
||||
@@ -58,6 +62,7 @@
|
||||
:node="treeItem"
|
||||
:selected-item="selectedItem"
|
||||
:handle-item-selected="handleItemSelection"
|
||||
:navigateToParent="navigateToParent"
|
||||
/>
|
||||
</ul>
|
||||
<!-- end main tree -->
|
||||
@@ -91,6 +96,36 @@ export default {
|
||||
ConditionSetDialogTreeItem
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
cssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
hideTitle: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
ignoreTypeCheck: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
parent: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
@@ -98,7 +133,8 @@ export default {
|
||||
allTreeItems: [],
|
||||
filteredTreeItems: [],
|
||||
isLoading: false,
|
||||
selectedItem: undefined
|
||||
selectedItem: undefined,
|
||||
navigateToParent: undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -115,10 +151,24 @@ export default {
|
||||
this.getDebouncedFilteredChildren = debounce(this.getFilteredChildren, 400);
|
||||
},
|
||||
mounted() {
|
||||
this.getAllChildren();
|
||||
|
||||
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: {
|
||||
getAllChildren() {
|
||||
getAllChildren(navigateToParent) {
|
||||
this.isLoading = true;
|
||||
this.openmct.objects.get('ROOT')
|
||||
.then(root => {
|
||||
@@ -131,7 +181,7 @@ export default {
|
||||
id: this.openmct.objects.makeKeyString(c.identifier),
|
||||
object: c,
|
||||
objectPath: [c],
|
||||
navigateToParent: '/browse'
|
||||
navigateToParent: navigateToParent || '/browse'
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -178,7 +228,7 @@ export default {
|
||||
}
|
||||
},
|
||||
handleItemSelection(item, node) {
|
||||
if (item && item.type === 'conditionSet') {
|
||||
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,
|
||||
|
||||
@@ -34,45 +34,10 @@ export default class DuplicateAction {
|
||||
}
|
||||
|
||||
async invoke(objectPath) {
|
||||
let duplicationTask = new DuplicateTask(this.openmct);
|
||||
let originalObject = objectPath[0];
|
||||
let parent = objectPath[1];
|
||||
let userInput = await this.getUserInput(originalObject, parent);
|
||||
let newParent = userInput.location;
|
||||
let inNavigationPath = this.inNavigationPath(originalObject);
|
||||
let object = objectPath[0];
|
||||
this.parent = objectPath[1];
|
||||
|
||||
// legacy check
|
||||
if (this.isLegacyDomainObject(newParent)) {
|
||||
newParent = await this.convertFromLegacy(newParent);
|
||||
}
|
||||
|
||||
// if editing, save
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
// duplicate
|
||||
let newObject = await duplicationTask.duplicate(originalObject, newParent);
|
||||
this.updateNameCheck(newObject, userInput.name);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async getUserInput(originalObject, parent) {
|
||||
let dialogService = this.openmct.$injector.get('dialogService');
|
||||
let dialogForm = this.getDialogForm(originalObject, parent);
|
||||
let formState = {
|
||||
name: originalObject.name
|
||||
};
|
||||
let userInput = await dialogService.getUserInput(dialogForm, formState);
|
||||
|
||||
return userInput;
|
||||
}
|
||||
|
||||
updateNameCheck(object, name) {
|
||||
if (object.name !== name) {
|
||||
this.openmct.objects.mutate(object, 'name', name);
|
||||
}
|
||||
this.showForm(object, this.parent);
|
||||
}
|
||||
|
||||
inNavigationPath(object) {
|
||||
@@ -80,40 +45,66 @@ export default class DuplicateAction {
|
||||
.some(objectInPath => this.openmct.objects.areIdsEqual(objectInPath.identifier, object.identifier));
|
||||
}
|
||||
|
||||
getDialogForm(object, parent) {
|
||||
return {
|
||||
name: "Duplicate Item",
|
||||
async onSave(object, changes, parent) {
|
||||
console.log('onSave');
|
||||
let inNavigationPath = this.inNavigationPath(object);
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
if (changes.name && (changes.name !== object.name)) {
|
||||
object.name = changes.name;
|
||||
}
|
||||
|
||||
|
||||
// duplicate
|
||||
let duplicationTask = new DuplicateTask(this.openmct);
|
||||
duplicationTask.duplicate(object, parent);
|
||||
}
|
||||
|
||||
showForm(domainObject, parentDomainObject) {
|
||||
const formStructure = {
|
||||
title: "Duplicate Item",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "name",
|
||||
control: "textfield",
|
||||
name: "Name",
|
||||
name: "Title",
|
||||
pattern: "\\S+",
|
||||
required: true,
|
||||
cssClass: "l-input-lg"
|
||||
cssClass: "l-input-lg",
|
||||
value: domainObject.name
|
||||
},
|
||||
{
|
||||
name: "location",
|
||||
cssClass: "grows",
|
||||
control: "locator",
|
||||
validate: this.validate(object, parent),
|
||||
required: true,
|
||||
parent: parentDomainObject,
|
||||
validate: this.validate(parentDomainObject),
|
||||
key: 'location'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.openmct.forms.showForm(formStructure, {
|
||||
domainObject,
|
||||
parentDomainObject,
|
||||
onSave: this.onSave.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
validate(object, currentParent) {
|
||||
return (parentCandidate) => {
|
||||
validate(currentParent) {
|
||||
return (object, data) => {
|
||||
const parentCandidate = data.value;
|
||||
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.getId());
|
||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
||||
let objectKeystring = this.openmct.objects.makeKeyString(object.identifier);
|
||||
|
||||
if (!parentCandidate || !currentParentKeystring) {
|
||||
if (!parentCandidateKeystring || !currentParentKeystring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -121,24 +112,15 @@ export default class DuplicateAction {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.openmct.composition.checkPolicy(
|
||||
parentCandidate.useCapability('adapter'),
|
||||
object
|
||||
);
|
||||
const parentCandidateComposition = parentCandidate.composition;
|
||||
if (parentCandidateComposition && parentCandidateComposition.indexOf(objectKeystring) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, object);
|
||||
};
|
||||
}
|
||||
|
||||
isLegacyDomainObject(domainObject) {
|
||||
return domainObject.getCapability !== undefined;
|
||||
}
|
||||
|
||||
async convertFromLegacy(legacyDomainObject) {
|
||||
let objectContext = legacyDomainObject.getCapability('context');
|
||||
let domainObject = await this.openmct.objects.get(objectContext.domainObject.id);
|
||||
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
let parent = objectPath[1];
|
||||
let parentType = parent && this.openmct.types.get(parent.type);
|
||||
|
||||
@@ -34,7 +34,6 @@ import uuid from 'uuid';
|
||||
* @constructor
|
||||
*/
|
||||
export default class DuplicateTask {
|
||||
|
||||
constructor(openmct) {
|
||||
this.domainObject = undefined;
|
||||
this.parent = undefined;
|
||||
|
||||
208
src/plugins/linkAction/LinkAction.js
Normal file
208
src/plugins/linkAction/LinkAction.js
Normal file
@@ -0,0 +1,208 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 LinkAction {
|
||||
constructor(openmct) {
|
||||
this.name = 'Create Link';
|
||||
this.key = 'link';
|
||||
this.description = 'Create Link to object in another location.';
|
||||
this.cssClass = "icon-link";
|
||||
this.group = "action";
|
||||
this.priority = 7;
|
||||
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
this.showForm(objectPath[0], objectPath[1]);
|
||||
}
|
||||
|
||||
inNavigationPath(object) {
|
||||
return this.openmct.router.path
|
||||
.some(objectInPath => this.openmct.objects.areIdsEqual(objectInPath.identifier, object.identifier));
|
||||
}
|
||||
|
||||
onSave(object, changes, parent) {
|
||||
let inNavigationPath = this.inNavigationPath(object);
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
this.linkInNewParent(object, parent);
|
||||
}
|
||||
|
||||
linkInNewParent(child, newParent) {
|
||||
let compositionCollection = this.openmct.composition.get(newParent);
|
||||
|
||||
compositionCollection.add(child);
|
||||
}
|
||||
|
||||
showForm(domainObject, parentDomainObject) {
|
||||
|
||||
const formStructure = {
|
||||
title: `Link "${domainObject.name}" to a New Location`,
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
name: "location",
|
||||
control: "locator",
|
||||
required: true,
|
||||
validate: this.validate(parentDomainObject),
|
||||
key: 'location'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.openmct.forms.showForm(formStructure, {
|
||||
domainObject,
|
||||
parentDomainObject,
|
||||
onSave: this.onSave.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
validate(currentParent) {
|
||||
return (object, data) => {
|
||||
const parentCandidate = data.value;
|
||||
// console.log('move action : validateLocation', );
|
||||
// TODO: remove getModel, checkPolicy and useCapability
|
||||
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
||||
let objectKeystring = this.openmct.objects.makeKeyString(object.identifier);
|
||||
|
||||
if (!parentCandidateKeystring || !currentParentKeystring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentCandidateKeystring === currentParentKeystring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentCandidateKeystring === objectKeystring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parentCandidateComposition = parentCandidate.composition;
|
||||
if (parentCandidateComposition && parentCandidateComposition.indexOf(objectKeystring) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, object);
|
||||
};
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
let domainObject = objectPath[0];
|
||||
let type = domainObject && this.openmct.types.get(domainObject.type);
|
||||
|
||||
return type && type.definition.creatable;
|
||||
}
|
||||
|
||||
// appliesTo(objectPath) {
|
||||
// let parent = objectPath[1];
|
||||
// let parentType = parent && this.openmct.types.get(parent.type);
|
||||
// let child = objectPath[0];
|
||||
// let childType = child && this.openmct.types.get(child.type);
|
||||
|
||||
// if (child.locked || (parent && parent.locked)) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// return parentType
|
||||
// && parentType.definition.creatable
|
||||
// && childType
|
||||
// && childType.definition.creatable
|
||||
// && Array.isArray(parent.composition);
|
||||
// }
|
||||
|
||||
// async invoke(objectPath) {
|
||||
// let objectToLink = objectPath[0];
|
||||
// let dialogService = this.openmct.$injector.get('dialogService');
|
||||
// let dialogForm = this.getDialogForm(objectToLink);
|
||||
// let userInput = await dialogService.getUserInput(dialogForm, {});
|
||||
// let newParent = userInput.location;
|
||||
|
||||
// // legacy check
|
||||
// if (this.isLegacyDomainObject(newParent)) {
|
||||
// newParent = await this.convertFromLegacy(newParent);
|
||||
// }
|
||||
|
||||
// this.linkInNewParent(objectToLink, newParent);
|
||||
// }
|
||||
|
||||
// isLegacyDomainObject(domainObject) {
|
||||
// return domainObject.getCapability !== undefined;
|
||||
// }
|
||||
|
||||
// async convertFromLegacy(legacyDomainObject) {
|
||||
// let objectContext = legacyDomainObject.getCapability('context');
|
||||
// let domainObject = await this.openmct.objects.get(objectContext.domainObject.id);
|
||||
|
||||
// return domainObject;
|
||||
// }
|
||||
|
||||
// getDialogForm(objectToLink) {
|
||||
// let validate = this.validate(objectToLink);
|
||||
|
||||
// return {
|
||||
// name: `Link "${objectToLink.name}" to a New Location`,
|
||||
// sections: [
|
||||
// {
|
||||
// rows: [
|
||||
// {
|
||||
// name: "Link To",
|
||||
// control: "locator",
|
||||
// validate,
|
||||
// key: 'location'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
// };
|
||||
// }
|
||||
|
||||
// validate(objectToLink) {
|
||||
// return (parentObject) => {
|
||||
// let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentObject.getId());
|
||||
// let objectToLinkKeystring = this.openmct.objects.makeKeyString(objectToLink.identifier);
|
||||
// let sameObjectOrChildAlready = parentCandidateKeystring === objectToLinkKeystring
|
||||
// || parentObject.getModel().composition.includes(objectToLinkKeystring);
|
||||
|
||||
// // the same object or a child already, not valid
|
||||
// if (sameObjectOrChildAlready) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (parentObject.getModel().locked) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // can contain
|
||||
// return this.openmct.composition.checkPolicy(
|
||||
// parentObject.useCapability('adapter'),
|
||||
// objectToLink
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
}
|
||||
@@ -19,30 +19,10 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import LinkAction from "./LinkAction";
|
||||
|
||||
define(
|
||||
['./AbstractComposeAction'],
|
||||
function (AbstractComposeAction) {
|
||||
|
||||
/**
|
||||
* The LinkAction is available from context menus and allows a user to
|
||||
* link an object to another location of their choosing.
|
||||
*
|
||||
* @implements {Action}
|
||||
* @constructor
|
||||
* @memberof platform/entanglement
|
||||
*/
|
||||
function LinkAction(policyService, locationService, linkService, context) {
|
||||
AbstractComposeAction.apply(
|
||||
this,
|
||||
[policyService, locationService, linkService, context, "Link"]
|
||||
);
|
||||
}
|
||||
|
||||
LinkAction.prototype = Object.create(AbstractComposeAction.prototype);
|
||||
LinkAction.appliesTo = AbstractComposeAction.appliesTo;
|
||||
|
||||
return LinkAction;
|
||||
}
|
||||
);
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
openmct.actions.register(new LinkAction(openmct));
|
||||
};
|
||||
}
|
||||
124
src/plugins/linkAction/pluginSpec.js
Normal file
124
src/plugins/linkAction/pluginSpec.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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 LinkActionPlugin from './plugin.js';
|
||||
import LinkAction from './LinkAction.js';
|
||||
import {
|
||||
createOpenMct,
|
||||
resetApplicationState,
|
||||
getMockObjects
|
||||
} from 'utils/testing';
|
||||
|
||||
describe("The Link Action plugin", () => {
|
||||
|
||||
let openmct;
|
||||
let linkAction;
|
||||
let childObject;
|
||||
let parentObject;
|
||||
let anotherParentObject;
|
||||
const ORIGINAL_PARENT_ID = 'original-parent-object';
|
||||
const LINK_ACITON_KEY = 'link';
|
||||
|
||||
beforeEach((done) => {
|
||||
const appHolder = document.createElement('div');
|
||||
appHolder.style.width = '640px';
|
||||
appHolder.style.height = '480px';
|
||||
|
||||
openmct = createOpenMct();
|
||||
|
||||
childObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
folder: {
|
||||
name: "Child Folder",
|
||||
location: ORIGINAL_PARENT_ID,
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "child-folder-object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
parentObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
folder: {
|
||||
name: "Parent Folder",
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "original-parent-object"
|
||||
},
|
||||
composition: [childObject.identifier]
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
anotherParentObject = getMockObjects({
|
||||
objectKeyStrings: ['folder'],
|
||||
overwrite: {
|
||||
folder: {
|
||||
name: "Another Parent Folder"
|
||||
}
|
||||
}
|
||||
}).folder;
|
||||
|
||||
openmct.router.path = [childObject]; // preview action uses this in it's applyTo method
|
||||
|
||||
openmct.install(LinkActionPlugin());
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless(appHolder);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(LinkActionPlugin).toBeDefined();
|
||||
});
|
||||
|
||||
it("should make the link action available for an appropriate domainObject", () => {
|
||||
let actions = openmct.actions.get([childObject]);
|
||||
let action = actions.filter(a => a.key === LINK_ACITON_KEY);
|
||||
|
||||
expect(action.length).toEqual(1);
|
||||
});
|
||||
|
||||
describe("when linking an object in a new parent", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
linkAction = new LinkAction(openmct);
|
||||
linkAction.linkInNewParent(childObject, anotherParentObject);
|
||||
});
|
||||
|
||||
it("the child object's identifier should be in the new parent's composition and location set to original parent", () => {
|
||||
let newParentChild = anotherParentObject.composition[0];
|
||||
expect(newParentChild).toEqual(childObject.identifier);
|
||||
expect(childObject.location).toEqual(ORIGINAL_PARENT_ID)
|
||||
});
|
||||
|
||||
it("the child object's identifier should remain in the original parent's composition", () => {
|
||||
let oldParentCompositionChild = parentObject.composition[0];
|
||||
expect(oldParentCompositionChild).toEqual(childObject.identifier);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -33,39 +33,9 @@ export default class MoveAction {
|
||||
|
||||
async invoke(objectPath) {
|
||||
let object = objectPath[0];
|
||||
let inNavigationPath = this.inNavigationPath(object);
|
||||
let oldParent = objectPath[1];
|
||||
let dialogService = this.openmct.$injector.get('dialogService');
|
||||
let dialogForm = this.getDialogForm(object, oldParent);
|
||||
let userInput = await dialogService.getUserInput(dialogForm, { name: object.name });
|
||||
this.oldParent = objectPath[1];
|
||||
|
||||
// if we need to update name
|
||||
if (object.name !== userInput.name) {
|
||||
this.openmct.objects.mutate(object, 'name', userInput.name);
|
||||
}
|
||||
|
||||
let parentContext = userInput.location.getCapability('context');
|
||||
let newParent = await this.openmct.objects.get(parentContext.domainObject.id);
|
||||
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
this.addToNewParent(object, newParent);
|
||||
this.removeFromOldParent(oldParent, object);
|
||||
|
||||
if (inNavigationPath) {
|
||||
let newObjectPath = await this.openmct.objects.getOriginalPath(object.identifier);
|
||||
let root = await this.openmct.objects.getRoot();
|
||||
let rootChildCount = root.composition.length;
|
||||
|
||||
// if not multiple root children, remove root from path
|
||||
if (rootChildCount < 2) {
|
||||
newObjectPath.pop(); // remove ROOT
|
||||
}
|
||||
|
||||
this.navigateTo(newObjectPath);
|
||||
}
|
||||
this.showForm(object, this.oldParent);
|
||||
}
|
||||
|
||||
inNavigationPath(object) {
|
||||
@@ -89,42 +59,86 @@ export default class MoveAction {
|
||||
compositionCollection.add(child);
|
||||
}
|
||||
|
||||
removeFromOldParent(parent, child) {
|
||||
let compositionCollection = this.openmct.composition.get(parent);
|
||||
async onSave(object, changes, parent) {
|
||||
let inNavigationPath = this.inNavigationPath(object);
|
||||
if (inNavigationPath && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
|
||||
if (this.openmct.objects.areIdsEqual(parent.identifier, this.oldParent.identifier)) {
|
||||
this.openmct.notifications.error(`Error: new location cant not be same as old`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.name && (changes.name !== object.name)) {
|
||||
object.name = changes.name;
|
||||
}
|
||||
|
||||
this.addToNewParent(object, parent);
|
||||
this.removeFromOldParent(object);
|
||||
|
||||
if (inNavigationPath) {
|
||||
let newObjectPath = await this.openmct.objects.getOriginalPath(object.identifier);
|
||||
let root = await this.openmct.objects.getRoot();
|
||||
let rootChildCount = root.composition.length;
|
||||
|
||||
// if not multiple root children, remove root from path
|
||||
if (rootChildCount < 2) {
|
||||
newObjectPath.pop(); // remove ROOT
|
||||
}
|
||||
|
||||
this.navigateTo(newObjectPath);
|
||||
}
|
||||
}
|
||||
|
||||
removeFromOldParent(child) {
|
||||
let compositionCollection = this.openmct.composition.get(this.oldParent);
|
||||
|
||||
compositionCollection.remove(child);
|
||||
}
|
||||
|
||||
getDialogForm(object, parent) {
|
||||
return {
|
||||
name: "Move Item",
|
||||
showForm(domainObject, parentDomainObject) {
|
||||
const formStructure = {
|
||||
title: "Move Item",
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
key: "name",
|
||||
control: "textfield",
|
||||
name: "Folder Name",
|
||||
name: "Title",
|
||||
pattern: "\\S+",
|
||||
required: true,
|
||||
cssClass: "l-input-lg"
|
||||
cssClass: "l-input-lg",
|
||||
value: domainObject.name
|
||||
},
|
||||
{
|
||||
name: "location",
|
||||
control: "locator",
|
||||
validate: this.validate(object, parent),
|
||||
required: true,
|
||||
validate: this.validate(parentDomainObject),
|
||||
key: 'location'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.openmct.forms.showForm(formStructure, {
|
||||
domainObject,
|
||||
parentDomainObject,
|
||||
onSave: this.onSave.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
validate(object, currentParent) {
|
||||
return (parentCandidate) => {
|
||||
validate(currentParent) {
|
||||
return (object, data) => {
|
||||
const parentCandidate = data.value;
|
||||
console.log('move action : validateLocation', );
|
||||
// TODO: remove getModel, checkPolicy and useCapability
|
||||
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.getId());
|
||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
||||
let objectKeystring = this.openmct.objects.makeKeyString(object.identifier);
|
||||
|
||||
if (!parentCandidateKeystring || !currentParentKeystring) {
|
||||
@@ -139,14 +153,12 @@ export default class MoveAction {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parentCandidate.getModel().composition.indexOf(objectKeystring) !== -1) {
|
||||
const parentCandidateComposition = parentCandidate.composition;
|
||||
if (parentCandidateComposition && parentCandidateComposition.indexOf(objectKeystring) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.openmct.composition.checkPolicy(
|
||||
parentCandidate.useCapability('adapter'),
|
||||
object
|
||||
);
|
||||
return parentCandidate && this.openmct.composition.checkPolicy(parentCandidate, object);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,10 @@ export default function () {
|
||||
control: 'file-input',
|
||||
required: true,
|
||||
text: 'Select File...',
|
||||
type: 'application/json'
|
||||
type: 'application/json',
|
||||
property: [
|
||||
"selectFile"
|
||||
]
|
||||
}
|
||||
],
|
||||
initialize: function (domainObject) {
|
||||
|
||||
@@ -345,8 +345,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
mct-form.validates {
|
||||
.form-row.validates {
|
||||
.mct-form {
|
||||
.form-row {
|
||||
> .label {
|
||||
padding-right: 1em; // Keep room for validation element
|
||||
&:after {
|
||||
@@ -363,7 +363,7 @@ mct-form.validates {
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop .form-row.validates > .label {
|
||||
body.desktop .form-row > .label {
|
||||
&:after {
|
||||
position: absolute;
|
||||
right: $interiorMargin;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CreateAction from '../../../platform/commonUI/edit/src/creation/CreateAction';
|
||||
import CreateAction from '@/api/forms/actions/CreateAction';
|
||||
import objectUtils from 'objectUtils';
|
||||
|
||||
export default {
|
||||
@@ -74,20 +74,9 @@ export default {
|
||||
// 4. perform action.
|
||||
return this.openmct.objects.get(this.openmct.router.path[0].identifier)
|
||||
.then((currentObject) => {
|
||||
let legacyContextualParent = this.convertToLegacy(currentObject);
|
||||
let legacyType = this.openmct.$injector.get('typeService').getType(key);
|
||||
let context = {
|
||||
key: "create",
|
||||
domainObject: legacyContextualParent // should be same as parent object.
|
||||
};
|
||||
let action = new CreateAction(
|
||||
legacyType,
|
||||
legacyContextualParent,
|
||||
context,
|
||||
this.openmct
|
||||
);
|
||||
const createAction = new CreateAction(this.openmct, key, currentObject);
|
||||
|
||||
return action.perform();
|
||||
createAction.invoke();
|
||||
});
|
||||
},
|
||||
convertToLegacy(domainObject) {
|
||||
|
||||
Reference in New Issue
Block a user