Compare commits
	
		
			32 Commits
		
	
	
		
			dependabot
			...
			new-link-a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c7da48d493 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 4ba9bcb8f3 | ||
|   | 9fbacfd023 | ||
|   | affa934766 | ||
|   | f203806406 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 6540518daa | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 730efb75ed | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | a2aa0b8ab0 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 8a3864edf6 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | f1b748b46e | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | a2a6d3020c | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 6813f2083a | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | a641996a3f | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 3cae138b6a | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | a44d8ab451 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | af4db1f799 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 38416b9434 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | d27d4aba16 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 354b590852 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 99427b4bed | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 3b3b9368b5 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | a829a14463 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | f42e37930c | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | aeb9dbeb33 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | f9393f80e0 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 28fe4688db | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 50192b1aca | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | c5e3838353 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | a66e77821f | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 068d804a79 | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 05558c02aa | ||
| ![Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]](/assets/img/avatar_default.png)  | 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