Compare commits

...

32 Commits

Author SHA1 Message Date
Jamie Vigliotta
c7da48d493 merged in new form factor code and updated link plugin 2021-06-03 17:44:29 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
4ba9bcb8f3 WIP: form refactor 2021-06-03 17:39:02 -07:00
Jamie Vigliotta
9fbacfd023 checking if possible parent is "locked" 2021-06-03 17:37:18 -07:00
Jamie Vigliotta
affa934766 fixed test 2021-06-03 17:37:18 -07:00
Jamie Vigliotta
f203806406 removed old link action legacy code, added new link action plugin 2021-06-03 17:37:18 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
6540518daa fixed duplicate action to work with new forms 2021-06-03 17:29:56 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
730efb75ed add pattern validation. 2021-06-03 17:29:56 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
a2aa0b8ab0 fixed logic to identify complex properties for creating and editing properties. 2021-06-03 17:29:56 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
8a3864edf6 fixed plan view to show plan file if exists. 2021-06-03 17:29:56 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
f1b748b46e added file input component 2021-06-03 17:29:56 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
a2a6d3020c fixed select field display value 2021-06-03 17:29:56 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
6813f2083a fixed Timer, default timerFormat value 2021-06-03 17:29:56 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
a641996a3f navigate to selected object in save in tree 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
3cae138b6a cleanup 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
a44d8ab451 fixed: If required field value is empty ok button should be disabled. 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
af4db1f799 fixed autocomplete control field and issue with issue with form submit on autocomplete filed select via keyboard. 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
38416b9434 small refactor and cleanup 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
d27d4aba16 Fixed undefined name issue. 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
354b590852 provide domain object to form validate and fixed validate functions. 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
99427b4bed add required field validation + move form submit buttons from overlay to form itself. 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
3b3b9368b5 add css class to form locator 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
a829a14463 add default name value 2021-06-03 17:29:55 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
f42e37930c clean up + fixed small bug 2021-06-03 17:29:54 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
aeb9dbeb33 moved action specific operations into action files. 2021-06-03 17:29:54 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
f9393f80e0 fixed move action 2021-06-03 17:29:54 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
28fe4688db WIP for move action 2021-06-03 17:29:54 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
50192b1aca cleanup 2021-06-03 17:29:54 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
c5e3838353 WIP create new object 2021-06-03 17:29:54 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
a66e77821f fixed composite item value 2021-06-03 17:29:54 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
068d804a79 added textArea, update domainObject 2021-06-03 17:29:54 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
05558c02aa added changes from Shefali and updated form API. 2021-06-03 17:29:54 -07:00
Mandlik, Nikhil K. (ARC-TI)[KBR Wyle Services, LLC]
cc6c57398d WIP: form refactor 2021-06-03 17:29:51 -07:00
39 changed files with 2037 additions and 713 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}
);

View File

@@ -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);
});
});
});
}
);

View File

@@ -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();
});
});
});
}
);

View File

@@ -282,7 +282,7 @@ define([
}
],
"model": {
"timerFormat": "DDD hh:mm:ss"
"timerFormat": "long"
}
}
],

View File

@@ -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());

View File

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

View File

@@ -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,
};
});

View 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
View 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();
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

@@ -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;
});

View File

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

View File

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

View File

@@ -34,7 +34,6 @@ import uuid from 'uuid';
* @constructor
*/
export default class DuplicateTask {
constructor(openmct) {
this.domainObject = undefined;
this.parent = undefined;

View 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
// );
// };
// }
}

View File

@@ -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));
};
}

View 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);
});
});
});

View File

@@ -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);
};
}

View File

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

View File

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

View File

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