mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
72 Commits
plot-filte
...
select-tab
Author | SHA1 | Date | |
---|---|---|---|
faa1d01499 | |||
a9c245a7db | |||
85dd5ce00c | |||
ec86ebd692 | |||
6c69694dca | |||
0b8bf682a4 | |||
e72ba5e8bf | |||
3caba5efac | |||
efdd80bd57 | |||
832c4d9816 | |||
3a5024d38d | |||
dcd6334036 | |||
728b39164e | |||
884aec8ea0 | |||
216f447578 | |||
c38d810658 | |||
f5c48b7bf6 | |||
d0e08f1d9a | |||
72ea7b80fd | |||
35d0c02bc5 | |||
abd7506b45 | |||
526b4aa07e | |||
b5e23963d4 | |||
2c11eb90d4 | |||
90e9c79e19 | |||
1b83631e43 | |||
547d4e82db | |||
3377ad5e0d | |||
1c0df60f05 | |||
138067dca9 | |||
844280eaa5 | |||
d2e2d55caf | |||
f01d4071a1 | |||
06524ce967 | |||
1ec529f360 | |||
cf6458c69d | |||
3316500774 | |||
0f780587c0 | |||
ea69508e22 | |||
4274d8cc0b | |||
1a2048332f | |||
38a875395f | |||
f601ab03e7 | |||
ee1d92d4a9 | |||
548286bacd | |||
9c9006d415 | |||
3219a64d09 | |||
570aa2c02a | |||
c577d2e231 | |||
6bf4b3aba8 | |||
b659f205f7 | |||
40d54df567 | |||
b7fa5c7ba8 | |||
3b0605d17b | |||
d93e7bfd1a | |||
104cd0ed29 | |||
7fb3d86d06 | |||
dbb42e9bb6 | |||
d1baa1f98b | |||
5ab68c0586 | |||
3cf78f509d | |||
c6053e234a | |||
964c326535 | |||
baf410a364 | |||
517a40a32b | |||
8b275b206b | |||
a40a31aa4c | |||
6c0c1df010 | |||
c552afff17 | |||
0837129ad5 | |||
6f3e2a8fbb | |||
4189a05758 |
@ -86,6 +86,7 @@
|
||||
openmct.install(openmct.plugins.LADTable());
|
||||
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
|
||||
openmct.install(openmct.plugins.ObjectMigration());
|
||||
openmct.install(openmct.plugins.GoToOriginalAction());
|
||||
openmct.start();
|
||||
</script>
|
||||
</html>
|
||||
|
@ -55,7 +55,7 @@
|
||||
"node-bourbon": "^4.2.3",
|
||||
"node-sass": "^4.9.2",
|
||||
"painterro": "^0.2.65",
|
||||
"printj": "^1.1.0",
|
||||
"printj": "^1.2.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"request": "^2.69.0",
|
||||
"split": "^1.0.0",
|
||||
|
@ -9,17 +9,17 @@
|
||||
ng-model="ngModel"
|
||||
ng-show="ngModel.progressPerc !== undefined"></mct-include>
|
||||
</div>
|
||||
<div class="c-overlay__button-bar">
|
||||
<button ng-repeat="dialogOption in ngModel.options"
|
||||
</div>
|
||||
<div class="c-overlay__button-bar">
|
||||
<button ng-repeat="dialogOption in ngModel.options"
|
||||
class="c-button"
|
||||
ng-click="dialogOption.callback()">
|
||||
{{dialogOption.label}}
|
||||
</button>
|
||||
<button class="c-button c-button--major"
|
||||
{{dialogOption.label}}
|
||||
</button>
|
||||
<button class="c-button c-button--major"
|
||||
ng-if="ngModel.primaryOption"
|
||||
ng-click="ngModel.primaryOption.callback()">
|
||||
{{ngModel.primaryOption.label}}
|
||||
</button>
|
||||
</div>
|
||||
{{ngModel.primaryOption.label}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -49,7 +49,7 @@ define(
|
||||
name: "Properties",
|
||||
rows: this.properties.map(function (property, index) {
|
||||
// Property definition is same as form row definition
|
||||
var row = Object.create(property.getDefinition());
|
||||
var row = JSON.parse(JSON.stringify(property.getDefinition()));
|
||||
row.key = index;
|
||||
return row;
|
||||
}).filter(function (row) {
|
||||
|
@ -64,7 +64,6 @@ define(
|
||||
* @returns boolean
|
||||
*/
|
||||
EditorCapability.prototype.inEditContext = function () {
|
||||
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
||||
return this.openmct.editor.isEditing();
|
||||
};
|
||||
|
||||
@ -74,7 +73,6 @@ define(
|
||||
* @returns {*}
|
||||
*/
|
||||
EditorCapability.prototype.isEditContextRoot = function () {
|
||||
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
||||
return this.openmct.editor.isEditing();
|
||||
};
|
||||
|
||||
|
@ -66,7 +66,7 @@ define(
|
||||
name: "Properties",
|
||||
rows: this.properties.map(function (property, index) {
|
||||
// Property definition is same as form row definition
|
||||
var row = Object.create(property.getDefinition());
|
||||
var row = JSON.parse(JSON.stringify(property.getDefinition()));
|
||||
|
||||
// Use index as the key into the formValue;
|
||||
// this correlates to the indexing provided by
|
||||
|
@ -77,14 +77,19 @@ define([], function () {
|
||||
return promiseFn().then(nextFn);
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.isScheduled(id)) {
|
||||
this.clearTransactionFns[id] =
|
||||
this.transactionService.addToTransaction(
|
||||
chain(onCommit, release),
|
||||
chain(onCancel, release)
|
||||
);
|
||||
/**
|
||||
* Clear any existing persistence calls for object with given ID. This ensures only the most recent persistence
|
||||
* call is executed. This should prevent stale objects being persisted and overwriting fresh ones.
|
||||
*/
|
||||
if (this.isScheduled(id)) {
|
||||
this.clearTransactionsFor(id);
|
||||
}
|
||||
|
||||
this.clearTransactionFns[id] =
|
||||
this.transactionService.addToTransaction(
|
||||
chain(onCommit, release),
|
||||
chain(onCancel, release)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -93,24 +93,33 @@ define(
|
||||
expect(mockOnCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores subsequent calls for the same object", function () {
|
||||
manager.addToTransaction(
|
||||
testId,
|
||||
jasmine.createSpy(),
|
||||
jasmine.createSpy()
|
||||
);
|
||||
expect(mockTransactionService.addToTransaction.calls.count())
|
||||
.toEqual(1);
|
||||
});
|
||||
describe("Adds callbacks to transaction", function () {
|
||||
beforeEach(function () {
|
||||
spyOn(manager, 'clearTransactionsFor');
|
||||
manager.clearTransactionsFor.and.callThrough();
|
||||
});
|
||||
|
||||
it("accepts subsequent calls for other objects", function () {
|
||||
manager.addToTransaction(
|
||||
'other-id',
|
||||
jasmine.createSpy(),
|
||||
jasmine.createSpy()
|
||||
);
|
||||
expect(mockTransactionService.addToTransaction.calls.count())
|
||||
.toEqual(2);
|
||||
it("and clears pending calls if same object", function () {
|
||||
manager.addToTransaction(
|
||||
testId,
|
||||
jasmine.createSpy(),
|
||||
jasmine.createSpy()
|
||||
);
|
||||
expect(manager.clearTransactionsFor).toHaveBeenCalledWith(testId);
|
||||
});
|
||||
|
||||
it("and does not clear pending calls if different object", function () {
|
||||
manager.addToTransaction(
|
||||
'other-id',
|
||||
jasmine.createSpy(),
|
||||
jasmine.createSpy()
|
||||
);
|
||||
expect(manager.clearTransactionsFor).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
expect(mockTransactionService.addToTransaction.calls.count()).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not remove callbacks from the transaction", function () {
|
||||
|
@ -54,6 +54,7 @@ define(
|
||||
if (isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
var removeSelectable = openmct.selection.selectable(
|
||||
element[0],
|
||||
scope.$eval(attrs.mctSelectable),
|
||||
|
@ -43,23 +43,10 @@ define([], function () {
|
||||
var mutationTopic = topic('mutation');
|
||||
mutationTopic.listen(function (domainObject) {
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
var wasActive = transactionService.isActive();
|
||||
cacheService.put(domainObject.getId(), domainObject.getModel());
|
||||
|
||||
if (hasChanged(domainObject)) {
|
||||
|
||||
if (!wasActive) {
|
||||
transactionService.startTransaction();
|
||||
}
|
||||
|
||||
transactionService.addToTransaction(
|
||||
persistence.persist.bind(persistence),
|
||||
persistence.refresh.bind(persistence)
|
||||
);
|
||||
|
||||
if (!wasActive) {
|
||||
transactionService.commit();
|
||||
}
|
||||
persistence.persist();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -24,22 +24,27 @@ define(
|
||||
["../../src/runs/TransactingMutationListener"],
|
||||
function (TransactingMutationListener) {
|
||||
|
||||
xdescribe("TransactingMutationListener", function () {
|
||||
describe("TransactingMutationListener", function () {
|
||||
var mockTopic,
|
||||
mockMutationTopic,
|
||||
mockCacheService,
|
||||
mockTransactionService,
|
||||
mockDomainObject,
|
||||
mockModel,
|
||||
mockPersistence;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTopic = jasmine.createSpy('topic');
|
||||
mockMutationTopic =
|
||||
jasmine.createSpyObj('mutation', ['listen']);
|
||||
mockCacheService =
|
||||
jasmine.createSpyObj('cacheService', [
|
||||
'put'
|
||||
]);
|
||||
mockTransactionService =
|
||||
jasmine.createSpyObj('transactionService', [
|
||||
'isActive',
|
||||
'startTransaction',
|
||||
'addToTransaction',
|
||||
'commit'
|
||||
]);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
@ -52,18 +57,24 @@ define(
|
||||
);
|
||||
|
||||
mockTopic.and.callFake(function (t) {
|
||||
return (t === 'mutation') && mockMutationTopic;
|
||||
expect(t).toBe('mutation');
|
||||
return mockMutationTopic;
|
||||
});
|
||||
|
||||
mockDomainObject.getId.and.returnValue('mockId');
|
||||
mockDomainObject.getCapability.and.callFake(function (c) {
|
||||
return (c === 'persistence') && mockPersistence;
|
||||
expect(c).toBe('persistence');
|
||||
return mockPersistence;
|
||||
});
|
||||
mockModel = {};
|
||||
mockDomainObject.getModel.and.returnValue(mockModel);
|
||||
|
||||
mockPersistence.persisted.and.returnValue(true);
|
||||
|
||||
return new TransactingMutationListener(
|
||||
mockTopic,
|
||||
mockTransactionService
|
||||
mockTransactionService,
|
||||
mockCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -72,48 +83,27 @@ define(
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
});
|
||||
|
||||
[false, true].forEach(function (isActive) {
|
||||
var verb = isActive ? "is" : "isn't";
|
||||
it("calls persist if the model has changed", function () {
|
||||
mockModel.persisted = Date.now();
|
||||
|
||||
function onlyWhenInactive(expectation) {
|
||||
return isActive ? expectation.not : expectation;
|
||||
}
|
||||
//Mark the model dirty by setting the mutated date later than the last persisted date.
|
||||
mockModel.modified = mockModel.persisted + 1;
|
||||
|
||||
describe("when a transaction " + verb + " active", function () {
|
||||
var innerVerb = isActive ? "does" : "doesn't";
|
||||
mockMutationTopic.listen.calls.mostRecent()
|
||||
.args[0](mockDomainObject);
|
||||
|
||||
beforeEach(function () {
|
||||
mockTransactionService.isActive.and.returnValue(isActive);
|
||||
});
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("and mutation occurs", function () {
|
||||
beforeEach(function () {
|
||||
mockMutationTopic.listen.calls.mostRecent()
|
||||
.args[0](mockDomainObject);
|
||||
});
|
||||
it("does not call persist if the model has not changed", function () {
|
||||
mockModel.persisted = Date.now();
|
||||
|
||||
mockModel.modified = mockModel.persisted;
|
||||
|
||||
it(innerVerb + " start a new transaction", function () {
|
||||
onlyWhenInactive(
|
||||
expect(mockTransactionService.startTransaction)
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
mockMutationTopic.listen.calls.mostRecent()
|
||||
.args[0](mockDomainObject);
|
||||
|
||||
it("adds to the active transaction", function () {
|
||||
expect(mockTransactionService.addToTransaction)
|
||||
.toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it(innerVerb + " immediately commit", function () {
|
||||
onlyWhenInactive(
|
||||
expect(mockTransactionService.commit)
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(mockPersistence.persist).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ define([
|
||||
"./src/actions/MoveAction",
|
||||
"./src/actions/CopyAction",
|
||||
"./src/actions/LinkAction",
|
||||
"./src/actions/GoToOriginalAction",
|
||||
"./src/actions/SetPrimaryLocationAction",
|
||||
"./src/services/LocatingCreationDecorator",
|
||||
"./src/services/LocatingObjectDecorator",
|
||||
@ -41,7 +40,6 @@ define([
|
||||
MoveAction,
|
||||
CopyAction,
|
||||
LinkAction,
|
||||
GoToOriginalAction,
|
||||
SetPrimaryLocationAction,
|
||||
LocatingCreationDecorator,
|
||||
LocatingObjectDecorator,
|
||||
@ -104,14 +102,6 @@ define([
|
||||
"linkService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "follow",
|
||||
"name": "Go To Original",
|
||||
"description": "Go to the original, un-linked instance of this object.",
|
||||
"cssClass": "",
|
||||
"category": "contextual",
|
||||
"implementation": GoToOriginalAction
|
||||
},
|
||||
{
|
||||
"key": "locate",
|
||||
"name": "Set Primary Location",
|
||||
|
@ -1,60 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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 () {
|
||||
|
||||
/**
|
||||
* Implements the "Go To Original" action, which follows a link back
|
||||
* to an original instance of an object.
|
||||
*
|
||||
* @implements {Action}
|
||||
* @constructor
|
||||
* @private
|
||||
* @memberof platform/entanglement
|
||||
* @param {ActionContext} context the context in which the action
|
||||
* will be performed
|
||||
*/
|
||||
function GoToOriginalAction(context) {
|
||||
this.domainObject = context.domainObject;
|
||||
}
|
||||
|
||||
GoToOriginalAction.prototype.perform = function () {
|
||||
return this.domainObject.getCapability("location").getOriginal()
|
||||
.then(function (originalObject) {
|
||||
var actionCapability =
|
||||
originalObject.getCapability("action");
|
||||
return actionCapability &&
|
||||
actionCapability.perform("navigate");
|
||||
});
|
||||
};
|
||||
|
||||
GoToOriginalAction.appliesTo = function (context) {
|
||||
var domainObject = context.domainObject;
|
||||
return domainObject && domainObject.hasCapability("location") &&
|
||||
domainObject.getCapability("location").isLink();
|
||||
};
|
||||
|
||||
return GoToOriginalAction;
|
||||
}
|
||||
);
|
||||
|
@ -1,93 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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/GoToOriginalAction',
|
||||
'../DomainObjectFactory',
|
||||
'../ControlledPromise'
|
||||
],
|
||||
function (GoToOriginalAction, domainObjectFactory, ControlledPromise) {
|
||||
|
||||
describe("The 'go to original' action", function () {
|
||||
var testContext,
|
||||
originalDomainObject,
|
||||
mockLocationCapability,
|
||||
mockOriginalActionCapability,
|
||||
originalPromise,
|
||||
action;
|
||||
|
||||
beforeEach(function () {
|
||||
mockLocationCapability = jasmine.createSpyObj(
|
||||
'location',
|
||||
['isLink', 'isOriginal', 'getOriginal']
|
||||
);
|
||||
mockOriginalActionCapability = jasmine.createSpyObj(
|
||||
'action',
|
||||
['perform', 'getActions']
|
||||
);
|
||||
originalPromise = new ControlledPromise();
|
||||
mockLocationCapability.getOriginal.and.returnValue(originalPromise);
|
||||
mockLocationCapability.isLink.and.returnValue(true);
|
||||
mockLocationCapability.isOriginal.and.callFake(function () {
|
||||
return !mockLocationCapability.isLink();
|
||||
});
|
||||
testContext = {
|
||||
domainObject: domainObjectFactory({
|
||||
capabilities: {
|
||||
location: mockLocationCapability
|
||||
}
|
||||
})
|
||||
};
|
||||
originalDomainObject = domainObjectFactory({
|
||||
capabilities: {
|
||||
action: mockOriginalActionCapability
|
||||
}
|
||||
});
|
||||
|
||||
action = new GoToOriginalAction(testContext);
|
||||
});
|
||||
|
||||
it("is applicable to links", function () {
|
||||
expect(GoToOriginalAction.appliesTo(testContext))
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
it("is not applicable to originals", function () {
|
||||
mockLocationCapability.isLink.and.returnValue(false);
|
||||
expect(GoToOriginalAction.appliesTo(testContext))
|
||||
.toBeFalsy();
|
||||
});
|
||||
|
||||
it("navigates to original objects when performed", function () {
|
||||
expect(mockOriginalActionCapability.perform)
|
||||
.not.toHaveBeenCalled();
|
||||
action.perform();
|
||||
originalPromise.resolve(originalDomainObject);
|
||||
expect(mockOriginalActionCapability.perform)
|
||||
.toHaveBeenCalledWith('navigate');
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -64,12 +64,30 @@ define(['zepto'], function ($) {
|
||||
var tree = this.generateNewIdentifiers(objTree);
|
||||
var rootId = tree.rootId;
|
||||
var rootObj = this.instantiate(tree.openmct[rootId], rootId);
|
||||
var newStyleParent = parent.useCapability('adapter');
|
||||
var newStyleRootObj = rootObj.useCapability('adapter');
|
||||
|
||||
// Instantiate all objects in tree with their newly genereated ids,
|
||||
// adding each to its rightful parent's composition
|
||||
rootObj.getCapability("location").setPrimaryLocation(parent.getId());
|
||||
this.deepInstantiate(rootObj, tree.openmct, []);
|
||||
parent.getCapability("composition").add(rootObj);
|
||||
if (this.openmct.composition.checkPolicy(newStyleParent, newStyleRootObj)) {
|
||||
// Instantiate all objects in tree with their newly generated ids,
|
||||
// adding each to its rightful parent's composition
|
||||
rootObj.getCapability("location").setPrimaryLocation(parent.getId());
|
||||
this.deepInstantiate(rootObj, tree.openmct, []);
|
||||
parent.getCapability("composition").add(rootObj);
|
||||
} else {
|
||||
var dialog = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: "We're sorry, but you cannot import that object type into this object.",
|
||||
buttons: [
|
||||
{
|
||||
label: "Ok",
|
||||
emphasis: true,
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ImportAsJSONAction.prototype.deepInstantiate = function (parent, tree, seen) {
|
||||
@ -80,15 +98,17 @@ define(['zepto'], function ($) {
|
||||
var newObj;
|
||||
|
||||
seen.push(parent.getId());
|
||||
parentModel.composition.forEach(function (childId, index) {
|
||||
if (!tree[childId] || seen.includes(childId)) {
|
||||
|
||||
parentModel.composition.forEach(function (childId) {
|
||||
let keystring = this.openmct.objects.makeKeyString(childId);
|
||||
|
||||
if (!tree[keystring] || seen.includes(keystring)) {
|
||||
return;
|
||||
}
|
||||
|
||||
newObj = this.instantiate(tree[childId], childId);
|
||||
parent.getCapability("composition").add(newObj);
|
||||
newObj = this.instantiate(tree[keystring], keystring);
|
||||
newObj.getCapability("location")
|
||||
.setPrimaryLocation(tree[childId].location);
|
||||
.setPrimaryLocation(tree[keystring].location);
|
||||
this.deepInstantiate(newObj, tree, seen);
|
||||
}, this);
|
||||
}
|
||||
|
@ -33,20 +33,25 @@ export default class LegacyContextMenuAction {
|
||||
}
|
||||
|
||||
invoke(objectPath) {
|
||||
let context = {
|
||||
category: 'contextual',
|
||||
domainObject: this.openmct.legacyObject(objectPath)
|
||||
}
|
||||
let legacyAction = new this.LegacyAction(context);
|
||||
this.openmct.objects.getRoot().then((root) => {
|
||||
let pathWithRoot = objectPath.slice();
|
||||
pathWithRoot.push(root);
|
||||
|
||||
if (!legacyAction.getMetadata) {
|
||||
let metadata = Object.create(this.LegacyAction.definition);
|
||||
metadata.context = context;
|
||||
legacyAction.getMetadata = function () {
|
||||
return metadata;
|
||||
}.bind(legacyAction);
|
||||
}
|
||||
legacyAction.perform();
|
||||
let context = {
|
||||
category: 'contextual',
|
||||
domainObject: this.openmct.legacyObject(pathWithRoot)
|
||||
}
|
||||
let legacyAction = new this.LegacyAction(context);
|
||||
|
||||
if (!legacyAction.getMetadata) {
|
||||
let metadata = Object.create(this.LegacyAction.definition);
|
||||
metadata.context = context;
|
||||
legacyAction.getMetadata = function () {
|
||||
return metadata;
|
||||
}.bind(legacyAction);
|
||||
}
|
||||
legacyAction.perform();
|
||||
});
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
|
@ -36,7 +36,7 @@ define([
|
||||
'./runs/RegisterLegacyTypes',
|
||||
'./services/LegacyObjectAPIInterceptor',
|
||||
'./views/installLegacyViews',
|
||||
'./policies/legacyCompositionPolicyAdapter',
|
||||
'./policies/LegacyCompositionPolicyAdapter',
|
||||
'./actions/LegacyActionAdapter'
|
||||
], function (
|
||||
legacyRegistry,
|
||||
|
@ -57,8 +57,10 @@ define([
|
||||
}.bind(this);
|
||||
|
||||
handleLegacyMutation = function (legacyObject) {
|
||||
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
|
||||
this.eventEmitter.emit(newStyleObject.identifier.key + ":*", newStyleObject);
|
||||
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId()),
|
||||
keystring = utils.makeKeyString(newStyleObject.identifier);
|
||||
|
||||
this.eventEmitter.emit(keystring + ":*", newStyleObject);
|
||||
this.eventEmitter.emit('mutation', newStyleObject);
|
||||
}.bind(this);
|
||||
|
||||
|
@ -45,15 +45,30 @@ define([
|
||||
view: function (domainObject) {
|
||||
let $rootScope = openmct.$injector.get('$rootScope');
|
||||
let templateLinker = openmct.$injector.get('templateLinker');
|
||||
let scope = $rootScope.$new();
|
||||
let scope = $rootScope.$new(true);
|
||||
let legacyObject = convertToLegacyObject(domainObject);
|
||||
let isDestroyed = false;
|
||||
let unlistenToStatus;
|
||||
let element;
|
||||
scope.domainObject = legacyObject;
|
||||
scope.model = legacyObject.getModel();
|
||||
|
||||
let child;
|
||||
let parent;
|
||||
|
||||
return {
|
||||
show: function (container) {
|
||||
parent = container;
|
||||
child = document.createElement('div');
|
||||
parent.appendChild(child);
|
||||
let statusCapability = legacyObject.getCapability('status');
|
||||
unlistenToStatus = statusCapability.listen((newStatus) => {
|
||||
child.classList.remove('s-status-timeconductor-unsynced');
|
||||
|
||||
if (newStatus.includes('timeconductor-unsynced')) {
|
||||
child.classList.add('s-status-timeconductor-unsynced');
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: implement "gestures" support ?
|
||||
let uses = legacyView.uses || [];
|
||||
let promises = [];
|
||||
@ -74,12 +89,13 @@ define([
|
||||
uses.forEach(function (key, i) {
|
||||
scope[key] = results[i];
|
||||
});
|
||||
element = openmct.$angular.element(child);
|
||||
templateLinker.link(
|
||||
scope,
|
||||
openmct.$angular.element(container),
|
||||
element,
|
||||
legacyView
|
||||
);
|
||||
container.classList.add('u-contents');
|
||||
child.classList.add('u-contents');
|
||||
}
|
||||
|
||||
if (promises.length) {
|
||||
@ -93,7 +109,12 @@ define([
|
||||
}
|
||||
},
|
||||
destroy: function () {
|
||||
element.off();
|
||||
element.remove();
|
||||
scope.$destroy();
|
||||
element = null;
|
||||
scope = null;
|
||||
unlistenToStatus();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -25,25 +25,34 @@ define([
|
||||
cssClass: representation.cssClass,
|
||||
description: representation.description,
|
||||
canView: function (selection) {
|
||||
if (!selection[0] || !selection[0].context.item) {
|
||||
if (selection.length !== 1 || selection[0].length === 0) {
|
||||
return false;
|
||||
}
|
||||
let domainObject = selection[0].context.item;
|
||||
return domainObject.type === typeDefinition.key;
|
||||
|
||||
let selectionContext = selection[0][0].context;
|
||||
|
||||
if (!selectionContext.item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selectionContext.item.type === typeDefinition.key;
|
||||
},
|
||||
view: function (selection) {
|
||||
let domainObject = selection[0].context.item;
|
||||
let domainObject = selection[0][0].context.item;
|
||||
let $rootScope = openmct.$injector.get('$rootScope');
|
||||
let templateLinker = openmct.$injector.get('templateLinker');
|
||||
let scope = $rootScope.$new();
|
||||
let scope = $rootScope.$new(true);
|
||||
let legacyObject = convertToLegacyObject(domainObject);
|
||||
let isDestroyed = false;
|
||||
let element;
|
||||
scope.domainObject = legacyObject;
|
||||
scope.model = legacyObject.getModel();
|
||||
|
||||
|
||||
return {
|
||||
show: function (container) {
|
||||
let child = document.createElement('div');
|
||||
container.appendChild(child);
|
||||
// TODO: implement "gestures" support ?
|
||||
let uses = representation.uses || [];
|
||||
let promises = [];
|
||||
@ -64,9 +73,10 @@ define([
|
||||
uses.forEach(function (key, i) {
|
||||
scope[key] = results[i];
|
||||
});
|
||||
element = openmct.$angular.element(child)
|
||||
templateLinker.link(
|
||||
scope,
|
||||
openmct.$angular.element(container),
|
||||
element,
|
||||
representation
|
||||
);
|
||||
container.style.height = '100%';
|
||||
@ -83,7 +93,11 @@ define([
|
||||
}
|
||||
},
|
||||
destroy: function () {
|
||||
element.off();
|
||||
element.remove();
|
||||
scope.$destroy();
|
||||
element = null;
|
||||
scope = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,11 +28,6 @@ export default class Editor extends EventEmitter {
|
||||
super();
|
||||
this.editing = false;
|
||||
this.openmct = openmct;
|
||||
document.addEventListener('drop', (event) => {
|
||||
if (!this.isEditing()) {
|
||||
this.edit();
|
||||
}
|
||||
}, {capture: true});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,9 +74,11 @@ export default class Editor extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
cancel() {
|
||||
this.getTransactionService().cancel();
|
||||
let cancelPromise = this.getTransactionService().cancel();
|
||||
this.editing = false;
|
||||
this.emit('isEditing', false);
|
||||
|
||||
return cancelPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,8 +22,20 @@ define([
|
||||
publicAPI = {};
|
||||
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
|
||||
'get',
|
||||
'mutate'
|
||||
'mutate',
|
||||
'observe',
|
||||
'areIdsEqual'
|
||||
]);
|
||||
|
||||
publicAPI.objects.areIdsEqual.and.callFake(function (id1, id2) {
|
||||
return id1.namespace === id2.namespace && id1.key === id2.key;
|
||||
});
|
||||
|
||||
publicAPI.composition = jasmine.createSpyObj('CompositionAPI', [
|
||||
'checkPolicy'
|
||||
]);
|
||||
publicAPI.composition.checkPolicy.and.returnValue(true);
|
||||
|
||||
publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [
|
||||
'on'
|
||||
]);
|
||||
@ -91,7 +103,7 @@ define([
|
||||
beforeEach(function () {
|
||||
listener = jasmine.createSpy('reorderListener');
|
||||
composition.on('reorder', listener);
|
||||
|
||||
|
||||
return composition.load();
|
||||
});
|
||||
it('', function () {
|
||||
@ -119,49 +131,16 @@ define([
|
||||
expect(newComposition[2].key).toEqual('a');
|
||||
})
|
||||
});
|
||||
|
||||
// TODO: Implement add/removal in new default provider.
|
||||
xit('synchronizes changes between instances', function () {
|
||||
var otherComposition = compositionAPI.get(domainObject);
|
||||
var addListener = jasmine.createSpy('addListener');
|
||||
var removeListener = jasmine.createSpy('removeListener');
|
||||
var otherAddListener = jasmine.createSpy('otherAddListener');
|
||||
var otherRemoveListener = jasmine.createSpy('otherRemoveListener');
|
||||
it('supports adding an object to composition', function () {
|
||||
let addListener = jasmine.createSpy('addListener');
|
||||
let mockChildObject = {
|
||||
identifier: {key: 'mock-key', namespace: ''}
|
||||
};
|
||||
composition.on('add', addListener);
|
||||
composition.on('remove', removeListener);
|
||||
otherComposition.on('add', otherAddListener);
|
||||
otherComposition.on('remove', otherRemoveListener);
|
||||
composition.add(mockChildObject);
|
||||
|
||||
return Promise.all([composition.load(), otherComposition.load()])
|
||||
.then(function () {
|
||||
expect(addListener).toHaveBeenCalled();
|
||||
expect(otherAddListener).toHaveBeenCalled();
|
||||
expect(removeListener).not.toHaveBeenCalled();
|
||||
expect(otherRemoveListener).not.toHaveBeenCalled();
|
||||
|
||||
var object = addListener.calls.mostRecent().args[0];
|
||||
composition.remove(object);
|
||||
expect(removeListener).toHaveBeenCalled();
|
||||
expect(otherRemoveListener).toHaveBeenCalled();
|
||||
|
||||
addListener.reset();
|
||||
otherAddListener.reset();
|
||||
composition.add(object);
|
||||
expect(addListener).toHaveBeenCalled();
|
||||
expect(otherAddListener).toHaveBeenCalled();
|
||||
|
||||
removeListener.reset();
|
||||
otherRemoveListener.reset();
|
||||
otherComposition.remove(object);
|
||||
expect(removeListener).toHaveBeenCalled();
|
||||
expect(otherRemoveListener).toHaveBeenCalled();
|
||||
|
||||
addListener.reset();
|
||||
otherAddListener.reset();
|
||||
otherComposition.add(object);
|
||||
expect(addListener).toHaveBeenCalled();
|
||||
expect(otherAddListener).toHaveBeenCalled();
|
||||
});
|
||||
expect(domainObject.composition.length).toBe(4);
|
||||
expect(domainObject.composition[3]).toEqual(mockChildObject.identifier);
|
||||
});
|
||||
});
|
||||
|
||||
@ -184,7 +163,9 @@ define([
|
||||
key: 'thing'
|
||||
}
|
||||
]);
|
||||
}
|
||||
},
|
||||
add: jasmine.createSpy('add'),
|
||||
remove: jasmine.createSpy('remove')
|
||||
};
|
||||
domainObject = {
|
||||
identifier: {
|
||||
@ -214,6 +195,25 @@ define([
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Calling add or remove', function () {
|
||||
let mockChildObject;
|
||||
|
||||
beforeEach(function () {
|
||||
mockChildObject = {
|
||||
identifier: {key: 'mock-key', namespace: ''}
|
||||
};
|
||||
composition.add(mockChildObject);
|
||||
});
|
||||
|
||||
it('calls add on the provider', function () {
|
||||
expect(customProvider.add).toHaveBeenCalledWith(domainObject, mockChildObject.identifier);
|
||||
});
|
||||
|
||||
it('calls remove on the provider', function () {
|
||||
composition.remove(mockChildObject);
|
||||
expect(customProvider.remove).toHaveBeenCalledWith(domainObject, mockChildObject.identifier);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dynamic custom composition', function () {
|
||||
|
@ -25,7 +25,6 @@ define([
|
||||
], function (
|
||||
_
|
||||
) {
|
||||
|
||||
/**
|
||||
* A CompositionCollection represents the list of domain objects contained
|
||||
* by another domain object. It provides methods for loading this
|
||||
@ -63,7 +62,6 @@ define([
|
||||
this.onProviderRemove = this.onProviderRemove.bind(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listen for changes to this composition. Supports 'add', 'remove', and
|
||||
* 'load' events.
|
||||
@ -76,7 +74,9 @@ define([
|
||||
if (!this.listeners[event]) {
|
||||
throw new Error('Event not supported by composition: ' + event);
|
||||
}
|
||||
|
||||
if (!this.mutationListener) {
|
||||
this._synchronize();
|
||||
}
|
||||
if (this.provider.on && this.provider.off) {
|
||||
if (event === 'add') {
|
||||
this.provider.on(
|
||||
@ -132,6 +132,8 @@ define([
|
||||
|
||||
this.listeners[event].splice(index, 1);
|
||||
if (this.listeners[event].length === 0) {
|
||||
this._destroy();
|
||||
|
||||
// Remove provider listener if this is the last callback to
|
||||
// be removed.
|
||||
if (this.provider.off && this.provider.on) {
|
||||
@ -175,6 +177,9 @@ define([
|
||||
*/
|
||||
CompositionCollection.prototype.add = function (child, skipMutate) {
|
||||
if (!skipMutate) {
|
||||
if (!this.publicAPI.composition.checkPolicy(this.domainObject, child)) {
|
||||
throw `Object of type ${child.type} cannot be added to object of type ${this.domainObject.type}`;
|
||||
}
|
||||
this.provider.add(this.domainObject, child.identifier);
|
||||
} else {
|
||||
this.emit('add', child);
|
||||
@ -266,6 +271,19 @@ define([
|
||||
this.remove(child, true);
|
||||
};
|
||||
|
||||
CompositionCollection.prototype._synchronize = function () {
|
||||
this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => {
|
||||
this.domainObject = JSON.parse(JSON.stringify(newDomainObject));
|
||||
});
|
||||
};
|
||||
|
||||
CompositionCollection.prototype._destroy = function () {
|
||||
if (this.mutationListener) {
|
||||
this.mutationListener();
|
||||
delete this.mutationListener;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emit events.
|
||||
* @private
|
||||
|
@ -48,24 +48,11 @@ define([
|
||||
this.listeningTo = {};
|
||||
this.onMutation = this.onMutation.bind(this);
|
||||
|
||||
this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this);
|
||||
this.cannotContainItself = this.cannotContainItself.bind(this);
|
||||
|
||||
compositionAPI.addPolicy(this.cannotContainDuplicates);
|
||||
compositionAPI.addPolicy(this.cannotContainItself);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
DefaultCompositionProvider.prototype.cannotContainDuplicates = function (parent, child) {
|
||||
return this.appliesTo(parent) &&
|
||||
parent.composition.findIndex((composeeId) => {
|
||||
return composeeId.namespace === child.identifier.namespace &&
|
||||
composeeId.key === child.identifier.key;
|
||||
}) === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@ -199,9 +186,18 @@ define([
|
||||
* @memberof module:openmct.CompositionProvider#
|
||||
* @method add
|
||||
*/
|
||||
DefaultCompositionProvider.prototype.add = function (domainObject, child) {
|
||||
throw new Error('Default Provider does not implement adding.');
|
||||
// TODO: this needs to be synchronized via mutation
|
||||
DefaultCompositionProvider.prototype.add = function (parent, childId) {
|
||||
if (!this.includes(parent, childId)) {
|
||||
parent.composition.push(childId);
|
||||
this.publicAPI.objects.mutate(parent, 'composition', parent.composition);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
DefaultCompositionProvider.prototype.includes = function (parent, childId) {
|
||||
return parent.composition.findIndex(composee =>
|
||||
this.publicAPI.objects.areIdsEqual(composee, childId)) !== -1;
|
||||
};
|
||||
|
||||
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {
|
||||
|
@ -21,8 +21,10 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./object-utils.js',
|
||||
'lodash'
|
||||
], function (
|
||||
utils,
|
||||
_
|
||||
) {
|
||||
var ANY_OBJECT_EVENT = "mutation";
|
||||
@ -41,7 +43,9 @@ define([
|
||||
}
|
||||
|
||||
function qualifiedEventName(object, eventName) {
|
||||
return [object.identifier.key, eventName].join(':');
|
||||
var keystring = utils.makeKeyString(object.identifier);
|
||||
|
||||
return [keystring, eventName].join(':');
|
||||
}
|
||||
|
||||
MutableObject.prototype.stopListening = function () {
|
||||
|
@ -92,6 +92,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
height: 0; // Chrome 73 overflow bug fix
|
||||
overflow: auto;
|
||||
padding-right: $interiorMargin; // fend off scroll bar
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./components/LadTable.vue',
|
||||
'./components/LADTable.vue',
|
||||
'vue'
|
||||
], function (
|
||||
LadTableComponent,
|
||||
|
@ -41,7 +41,7 @@
|
||||
|
||||
<script>
|
||||
import lodash from 'lodash';
|
||||
import LadRow from './LadRow.vue';
|
||||
import LadRow from './LADRow.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
|
@ -52,7 +52,7 @@
|
||||
|
||||
<script>
|
||||
import lodash from 'lodash';
|
||||
import LadRow from './LadRow.vue';
|
||||
import LadRow from './LADRow.vue';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
|
77
src/plugins/displayLayout/AlphanumericFormatViewProvider.js
Normal file
77
src/plugins/displayLayout/AlphanumericFormatViewProvider.js
Normal file
@ -0,0 +1,77 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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([
|
||||
'./components/AlphanumericFormatView.vue',
|
||||
'vue'
|
||||
], function (AlphanumericFormatView, Vue) {
|
||||
|
||||
function AlphanumericFormatViewProvider(openmct, options) {
|
||||
function isTelemetryObject(selectionPath) {
|
||||
let selectedObject = selectionPath[0].context.item;
|
||||
let parentObject = selectionPath[1].context.item;
|
||||
return parentObject &&
|
||||
parentObject.type === 'layout' &&
|
||||
selectedObject &&
|
||||
openmct.telemetry.isTelemetryObject(selectedObject) &&
|
||||
!options.showAsView.includes(selectedObject.type)
|
||||
}
|
||||
|
||||
return {
|
||||
key: 'alphanumeric-format',
|
||||
name: 'Alphanumeric Format',
|
||||
canView: function (selection) {
|
||||
if (selection.length === 0 || selection[0].length === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selection.every(isTelemetryObject);
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
components: {
|
||||
AlphanumericFormatView: AlphanumericFormatView.default
|
||||
},
|
||||
template: '<alphanumeric-format-view></alphanumeric-format-view>',
|
||||
el: element
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AlphanumericFormatViewProvider;
|
||||
});
|
@ -28,11 +28,17 @@ define([], function () {
|
||||
key: "layout",
|
||||
description: "A toolbar for objects inside a display layout.",
|
||||
forSelection: function (selection) {
|
||||
// Apply the layout toolbar if the selected object
|
||||
// is inside a layout, or the main layout is selected.
|
||||
return (selection &&
|
||||
((selection[1] && selection[1].context.item && selection[1].context.item.type === 'layout') ||
|
||||
(selection[0].context.item && selection[0].context.item.type === 'layout')));
|
||||
if (!selection || selection.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let selectionPath = selection[0];
|
||||
let selectedObject = selectionPath[0];
|
||||
let selectedParent = selectionPath[1];
|
||||
|
||||
// Apply the layout toolbar if the selected object is inside a layout, or the main layout is selected.
|
||||
return (selectedParent && selectedParent.context.item && selectedParent.context.item.type === 'layout') ||
|
||||
(selectedObject.context.item && selectedObject.context.item.type === 'layout');
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
const DIALOG_FORM = {
|
||||
@ -73,190 +79,72 @@ define([], function () {
|
||||
return openmct.$injector.get('dialogService').getUserInput(form, {});
|
||||
}
|
||||
|
||||
function getPath() {
|
||||
return `configuration.items[${selection[0].context.index}]`;
|
||||
function getPath(selectionPath) {
|
||||
return `configuration.items[${selectionPath[0].context.index}]`;
|
||||
}
|
||||
|
||||
let selectedParent = selection[1] && selection[1].context.item,
|
||||
selectedObject = selection[0].context.item,
|
||||
layoutItem = selection[0].context.layoutItem,
|
||||
toolbar = [];
|
||||
|
||||
if (selectedObject && selectedObject.type === 'layout') {
|
||||
toolbar.push({
|
||||
control: "menu",
|
||||
domainObject: selectedObject,
|
||||
method: function (option) {
|
||||
let name = option.name.toLowerCase();
|
||||
let form = DIALOG_FORM[name];
|
||||
if (form) {
|
||||
getUserInput(form)
|
||||
.then(element => selection[0].context.addElement(name, element));
|
||||
} else {
|
||||
selection[0].context.addElement(name);
|
||||
}
|
||||
},
|
||||
key: "add",
|
||||
icon: "icon-plus",
|
||||
label: "Add",
|
||||
options: [
|
||||
{
|
||||
"name": "Box",
|
||||
"class": "icon-box-round-corners"
|
||||
},
|
||||
{
|
||||
"name": "Line",
|
||||
"class": "icon-line-horz"
|
||||
},
|
||||
{
|
||||
"name": "Text",
|
||||
"class": "icon-font"
|
||||
},
|
||||
{
|
||||
"name": "Image",
|
||||
"class": "icon-image"
|
||||
}
|
||||
]
|
||||
function getAllTypes(selection) {
|
||||
return selection.filter(selectionPath => {
|
||||
let type = selectionPath[0].context.layoutItem.type;
|
||||
return type === 'text-view' ||
|
||||
type === 'telemetry-view' ||
|
||||
type === 'box-view' ||
|
||||
type === 'image-view' ||
|
||||
type === 'line-view' ||
|
||||
type === 'subobject-view';
|
||||
});
|
||||
}
|
||||
|
||||
if (!layoutItem) {
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
let separator = {
|
||||
control: "separator"
|
||||
};
|
||||
let remove = {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
icon: "icon-trash",
|
||||
title: "Delete the selected object",
|
||||
method: function () {
|
||||
let removeItem = selection[1].context.removeItem;
|
||||
let prompt = openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
|
||||
buttons: [
|
||||
function getAddButton(selection, selectionPath) {
|
||||
if (selection.length === 1) {
|
||||
selectionPath = selectionPath || selection[0];
|
||||
return {
|
||||
control: "menu",
|
||||
domainObject: selectionPath[0].context.item,
|
||||
method: function (option) {
|
||||
let name = option.name.toLowerCase();
|
||||
let form = DIALOG_FORM[name];
|
||||
if (form) {
|
||||
getUserInput(form)
|
||||
.then(element => selectionPath[0].context.addElement(name, element));
|
||||
} else {
|
||||
selectionPath[0].context.addElement(name);
|
||||
}
|
||||
},
|
||||
key: "add",
|
||||
icon: "icon-plus",
|
||||
label: "Add",
|
||||
options: [
|
||||
{
|
||||
label: 'Ok',
|
||||
emphasis: 'true',
|
||||
callback: function () {
|
||||
removeItem(layoutItem, selection[0].context.index);
|
||||
prompt.dismiss();
|
||||
}
|
||||
"name": "Box",
|
||||
"class": "icon-box-round-corners"
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
prompt.dismiss();
|
||||
}
|
||||
"name": "Line",
|
||||
"class": "icon-line-horz"
|
||||
},
|
||||
{
|
||||
"name": "Text",
|
||||
"class": "icon-font"
|
||||
},
|
||||
{
|
||||
"name": "Image",
|
||||
"class": "icon-image"
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
let stackOrder = {
|
||||
control: "menu",
|
||||
domainObject: selectedParent,
|
||||
icon: "icon-layers",
|
||||
title: "Move the selected object above or below other objects",
|
||||
options: [
|
||||
{
|
||||
name: "Move to Top",
|
||||
value: "top",
|
||||
class: "icon-arrow-double-up"
|
||||
},
|
||||
{
|
||||
name: "Move Up",
|
||||
value: "up",
|
||||
class: "icon-arrow-up"
|
||||
},
|
||||
{
|
||||
name: "Move Down",
|
||||
value: "down",
|
||||
class: "icon-arrow-down"
|
||||
},
|
||||
{
|
||||
name: "Move to Bottom",
|
||||
value: "bottom",
|
||||
class: "icon-arrow-double-down"
|
||||
}
|
||||
],
|
||||
method: function (option) {
|
||||
selection[1].context.orderItem(option.value, selection[0].context.index);
|
||||
}
|
||||
};
|
||||
let useGrid = {
|
||||
control: "toggle-button",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".useGrid";
|
||||
},
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
icon: "icon-grid-snap-to",
|
||||
title: "Grid snapping enabled"
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
icon: "icon-grid-snap-no",
|
||||
title: "Grid snapping disabled"
|
||||
}
|
||||
]
|
||||
};
|
||||
let x = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".x";
|
||||
},
|
||||
label: "X:",
|
||||
title: "X position"
|
||||
},
|
||||
y = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".y";
|
||||
},
|
||||
label: "Y:",
|
||||
title: "Y position",
|
||||
},
|
||||
width = {
|
||||
control: 'input',
|
||||
type: 'number',
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".width";
|
||||
},
|
||||
label: 'W:',
|
||||
title: 'Resize object width'
|
||||
},
|
||||
height = {
|
||||
control: 'input',
|
||||
type: 'number',
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".height";
|
||||
},
|
||||
label: 'H:',
|
||||
title: 'Resize object height'
|
||||
};
|
||||
}
|
||||
|
||||
if (layoutItem.type === 'subobject-view') {
|
||||
if (toolbar.length > 0) {
|
||||
toolbar.push(separator);
|
||||
}
|
||||
|
||||
toolbar.push({
|
||||
function getToggleFrameButton(selectedParent, selection) {
|
||||
return {
|
||||
control: "toggle-button",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".hasFrame";
|
||||
applicableSelectedItems: selection.filter(selectionPath =>
|
||||
selectionPath[0].context.layoutItem.type === 'subobject-view'
|
||||
),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".hasFrame";
|
||||
},
|
||||
options: [
|
||||
{
|
||||
@ -270,52 +158,186 @@ define([], function () {
|
||||
title: "Frame hidden"
|
||||
}
|
||||
]
|
||||
});
|
||||
toolbar.push(separator);
|
||||
toolbar.push(stackOrder);
|
||||
toolbar.push(x);
|
||||
toolbar.push(y);
|
||||
toolbar.push(width);
|
||||
toolbar.push(height);
|
||||
toolbar.push(useGrid);
|
||||
toolbar.push(separator);
|
||||
toolbar.push(remove);
|
||||
} else {
|
||||
};
|
||||
}
|
||||
|
||||
function getRemoveButton(selectedParent, selectionPath, selection) {
|
||||
return {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
icon: "icon-trash",
|
||||
title: "Delete the selected object",
|
||||
method: function () {
|
||||
let removeItem = selectionPath[1].context.removeItem;
|
||||
let prompt = openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
|
||||
buttons: [
|
||||
{
|
||||
label: 'Ok',
|
||||
emphasis: 'true',
|
||||
callback: function () {
|
||||
removeItem(getAllTypes(selection));
|
||||
prompt.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: function () {
|
||||
prompt.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getStackOrder(selectedParent, selectionPath) {
|
||||
return {
|
||||
control: "menu",
|
||||
domainObject: selectedParent,
|
||||
icon: "icon-layers",
|
||||
title: "Move the selected object above or below other objects",
|
||||
options: [
|
||||
{
|
||||
name: "Move to Top",
|
||||
value: "top",
|
||||
class: "icon-arrow-double-up"
|
||||
},
|
||||
{
|
||||
name: "Move Up",
|
||||
value: "up",
|
||||
class: "icon-arrow-up"
|
||||
},
|
||||
{
|
||||
name: "Move Down",
|
||||
value: "down",
|
||||
class: "icon-arrow-down"
|
||||
},
|
||||
{
|
||||
name: "Move to Bottom",
|
||||
value: "bottom",
|
||||
class: "icon-arrow-double-down"
|
||||
}
|
||||
],
|
||||
method: function (option) {
|
||||
selectionPath[1].context.orderItem(option.value, getAllTypes(selection));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getXInput(selectedParent, selection) {
|
||||
if (selection.length === 1) {
|
||||
return {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: getAllTypes(selection),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".x";
|
||||
},
|
||||
label: "X:",
|
||||
title: "X position"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getYInput(selectedParent, selection) {
|
||||
if (selection.length === 1) {
|
||||
return {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: getAllTypes(selection),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".y";
|
||||
},
|
||||
label: "Y:",
|
||||
title: "Y position",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getWidthInput(selectedParent, selection) {
|
||||
if (selection.length === 1) {
|
||||
return {
|
||||
control: 'input',
|
||||
type: 'number',
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: getAllTypes(selection),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".width";
|
||||
},
|
||||
label: 'W:',
|
||||
title: 'Resize object width'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getHeightInput(selectedParent, selection) {
|
||||
if (selection.length === 1) {
|
||||
return {
|
||||
control: 'input',
|
||||
type: 'number',
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: getAllTypes(selection),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".height";
|
||||
},
|
||||
label: 'H:',
|
||||
title: 'Resize object height'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getX2Input(selectedParent, selection) {
|
||||
if (selection.length === 1) {
|
||||
return {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
return selectionPath[0].context.layoutItem.type === 'line-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".x2";
|
||||
},
|
||||
label: "X2:",
|
||||
title: "X2 position"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getY2Input(selectedParent, selection) {
|
||||
if (selection.length === 1) {
|
||||
return {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
return selectionPath[0].context.layoutItem.type === 'line-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".y2";
|
||||
},
|
||||
label: "Y2:",
|
||||
title: "Y2 position",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getTextSizeMenu(selectedParent, selection) {
|
||||
const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128];
|
||||
let fill = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".fill";
|
||||
},
|
||||
icon: "icon-paint-bucket",
|
||||
title: "Set fill color"
|
||||
},
|
||||
stroke = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".stroke";
|
||||
},
|
||||
icon: "icon-line-horz",
|
||||
title: "Set border color"
|
||||
},
|
||||
color = {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".color";
|
||||
},
|
||||
icon: "icon-font",
|
||||
mandatory: true,
|
||||
title: "Set text color",
|
||||
preventNone: true
|
||||
},
|
||||
size = {
|
||||
return {
|
||||
control: "select-menu",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".size";
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
let type = selectionPath[0].context.layoutItem.type;
|
||||
return type === 'text-view' || type === 'telemetry-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".size";
|
||||
},
|
||||
title: "Set text size",
|
||||
options: TEXT_SIZE.map(size => {
|
||||
@ -324,13 +346,128 @@ define([], function () {
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
if (layoutItem.type === 'telemetry-view') {
|
||||
let displayMode = {
|
||||
function getFillMenu(selectedParent, selection) {
|
||||
return {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
let type = selectionPath[0].context.layoutItem.type;
|
||||
return type === 'text-view' ||
|
||||
type === 'telemetry-view' ||
|
||||
type === 'box-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".fill";
|
||||
},
|
||||
icon: "icon-paint-bucket",
|
||||
title: "Set fill color"
|
||||
};
|
||||
}
|
||||
|
||||
function getStrokeMenu(selectedParent, selection) {
|
||||
return {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
let type = selectionPath[0].context.layoutItem.type;
|
||||
return type === 'text-view' ||
|
||||
type === 'telemetry-view' ||
|
||||
type === 'box-view' ||
|
||||
type === 'image-view' ||
|
||||
type === 'line-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".stroke";
|
||||
},
|
||||
icon: "icon-line-horz",
|
||||
title: "Set border color"
|
||||
};
|
||||
}
|
||||
|
||||
function getTextColorMenu(selectedParent, selection) {
|
||||
return {
|
||||
control: "color-picker",
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
let type = selectionPath[0].context.layoutItem.type;
|
||||
return type === 'text-view' || type === 'telemetry-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".color";
|
||||
},
|
||||
icon: "icon-font",
|
||||
mandatory: true,
|
||||
title: "Set text color",
|
||||
preventNone: true
|
||||
};
|
||||
}
|
||||
|
||||
function getURLButton(selectedParent, selection) {
|
||||
return {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
return selectionPath[0].context.layoutItem.type === 'image-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath);
|
||||
},
|
||||
icon: "icon-image",
|
||||
title: "Edit image properties",
|
||||
dialog: DIALOG_FORM['image']
|
||||
};
|
||||
}
|
||||
|
||||
function getTextButton(selectedParent, selection) {
|
||||
return {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
return selectionPath[0].context.layoutItem.type === 'text-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath);
|
||||
},
|
||||
icon: "icon-gear",
|
||||
title: "Edit text properties",
|
||||
dialog: DIALOG_FORM['text']
|
||||
};
|
||||
}
|
||||
|
||||
function getTelemetryValueMenu(selectionPath, selection) {
|
||||
if (selection.length === 1) {
|
||||
return {
|
||||
control: "select-menu",
|
||||
domainObject: selectionPath[1].context.item,
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
return selectionPath[0].context.layoutItem.type === 'telemetry-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".value";
|
||||
},
|
||||
title: "Set value",
|
||||
options: openmct.telemetry.getMetadata(selectionPath[0].context.item).values().map(value => {
|
||||
return {
|
||||
name: value.name,
|
||||
value: value.key
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getDisplayModeMenu(selectedParent, selection) {
|
||||
if (selection.length === 1) {
|
||||
return {
|
||||
control: "select-menu",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".displayMode";
|
||||
applicableSelectedItems: selection.filter(selectionPath => {
|
||||
return selectionPath[0].context.layoutItem.type === 'telemetry-view';
|
||||
}),
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath) + ".displayMode";
|
||||
},
|
||||
title: "Set display mode",
|
||||
options: [
|
||||
@ -347,146 +484,196 @@ define([], function () {
|
||||
value: "value"
|
||||
}
|
||||
]
|
||||
},
|
||||
value = {
|
||||
control: "select-menu",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".value";
|
||||
},
|
||||
title: "Set value",
|
||||
options: openmct.telemetry.getMetadata(selectedObject).values().map(value => {
|
||||
return {
|
||||
name: value.name,
|
||||
value: value.key
|
||||
}
|
||||
})
|
||||
};
|
||||
toolbar = [
|
||||
displayMode,
|
||||
separator,
|
||||
value,
|
||||
separator,
|
||||
fill,
|
||||
stroke,
|
||||
color,
|
||||
separator,
|
||||
size,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
useGrid,
|
||||
separator,
|
||||
remove
|
||||
];
|
||||
} else if (layoutItem.type === 'text-view') {
|
||||
let text = {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath();
|
||||
},
|
||||
icon: "icon-gear",
|
||||
title: "Edit text properties",
|
||||
dialog: DIALOG_FORM['text']
|
||||
};
|
||||
toolbar = [
|
||||
fill,
|
||||
stroke,
|
||||
separator,
|
||||
color,
|
||||
size,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
useGrid,
|
||||
separator,
|
||||
text,
|
||||
separator,
|
||||
remove
|
||||
];
|
||||
} else if (layoutItem.type === 'box-view') {
|
||||
toolbar = [
|
||||
fill,
|
||||
stroke,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
useGrid,
|
||||
separator,
|
||||
remove
|
||||
];
|
||||
} else if (layoutItem.type === 'image-view') {
|
||||
let url = {
|
||||
control: "button",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath();
|
||||
},
|
||||
icon: "icon-image",
|
||||
title: "Edit image properties",
|
||||
dialog: DIALOG_FORM['image']
|
||||
};
|
||||
toolbar = [
|
||||
stroke,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width,
|
||||
useGrid,
|
||||
separator,
|
||||
url,
|
||||
separator,
|
||||
remove
|
||||
];
|
||||
} else if (layoutItem.type === 'line-view') {
|
||||
let x2 = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".x2";
|
||||
},
|
||||
label: "X2:",
|
||||
title: "X2 position"
|
||||
},
|
||||
y2 = {
|
||||
control: "input",
|
||||
type: "number",
|
||||
domainObject: selectedParent,
|
||||
property: function () {
|
||||
return getPath() + ".y2";
|
||||
},
|
||||
label: "Y2:",
|
||||
title: "Y2 position",
|
||||
};
|
||||
toolbar = [
|
||||
stroke,
|
||||
separator,
|
||||
stackOrder,
|
||||
x,
|
||||
y,
|
||||
x2,
|
||||
y2,
|
||||
useGrid,
|
||||
separator,
|
||||
remove
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return toolbar;
|
||||
function getSeparator() {
|
||||
return {
|
||||
control: "separator"
|
||||
};
|
||||
}
|
||||
|
||||
function isMainLayoutSelected(selectionPath) {
|
||||
let selectedObject = selectionPath[0].context.item;
|
||||
return selectedObject && selectedObject.type === 'layout' &&
|
||||
!selectionPath[0].context.layoutItem;
|
||||
}
|
||||
|
||||
if (isMainLayoutSelected(selection[0])) {
|
||||
return [getAddButton(selection)];
|
||||
}
|
||||
|
||||
let toolbar = {
|
||||
'add-menu': [],
|
||||
'toggle-frame': [],
|
||||
'display-mode': [],
|
||||
'telemetry-value': [],
|
||||
'style': [],
|
||||
'text-style': [],
|
||||
'position': [],
|
||||
'text': [],
|
||||
'url': [],
|
||||
'remove': [],
|
||||
};
|
||||
|
||||
selection.forEach(selectionPath => {
|
||||
let selectedParent = selectionPath[1].context.item;
|
||||
let layoutItem = selectionPath[0].context.layoutItem;
|
||||
|
||||
if (layoutItem.type === 'subobject-view') {
|
||||
if (toolbar['add-menu'].length === 0 && selectionPath[0].context.item.type === 'layout') {
|
||||
toolbar['add-menu'] = [getAddButton(selection, selectionPath)];
|
||||
}
|
||||
if (toolbar['toggle-frame'].length === 0) {
|
||||
toolbar['toggle-frame'] = [getToggleFrameButton(selectedParent, selection)];
|
||||
}
|
||||
if (toolbar['position'].length === 0) {
|
||||
toolbar['position'] = [
|
||||
getStackOrder(selectedParent, selectionPath),
|
||||
getXInput(selectedParent, selection),
|
||||
getYInput(selectedParent, selection),
|
||||
getHeightInput(selectedParent, selection),
|
||||
getWidthInput(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['remove'].length === 0) {
|
||||
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
|
||||
}
|
||||
} else if (layoutItem.type === 'telemetry-view') {
|
||||
if (toolbar['display-mode'].length === 0) {
|
||||
toolbar['display-mode'] = [getDisplayModeMenu(selectedParent, selection)];
|
||||
}
|
||||
if (toolbar['telemetry-value'].length === 0) {
|
||||
toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selection)];
|
||||
}
|
||||
if (toolbar['style'].length < 2) {
|
||||
toolbar['style'] = [
|
||||
getFillMenu(selectedParent, selection),
|
||||
getStrokeMenu(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['text-style'].length === 0) {
|
||||
toolbar['text-style'] = [
|
||||
getTextColorMenu(selectedParent, selection),
|
||||
getTextSizeMenu(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['position'].length === 0) {
|
||||
toolbar['position'] = [
|
||||
getStackOrder(selectedParent, selectionPath),
|
||||
getXInput(selectedParent, selection),
|
||||
getYInput(selectedParent, selection),
|
||||
getHeightInput(selectedParent, selection),
|
||||
getWidthInput(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['remove'].length === 0) {
|
||||
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
|
||||
}
|
||||
} else if (layoutItem.type === 'text-view') {
|
||||
if (toolbar['style'].length < 2) {
|
||||
toolbar['style'] = [
|
||||
getFillMenu(selectedParent, selection),
|
||||
getStrokeMenu(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['text-style'].length === 0) {
|
||||
toolbar['text-style'] = [
|
||||
getTextColorMenu(selectedParent, selection),
|
||||
getTextSizeMenu(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['position'].length === 0) {
|
||||
toolbar['position'] = [
|
||||
getStackOrder(selectedParent, selectionPath),
|
||||
getXInput(selectedParent, selection),
|
||||
getYInput(selectedParent, selection),
|
||||
getHeightInput(selectedParent, selection),
|
||||
getWidthInput(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['text'].length === 0) {
|
||||
toolbar['text'] = [getTextButton(selectedParent, selection)];
|
||||
}
|
||||
if (toolbar['remove'].length === 0) {
|
||||
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
|
||||
}
|
||||
} else if (layoutItem.type === 'box-view') {
|
||||
if (toolbar['style'].length < 2) {
|
||||
toolbar['style'] = [
|
||||
getFillMenu(selectedParent, selection),
|
||||
getStrokeMenu(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['position'].length === 0) {
|
||||
toolbar['position'] = [
|
||||
getStackOrder(selectedParent, selectionPath),
|
||||
getXInput(selectedParent, selection),
|
||||
getYInput(selectedParent, selection),
|
||||
getHeightInput(selectedParent, selection),
|
||||
getWidthInput(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['remove'].length === 0) {
|
||||
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
|
||||
}
|
||||
} else if (layoutItem.type === 'image-view') {
|
||||
if (toolbar['style'].length === 0) {
|
||||
toolbar['style'] = [
|
||||
getStrokeMenu(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['position'].length === 0) {
|
||||
toolbar['position'] = [
|
||||
getStackOrder(selectedParent, selectionPath),
|
||||
getXInput(selectedParent, selection),
|
||||
getYInput(selectedParent, selection),
|
||||
getHeightInput(selectedParent, selection),
|
||||
getWidthInput(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['url'].length === 0) {
|
||||
toolbar['url'] = [getURLButton(selectedParent, selection)];
|
||||
}
|
||||
if (toolbar['remove'].length === 0) {
|
||||
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
|
||||
}
|
||||
} else if (layoutItem.type === 'line-view') {
|
||||
if (toolbar['style'].length === 0) {
|
||||
toolbar['style'] = [
|
||||
getStrokeMenu(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['position'].length === 0) {
|
||||
toolbar['position'] = [
|
||||
getStackOrder(selectedParent, selectionPath),
|
||||
getXInput(selectedParent, selection),
|
||||
getYInput(selectedParent, selection),
|
||||
getX2Input(selectedParent, selection),
|
||||
getY2Input(selectedParent, selection)
|
||||
];
|
||||
}
|
||||
if (toolbar['remove'].length === 0) {
|
||||
toolbar['remove'] = [getRemoveButton(selectedParent, selectionPath, selection)];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let toolbarArray = Object.values(toolbar);
|
||||
return _.flatten(toolbarArray.reduce((accumulator, group, index) => {
|
||||
group = group.filter(control => control !== undefined);
|
||||
|
||||
if (group.length > 0) {
|
||||
accumulator.push(group);
|
||||
|
||||
if (index < toolbarArray.length - 1) {
|
||||
accumulator.push(getSeparator());
|
||||
}
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
}, []));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ define(
|
||||
* @param {number[]} pixelDelta the offset from the
|
||||
* original position, in pixels
|
||||
*/
|
||||
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
|
||||
LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) {
|
||||
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
|
||||
return {
|
||||
position: max(add(
|
||||
@ -109,6 +109,16 @@ define(
|
||||
};
|
||||
};
|
||||
|
||||
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
|
||||
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
|
||||
return {
|
||||
position: max(add(
|
||||
this.rawPosition.position,
|
||||
multiply(gridDelta, this.posFactor)
|
||||
), [0, 0])
|
||||
};
|
||||
};
|
||||
|
||||
return LayoutDrag;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,90 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-properties" v-if="isEditing">
|
||||
<div class="c-properties__header">Alphanumeric Format</div>
|
||||
<ul class="c-properties__section">
|
||||
<li class="c-properties__row">
|
||||
<div class="c-properties__label" title="Printf formatting for the selected telemetry">
|
||||
<label for="telemetryPrintfFormat">Format</label>
|
||||
</div>
|
||||
<div class="c-properties__value">
|
||||
<input id="telemetryPrintfFormat"
|
||||
type="text"
|
||||
@change="formatTelemetry"
|
||||
:value="telemetryFormat"
|
||||
:placeholder="nonMixedFormat ? '' : 'Mixed'"
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
let selectionPath = this.openmct.selection.get()[0];
|
||||
return {
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
telemetryFormat: undefined,
|
||||
nonMixedFormat: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleEdit(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
},
|
||||
formatTelemetry(event) {
|
||||
let newFormat = event.currentTarget.value;
|
||||
this.openmct.selection.get().forEach(selectionPath => {
|
||||
selectionPath[0].context.updateTelemetryFormat(newFormat);
|
||||
});
|
||||
this.telemetryFormat = newFormat;
|
||||
},
|
||||
handleSelection(selection) {
|
||||
if (selection.length === 0 || selection[0].length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
let format = selection[0][0].context.layoutItem.format;
|
||||
this.nonMixedFormat = selection.every(selectionPath => {
|
||||
return selectionPath[0].context.layoutItem.format === format;
|
||||
});
|
||||
|
||||
this.telemetryFormat = this.nonMixedFormat ? format : '';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.editor.on('isEditing', this.toggleEdit);
|
||||
this.openmct.selection.on('change', this.handleSelection);
|
||||
this.handleSelection(this.openmct.selection.get());
|
||||
},
|
||||
destroyed() {
|
||||
this.openmct.editor.off('isEditing', this.toggleEdit);
|
||||
this.openmct.selection.off('change', this.handleSelection);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
@ -23,7 +23,8 @@
|
||||
<template>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')">
|
||||
<div class="c-box-view"
|
||||
:style="style">
|
||||
</div>
|
||||
@ -54,8 +55,7 @@
|
||||
x: 1,
|
||||
y: 1,
|
||||
width: 10,
|
||||
height: 5,
|
||||
useGrid: true
|
||||
height: 5
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
|
@ -23,8 +23,11 @@
|
||||
<template>
|
||||
<div class="l-layout"
|
||||
@dragover="handleDragOver"
|
||||
@click="bypassSelection"
|
||||
@drop="handleDrop">
|
||||
@click.capture="bypassSelection"
|
||||
@drop="handleDrop"
|
||||
:class="{
|
||||
'is-multi-selected': selectedLayoutItems.length > 1
|
||||
}">
|
||||
<!-- Background grid -->
|
||||
<div class="l-layout__grid-holder c-grid">
|
||||
<div class="c-grid__x l-grid l-grid-x"
|
||||
@ -39,18 +42,39 @@
|
||||
:is="item.type"
|
||||
:item="item"
|
||||
:key="item.id"
|
||||
:gridSize="item.useGrid ? gridSize : [1, 1]"
|
||||
:gridSize="gridSize"
|
||||
:initSelect="initSelectIndex === index"
|
||||
:index="index"
|
||||
@endDrag="endDrag"
|
||||
>
|
||||
:multiSelect="selectedLayoutItems.length > 1"
|
||||
@move="move"
|
||||
@endMove="endMove"
|
||||
@endLineResize='endLineResize'
|
||||
@formatChanged='updateTelemetryFormat'>
|
||||
</component>
|
||||
<edit-marquee v-if='showMarquee'
|
||||
:gridSize="gridSize"
|
||||
:selectedLayoutItems="selectedLayoutItems"
|
||||
@endResize="endResize">
|
||||
</edit-marquee>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
@mixin displayMarquee($c) {
|
||||
> .c-frame-edit {
|
||||
// All other frames
|
||||
//@include test($c, 0.4);
|
||||
display: block;
|
||||
}
|
||||
> .c-frame > .c-frame-edit {
|
||||
// Line object frame
|
||||
//@include test($c, 0.4);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.l-layout {
|
||||
@include abs();
|
||||
display: flex;
|
||||
@ -70,7 +94,7 @@
|
||||
.l-shell__main-container {
|
||||
&[s-selected],
|
||||
&[s-selected-parent] {
|
||||
// Display grid in main layout holder when editing
|
||||
// Display grid and allow edit marquee to display in main layout holder when editing
|
||||
> .l-layout {
|
||||
background: $editUIGridColorBg;
|
||||
|
||||
@ -84,7 +108,7 @@
|
||||
.l-layout__frame {
|
||||
&[s-selected],
|
||||
&[s-selected-parent] {
|
||||
// Display grid in nested layouts when editing
|
||||
// Display grid and allow edit marquee to display in nested layouts when editing
|
||||
> * > * > .l-layout {
|
||||
background: $editUIGridColorBg;
|
||||
box-shadow: inset $editUIGridColorFg 0 0 2px 1px;
|
||||
@ -95,10 +119,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********************** EDIT MARQUEE CONTROL */
|
||||
*[s-selected-parent] {
|
||||
> .l-layout {
|
||||
// When main shell layout is the parent
|
||||
@include displayMarquee(deeppink);
|
||||
}
|
||||
> * > * > * {
|
||||
// When a sub-layout is the parent
|
||||
@include displayMarquee(blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import uuid from 'uuid';
|
||||
|
||||
@ -108,6 +143,7 @@
|
||||
import TextView from './TextView.vue'
|
||||
import LineView from './LineView.vue'
|
||||
import ImageView from './ImageView.vue'
|
||||
import EditMarquee from './EditMarquee.vue'
|
||||
|
||||
const ITEM_TYPE_VIEW_MAP = {
|
||||
'subobject-view': SubobjectView,
|
||||
@ -123,9 +159,11 @@
|
||||
down: -1,
|
||||
bottom: Number.NEGATIVE_INFINITY
|
||||
};
|
||||
|
||||
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
|
||||
|
||||
let components = ITEM_TYPE_VIEW_MAP;
|
||||
components['edit-marquee'] = EditMarquee;
|
||||
|
||||
function getItemDefinition(itemType, ...options) {
|
||||
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
|
||||
|
||||
@ -141,7 +179,8 @@
|
||||
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
|
||||
return {
|
||||
internalDomainObject: domainObject,
|
||||
initSelectIndex: undefined
|
||||
initSelectIndex: undefined,
|
||||
selection: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -150,82 +189,145 @@
|
||||
},
|
||||
layoutItems() {
|
||||
return this.internalDomainObject.configuration.items;
|
||||
},
|
||||
selectedLayoutItems() {
|
||||
return this.layoutItems.filter(item => {
|
||||
return this.itemIsInCurrentSelection(item);
|
||||
});
|
||||
},
|
||||
showMarquee() {
|
||||
let selectionPath = this.selection[0];
|
||||
let singleSelectedLine = this.selection.length === 1 &&
|
||||
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view';
|
||||
return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
|
||||
}
|
||||
},
|
||||
inject: ['openmct', 'options'],
|
||||
props: ['domainObject'],
|
||||
components: ITEM_TYPE_VIEW_MAP,
|
||||
components: components,
|
||||
methods: {
|
||||
addElement(itemType, element) {
|
||||
this.addItem(itemType + '-view', element);
|
||||
},
|
||||
setSelection(selection) {
|
||||
if (selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.removeSelectionListener) {
|
||||
this.removeSelectionListener();
|
||||
}
|
||||
|
||||
let itemIndex = selection[0].context.index;
|
||||
|
||||
if (itemIndex !== undefined) {
|
||||
this.attachSelectionListener(itemIndex);
|
||||
}
|
||||
this.selection = selection;
|
||||
},
|
||||
attachSelectionListener(index) {
|
||||
let path = `configuration.items[${index}].useGrid`;
|
||||
this.removeSelectionListener = this.openmct.objects.observe(this.internalDomainObject, path, function (value) {
|
||||
let item = this.layoutItems[index];
|
||||
|
||||
if (value) {
|
||||
item.x = Math.round(item.x / this.gridSize[0]);
|
||||
item.y = Math.round(item.y / this.gridSize[1]);
|
||||
item.width = Math.round(item.width / this.gridSize[0]);
|
||||
item.height = Math.round(item.height / this.gridSize[1]);
|
||||
|
||||
if (item.x2) {
|
||||
item.x2 = Math.round(item.x2 / this.gridSize[0]);
|
||||
}
|
||||
if (item.y2) {
|
||||
item.y2 = Math.round(item.y2 / this.gridSize[1]);
|
||||
}
|
||||
} else {
|
||||
item.x = this.gridSize[0] * item.x;
|
||||
item.y = this.gridSize[1] * item.y;
|
||||
item.width = this.gridSize[0] * item.width;
|
||||
item.height = this.gridSize[1] * item.height;
|
||||
|
||||
if (item.x2) {
|
||||
item.x2 = this.gridSize[0] * item.x2;
|
||||
}
|
||||
if (item.y2) {
|
||||
item.y2 = this.gridSize[1] * item.y2;
|
||||
}
|
||||
}
|
||||
item.useGrid = value;
|
||||
this.mutate(`configuration.items[${index}]`, item);
|
||||
}.bind(this));
|
||||
itemIsInCurrentSelection(item) {
|
||||
return this.selection.some(selectionPath =>
|
||||
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.id === item.id);
|
||||
},
|
||||
bypassSelection($event) {
|
||||
if (this.dragInProgress) {
|
||||
if ($event) {
|
||||
$event.stopImmediatePropagation();
|
||||
}
|
||||
this.dragInProgress = false;
|
||||
return;
|
||||
}
|
||||
},
|
||||
endDrag(item, updates) {
|
||||
endLineResize(item, updates) {
|
||||
this.dragInProgress = true;
|
||||
setTimeout(function () {
|
||||
this.dragInProgress = false;
|
||||
}.bind(this), 0);
|
||||
|
||||
let index = this.layoutItems.indexOf(item);
|
||||
Object.assign(item, updates);
|
||||
this.mutate(`configuration.items[${index}]`, item);
|
||||
},
|
||||
endResize(scaleWidth, scaleHeight, marqueeStart, marqueeOffset) {
|
||||
this.dragInProgress = true;
|
||||
this.layoutItems.forEach(item => {
|
||||
if (this.itemIsInCurrentSelection(item)) {
|
||||
let itemXInMarqueeSpace = item.x - marqueeStart.x;
|
||||
let itemXInMarqueeSpaceAfterScale = Math.round(itemXInMarqueeSpace * scaleWidth);
|
||||
item.x = itemXInMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x;
|
||||
|
||||
let itemYInMarqueeSpace = item.y - marqueeStart.y;
|
||||
let itemYInMarqueeSpaceAfterScale = Math.round(itemYInMarqueeSpace * scaleHeight);
|
||||
item.y = itemYInMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
|
||||
|
||||
if (item.x2) {
|
||||
let itemX2InMarqueeSpace = item.x2 - marqueeStart.x;
|
||||
let itemX2InMarqueeSpaceAfterScale = Math.round(itemX2InMarqueeSpace * scaleWidth);
|
||||
item.x2 = itemX2InMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x;
|
||||
} else {
|
||||
item.width = Math.round(item.width * scaleWidth);
|
||||
}
|
||||
|
||||
if (item.y2) {
|
||||
let itemY2InMarqueeSpace = item.y2 - marqueeStart.y;
|
||||
let itemY2InMarqueeSpaceAfterScale = Math.round(itemY2InMarqueeSpace * scaleHeight);
|
||||
item.y2 = itemY2InMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
|
||||
} else {
|
||||
item.height = Math.round(item.height * scaleHeight);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.mutate("configuration.items", this.layoutItems);
|
||||
},
|
||||
move(gridDelta) {
|
||||
this.dragInProgress = true;
|
||||
|
||||
if (!this.initialPositions) {
|
||||
this.initialPositions = {};
|
||||
_.cloneDeep(this.selectedLayoutItems).forEach(selectedItem => {
|
||||
if (selectedItem.type === 'line-view') {
|
||||
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y, selectedItem.x2, selectedItem.y2];
|
||||
this.startingMinX2 = this.startingMinX2 !== undefined ? Math.min(this.startingMinX2, selectedItem.x2) : selectedItem.x2;
|
||||
this.startingMinY2 = this.startingMinY2 !== undefined ? Math.min(this.startingMinY2, selectedItem.y2) : selectedItem.y2;
|
||||
} else {
|
||||
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y];
|
||||
}
|
||||
|
||||
this.startingMinX = this.startingMinX !== undefined ? Math.min(this.startingMinX, selectedItem.x) : selectedItem.x;
|
||||
this.startingMinY = this.startingMinY !== undefined ? Math.min(this.startingMinY, selectedItem.y) : selectedItem.y;
|
||||
});
|
||||
}
|
||||
|
||||
let layoutItems = this.layoutItems.map(item => {
|
||||
if (this.initialPositions[item.id]) {
|
||||
this.updateItemPosition(item, gridDelta);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
},
|
||||
updateItemPosition(item, gridDelta) {
|
||||
let startingPosition = this.initialPositions[item.id];
|
||||
let [startingX, startingY, startingX2, startingY2] = startingPosition;
|
||||
|
||||
if (this.startingMinX + gridDelta[0] >= 0) {
|
||||
if (item.x2 !== undefined) {
|
||||
if (this.startingMinX2 + gridDelta[0] >= 0) {
|
||||
item.x = startingX + gridDelta[0];
|
||||
}
|
||||
} else {
|
||||
item.x = startingX + gridDelta[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.startingMinY + gridDelta[1] >= 0) {
|
||||
if (item.y2 !== undefined) {
|
||||
if (this.startingMinY2 + gridDelta[1] >= 0) {
|
||||
item.y = startingY + gridDelta[1];
|
||||
}
|
||||
} else {
|
||||
item.y = startingY + gridDelta[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (item.x2 !== undefined && this.startingMinX2 + gridDelta[0] >= 0 && this.startingMinX + gridDelta[0] >= 0) {
|
||||
item.x2 = startingX2 + gridDelta[0];
|
||||
}
|
||||
|
||||
if (item.y2 !== undefined && this.startingMinY2 + gridDelta[1] >= 0 && this.startingMinY + gridDelta[1] >= 0) {
|
||||
item.y2 = startingY2 + gridDelta[1];
|
||||
}
|
||||
},
|
||||
endMove() {
|
||||
this.mutate('configuration.items', this.layoutItems);
|
||||
this.initialPositions = undefined;
|
||||
this.startingMinX = undefined;
|
||||
this.startingMinY = undefined;
|
||||
this.startingMinX2 = undefined;
|
||||
this.startingMinY2 = undefined;
|
||||
},
|
||||
mutate(path, value) {
|
||||
this.openmct.objects.mutate(this.internalDomainObject, path, value);
|
||||
},
|
||||
@ -313,11 +415,15 @@
|
||||
this.objectViewMap[keyString] = true;
|
||||
}
|
||||
},
|
||||
removeItem(item, index) {
|
||||
removeItem(selectedItems) {
|
||||
let indices = [];
|
||||
this.initSelectIndex = -1;
|
||||
this.layoutItems.splice(index, 1);
|
||||
selectedItems.forEach(selectedItem => {
|
||||
indices.push(selectedItem[0].context.index);
|
||||
this.untrackItem(selectedItem[0].context.layoutItem);
|
||||
});
|
||||
_.pullAt(this.layoutItems, indices);
|
||||
this.mutate("configuration.items", this.layoutItems);
|
||||
this.untrackItem(item);
|
||||
this.$el.click();
|
||||
},
|
||||
untrackItem(item) {
|
||||
@ -383,21 +489,80 @@
|
||||
this.mutate("configuration.items", layoutItems);
|
||||
this.$el.click();
|
||||
},
|
||||
orderItem(position, index) {
|
||||
orderItem(position, selectedItems) {
|
||||
let delta = ORDERS[position];
|
||||
let newIndex = Math.max(Math.min(index + delta, this.layoutItems.length - 1), 0);
|
||||
let item = this.layoutItems[index];
|
||||
let indices = [];
|
||||
let newIndex = -1;
|
||||
let items = [];
|
||||
|
||||
if (newIndex !== index) {
|
||||
this.layoutItems.splice(index, 1);
|
||||
this.layoutItems.splice(newIndex, 0, item);
|
||||
this.mutate('configuration.items', this.layoutItems);
|
||||
Object.assign(items, this.layoutItems);
|
||||
this.selectedLayoutItems.forEach(selectedItem => {
|
||||
indices.push(this.layoutItems.indexOf(selectedItem));
|
||||
});
|
||||
indices.sort((a, b) => a - b);
|
||||
|
||||
if (this.removeSelectionListener) {
|
||||
this.removeSelectionListener();
|
||||
this.attachSelectionListener(newIndex);
|
||||
}
|
||||
if (position === 'top' || position === 'up') {
|
||||
indices.reverse();
|
||||
}
|
||||
|
||||
if (position === 'top' || position === 'bottom') {
|
||||
this.moveToTopOrBottom(position, indices, items, delta);
|
||||
} else {
|
||||
this.moveUpOrDown(position, indices, items, delta);
|
||||
}
|
||||
|
||||
this.mutate('configuration.items', this.layoutItems);
|
||||
},
|
||||
moveUpOrDown(position, indices, items, delta) {
|
||||
let previousItemIndex = -1;
|
||||
let newIndex = -1;
|
||||
|
||||
indices.forEach((itemIndex, index) => {
|
||||
let isAdjacentItemSelected = position === 'up' ?
|
||||
itemIndex + 1 === previousItemIndex :
|
||||
itemIndex - 1 === previousItemIndex;
|
||||
|
||||
if (index > 0 && isAdjacentItemSelected) {
|
||||
if (position === 'up') {
|
||||
newIndex -= 1;
|
||||
} else {
|
||||
newIndex += 1;
|
||||
}
|
||||
} else {
|
||||
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
|
||||
}
|
||||
|
||||
previousItemIndex = itemIndex;
|
||||
this.updateItemOrder(newIndex, itemIndex, items);
|
||||
});
|
||||
},
|
||||
moveToTopOrBottom(position, indices, items, delta) {
|
||||
let newIndex = -1;
|
||||
|
||||
indices.forEach((itemIndex, index) => {
|
||||
if (index === 0) {
|
||||
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
|
||||
} else {
|
||||
if (position === 'top') {
|
||||
newIndex -= 1;
|
||||
} else {
|
||||
newIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateItemOrder(newIndex, itemIndex, items);
|
||||
});
|
||||
},
|
||||
updateItemOrder(newIndex, itemIndex, items) {
|
||||
if (newIndex !== itemIndex) {
|
||||
this.layoutItems.splice(itemIndex, 1);
|
||||
this.layoutItems.splice(newIndex, 0, items[itemIndex]);
|
||||
}
|
||||
},
|
||||
updateTelemetryFormat(item, format) {
|
||||
let index = _.findIndex(this.layoutItems, item);
|
||||
item.format = format;
|
||||
this.mutate(`configuration.items[${index}]`, item);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -412,14 +577,10 @@
|
||||
this.composition.load();
|
||||
},
|
||||
destroyed: function () {
|
||||
this.openmct.off('change', this.setSelection);
|
||||
this.openmct.selection.off('change', this.setSelection);
|
||||
this.composition.off('add', this.addChild);
|
||||
this.composition.off('remove', this.removeChild);
|
||||
this.unlisten();
|
||||
|
||||
if (this.removeSelectionListener) {
|
||||
this.removeSelectionListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
233
src/plugins/displayLayout/components/EditMarquee.vue
Normal file
233
src/plugins/displayLayout/components/EditMarquee.vue
Normal file
@ -0,0 +1,233 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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>
|
||||
<!-- Resize handles -->
|
||||
<div class="c-frame-edit" :style="style">
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
|
||||
@mousedown="startResize([1,1], [-1,-1], $event)"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
|
||||
@mousedown="startResize([0,1], [1,-1], $event)"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
|
||||
@mousedown="startResize([1,0], [-1,1], $event)"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--se"
|
||||
@mousedown="startResize([0,0], [1,1], $event)"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.c-frame-edit {
|
||||
// In Layouts, this is the editing rect and handles
|
||||
display: none; // Set to display: block in DisplayLayout.vue
|
||||
pointer-events: none;
|
||||
@include abs();
|
||||
border: $editMarqueeBorder;
|
||||
|
||||
&__handle {
|
||||
$d: 6px;
|
||||
$o: floor($d * -0.5);
|
||||
background: $editFrameColorHandleFg;
|
||||
box-shadow: $editFrameColorHandleBg 0 0 0 2px;
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
width: $d; height: $d;
|
||||
top: auto; right: auto; bottom: auto; left: auto;
|
||||
|
||||
&:before {
|
||||
// Extended hit area
|
||||
@include abs(-10px);
|
||||
content: '';
|
||||
display: block;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $editUIColor;
|
||||
}
|
||||
|
||||
&--nwse {
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
&--nw {
|
||||
cursor: nw-resize;
|
||||
left: $o; top: $o;
|
||||
}
|
||||
|
||||
&--ne {
|
||||
cursor: ne-resize;
|
||||
right: $o; top: $o;
|
||||
}
|
||||
|
||||
&--se {
|
||||
cursor: se-resize;
|
||||
right: $o; bottom: $o;
|
||||
}
|
||||
|
||||
&--sw {
|
||||
cursor: sw-resize;
|
||||
left: $o; bottom: $o;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import LayoutDrag from './../LayoutDrag'
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
selectedLayoutItems: Array,
|
||||
gridSize: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragPosition: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
let x = Number.POSITIVE_INFINITY;
|
||||
let y = Number.POSITIVE_INFINITY;
|
||||
let width = Number.NEGATIVE_INFINITY;
|
||||
let height = Number.NEGATIVE_INFINITY;
|
||||
|
||||
this.selectedLayoutItems.forEach(item => {
|
||||
if (item.x2 !== undefined) {
|
||||
let lineWidth = Math.abs(item.x - item.x2);
|
||||
let lineMinX = Math.min(item.x, item.x2);
|
||||
x = Math.min(lineMinX, x);
|
||||
width = Math.max(lineWidth + lineMinX, width);
|
||||
} else {
|
||||
x = Math.min(item.x, x);
|
||||
width = Math.max(item.width + item.x, width);
|
||||
}
|
||||
|
||||
if (item.y2 !== undefined) {
|
||||
let lineHeight = Math.abs(item.y - item.y2);
|
||||
let lineMinY = Math.min(item.y, item.y2);
|
||||
y = Math.min(lineMinY, y);
|
||||
height = Math.max(lineHeight + lineMinY, height);
|
||||
} else {
|
||||
y = Math.min(item.y, y);
|
||||
height = Math.max(item.height + item.y, height);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.dragPosition) {
|
||||
[x, y] = this.dragPosition.position;
|
||||
[width, height] = this.dragPosition.dimensions;
|
||||
} else {
|
||||
width = width - x;
|
||||
height = height - y;
|
||||
}
|
||||
|
||||
this.marqueePosition = {
|
||||
x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height
|
||||
}
|
||||
return this.getMarqueeStyle(x, y, width, height);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getMarqueeStyle(x, y, width, height) {
|
||||
return {
|
||||
left: (this.gridSize[0] * x) + 'px',
|
||||
top: (this.gridSize[1] * y) + 'px',
|
||||
width: (this.gridSize[0] * width) + 'px',
|
||||
height: (this.gridSize[1] * height) + 'px'
|
||||
};
|
||||
},
|
||||
updatePosition(event) {
|
||||
let currentPosition = [event.pageX, event.pageY];
|
||||
this.initialPosition = this.initialPosition || currentPosition;
|
||||
this.delta = currentPosition.map(function (value, index) {
|
||||
return value - this.initialPosition[index];
|
||||
}.bind(this));
|
||||
},
|
||||
startResize(posFactor, dimFactor, event) {
|
||||
document.body.addEventListener('mousemove', this.continueResize);
|
||||
document.body.addEventListener('mouseup', this.endResize);
|
||||
this.marqueeStartPosition = {
|
||||
position: [this.marqueePosition.x, this.marqueePosition.y],
|
||||
dimensions: [this.marqueePosition.width, this.marqueePosition.height]
|
||||
};
|
||||
this.updatePosition(event);
|
||||
this.activeDrag = new LayoutDrag(this.marqueeStartPosition, posFactor, dimFactor, this.gridSize);
|
||||
event.preventDefault();
|
||||
},
|
||||
continueResize(event) {
|
||||
event.preventDefault();
|
||||
this.updatePosition(event);
|
||||
this.dragPosition = this.activeDrag.getAdjustedPositionAndDimensions(this.delta);
|
||||
},
|
||||
endResize(event) {
|
||||
document.body.removeEventListener('mousemove', this.continueResize);
|
||||
document.body.removeEventListener('mouseup', this.endResize);
|
||||
this.continueResize(event);
|
||||
|
||||
let marqueeStartWidth = this.marqueeStartPosition.dimensions[0];
|
||||
let marqueeStartHeight = this.marqueeStartPosition.dimensions[1];
|
||||
let marqueeStartX = this.marqueeStartPosition.position[0];
|
||||
let marqueeStartY = this.marqueeStartPosition.position[1];
|
||||
|
||||
let marqueeEndX = this.dragPosition.position[0];
|
||||
let marqueeEndY = this.dragPosition.position[1];
|
||||
let marqueeEndWidth = this.dragPosition.dimensions[0];
|
||||
let marqueeEndHeight = this.dragPosition.dimensions[1];
|
||||
|
||||
let scaleWidth = marqueeEndWidth / marqueeStartWidth;
|
||||
let scaleHeight = marqueeEndHeight / marqueeStartHeight;
|
||||
|
||||
let marqueeStart = {
|
||||
x: marqueeStartX,
|
||||
y: marqueeStartY,
|
||||
height: marqueeStartWidth,
|
||||
width: marqueeStartHeight
|
||||
};
|
||||
let marqueeEnd = {
|
||||
x: marqueeEndX,
|
||||
y: marqueeEndY,
|
||||
width: marqueeEndWidth,
|
||||
height: marqueeEndHeight
|
||||
};
|
||||
let marqueeOffset = {
|
||||
x: marqueeEnd.x - marqueeStart.x,
|
||||
y: marqueeEnd.y - marqueeStart.y
|
||||
};
|
||||
|
||||
this.$emit('endResize', scaleWidth, scaleHeight, marqueeStart, marqueeOffset);
|
||||
this.dragPosition = undefined;
|
||||
this.initialPosition = undefined;
|
||||
this.marqueeStartPosition = undefined;
|
||||
this.delta = undefined;
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -23,7 +23,8 @@
|
||||
<template>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')">
|
||||
<div class="c-image-view"
|
||||
:style="style">
|
||||
</div>
|
||||
@ -56,8 +57,7 @@
|
||||
y: 1,
|
||||
width: 10,
|
||||
height: 5,
|
||||
url: element.url,
|
||||
useGrid: true
|
||||
url: element.url
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
|
@ -24,25 +24,14 @@
|
||||
<div class="l-layout__frame c-frame"
|
||||
:class="{
|
||||
'no-frame': !item.hasFrame,
|
||||
'u-inspectable': inspectable,
|
||||
'is-resizing': isResizing
|
||||
'u-inspectable': inspectable
|
||||
}"
|
||||
:style="style">
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<!-- Drag handles -->
|
||||
<div class="c-frame-edit">
|
||||
<div class="c-frame-edit__move"
|
||||
@mousedown="startDrag([1,1], [0,0], $event, 'move')"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--nw"
|
||||
@mousedown="startDrag([1,1], [-1,-1], $event, 'resize')"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--ne"
|
||||
@mousedown="startDrag([0,1], [1,-1], $event, 'resize')"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--sw"
|
||||
@mousedown="startDrag([1,0], [-1,1], $event, 'resize')"></div>
|
||||
<div class="c-frame-edit__handle c-frame-edit__handle--se"
|
||||
@mousedown="startDrag([0,0], [1,1], $event, 'resize')"></div>
|
||||
<div class="c-frame-edit__move"
|
||||
@mousedown="startMove([1,1], [0,0], $event)">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -50,7 +39,7 @@
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* FRAME */
|
||||
/******************* FRAME */
|
||||
.c-frame {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -59,124 +48,15 @@
|
||||
> *:first-child {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&:not(.no-frame) {
|
||||
background: $colorBodyBg;
|
||||
border: $browseFrameBorder;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.c-frame-edit {
|
||||
// In Layouts, this is the editing rect and handles
|
||||
// In Fixed Position, this is a wrapper element
|
||||
@include abs();
|
||||
.c-frame-edit__move {
|
||||
display: none;
|
||||
|
||||
&__move {
|
||||
@include abs();
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
&__handle {
|
||||
$d: 6px;
|
||||
$o: floor($d * -0.5);
|
||||
background: $editFrameColorHandleFg;
|
||||
box-shadow: $editFrameColorHandleBg 0 0 0 2px;
|
||||
display: none; // Set to block via s-selected selector
|
||||
position: absolute;
|
||||
width: $d; height: $d;
|
||||
top: auto; right: auto; bottom: auto; left: auto;
|
||||
|
||||
&:before {
|
||||
// Extended hit area
|
||||
@include abs(-10px);
|
||||
content: '';
|
||||
display: block;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $editUIColor;
|
||||
}
|
||||
|
||||
&--nwse {
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
&--nw {
|
||||
cursor: nw-resize;
|
||||
left: $o; top: $o;
|
||||
}
|
||||
|
||||
&--ne {
|
||||
cursor: ne-resize;
|
||||
right: $o; top: $o;
|
||||
}
|
||||
|
||||
&--se {
|
||||
cursor: se-resize;
|
||||
right: $o; bottom: $o;
|
||||
}
|
||||
|
||||
&--sw {
|
||||
cursor: sw-resize;
|
||||
left: $o; bottom: $o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-so-view.has-complex-content + .c-frame-edit {
|
||||
// Target frames that hold domain objects that include header elements, as opposed to drawing and alpha objects
|
||||
// Make the __move element a more affordable drag UI element
|
||||
.c-frame-edit__move {
|
||||
@include userSelectNone();
|
||||
background: $editFrameMovebarColorBg;
|
||||
box-shadow: rgba(black, 0.2) 0 1px;
|
||||
bottom: auto;
|
||||
height: 0; // Height is set on hover on s-selected.c-frame
|
||||
opacity: 0.8;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
|
||||
&:before {
|
||||
// Grippy
|
||||
$h: 4px;
|
||||
$tbOffset: ($editFrameMovebarH - $h) / 2;
|
||||
$lrOffset: 25%;
|
||||
@include grippy($editFrameMovebarColorFg);
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: $tbOffset; right: $lrOffset; bottom: $tbOffset; left: $lrOffset;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $editFrameHovMovebarColorBg;
|
||||
&:before { @include grippy($editFrameHovMovebarColorFg); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-editing {
|
||||
/******************* STYLES FOR C-FRAME WHILE EDITING */
|
||||
.c-frame {
|
||||
$moveBarOutDelay: 500ms;
|
||||
&.no-frame {
|
||||
border: $editFrameBorder; // Base border style for a frame element while editing.
|
||||
}
|
||||
|
||||
&-edit {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
&-edit__move,
|
||||
.c-so-view {
|
||||
transition: $transOut;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
}
|
||||
|
||||
&:not([s-selected]) {
|
||||
&:hover {
|
||||
border: $editFrameBorderHov;
|
||||
@ -188,37 +68,110 @@
|
||||
border: $editFrameSelectedBorder;
|
||||
box-shadow: $editFrameSelectedShdw;
|
||||
|
||||
> .c-frame-edit {
|
||||
[class*='__handle'] {
|
||||
.c-frame-edit__move {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************* DEFAULT STYLES FOR -EDIT__MOVE */
|
||||
// All object types
|
||||
.c-frame-edit__move {
|
||||
@include abs();
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Has-complex-content objects
|
||||
.c-so-view.has-complex-content {
|
||||
transition: $transOut;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
|
||||
> .c-so-view__local-controls {
|
||||
transition: transform 250ms ease-in-out;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
}
|
||||
|
||||
+ .c-frame-edit__move {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.l-layout {
|
||||
/******************* 0 - 1 ITEM SELECTED */
|
||||
&:not(.is-multi-selected) {
|
||||
> .l-layout__frame[s-selected] {
|
||||
> .c-so-view.has-complex-content {
|
||||
> .c-so-view__local-controls {
|
||||
transition: transform $transOutTime ease-in-out;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
}
|
||||
|
||||
+ .c-frame-edit__move {
|
||||
transition: $transOut;
|
||||
transition-delay: $moveBarOutDelay;
|
||||
@include userSelectNone();
|
||||
background: $editFrameMovebarColorBg;
|
||||
box-shadow: rgba(black, 0.2) 0 1px;
|
||||
bottom: auto;
|
||||
display: block;
|
||||
height: 0; // Height is set on hover below
|
||||
opacity: 0.8;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
|
||||
&:before {
|
||||
// Grippy
|
||||
$h: 4px;
|
||||
$tbOffset: ($editFrameMovebarH - $h) / 2;
|
||||
$lrOffset: 25%;
|
||||
@include grippy($editFrameMovebarColorFg);
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: $tbOffset;
|
||||
right: $lrOffset;
|
||||
bottom: $tbOffset;
|
||||
left: $lrOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> .c-so-view.has-complex-content {
|
||||
transition: $transIn;
|
||||
transition-delay: 0s;
|
||||
padding-top: $editFrameMovebarH + $interiorMarginSm;
|
||||
|
||||
> .c-so-view__local-controls {
|
||||
transform: translateY($editFrameMovebarH);
|
||||
transition: transform $transInTime ease-in-out;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
+ .c-frame-edit__move {
|
||||
transition: $transIn;
|
||||
transition-delay: 0s;
|
||||
height: $editFrameMovebarH;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************* > 1 ITEMS SELECTED */
|
||||
&.is-multi-selected {
|
||||
.l-layout__frame[s-selected] {
|
||||
> .c-so-view.has-complex-content + .c-frame-edit__move {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l-layout__frame:not(.is-resizing) {
|
||||
// Show and animate the __move bar for sub-object views with complex content
|
||||
&:hover > .c-so-view.has-complex-content {
|
||||
// Move content down so the __move bar doesn't cover it.
|
||||
padding-top: $editFrameMovebarH;
|
||||
transition: $transIn;
|
||||
|
||||
&.c-so-view--no-frame {
|
||||
// Move content down with a bit more space
|
||||
padding-top: $editFrameMovebarH + $interiorMarginSm;
|
||||
}
|
||||
|
||||
// Show the move bar
|
||||
+ .c-frame-edit .c-frame-edit__move {
|
||||
height: $editFrameMovebarH;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import LayoutDrag from './../LayoutDrag'
|
||||
|
||||
@ -228,21 +181,9 @@
|
||||
item: Object,
|
||||
gridSize: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragPosition: undefined,
|
||||
isResizing: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
let {x, y, width, height} = this.item;
|
||||
|
||||
if (this.dragPosition) {
|
||||
[x, y] = this.dragPosition.position;
|
||||
[width, height] = this.dragPosition.dimensions;
|
||||
}
|
||||
|
||||
return {
|
||||
left: (this.gridSize[0] * x) + 'px',
|
||||
top: (this.gridSize[1] * y) + 'px',
|
||||
@ -264,36 +205,40 @@
|
||||
return value - this.initialPosition[index];
|
||||
}.bind(this));
|
||||
},
|
||||
startDrag(posFactor, dimFactor, event, type) {
|
||||
document.body.addEventListener('mousemove', this.continueDrag);
|
||||
document.body.addEventListener('mouseup', this.endDrag);
|
||||
|
||||
startMove(posFactor, dimFactor, event) {
|
||||
document.body.addEventListener('mousemove', this.continueMove);
|
||||
document.body.addEventListener('mouseup', this.endMove);
|
||||
this.dragPosition = {
|
||||
position: [this.item.x, this.item.y],
|
||||
dimensions: [this.item.width, this.item.height]
|
||||
position: [this.item.x, this.item.y]
|
||||
};
|
||||
this.updatePosition(event);
|
||||
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
|
||||
this.isResizing = type === 'resize';
|
||||
event.preventDefault();
|
||||
},
|
||||
continueDrag(event) {
|
||||
continueMove(event) {
|
||||
event.preventDefault();
|
||||
this.updatePosition(event);
|
||||
this.dragPosition = this.activeDrag.getAdjustedPosition(this.delta);
|
||||
let newPosition = this.activeDrag.getAdjustedPosition(this.delta);
|
||||
|
||||
if (!_.isEqual(newPosition, this.dragPosition)) {
|
||||
this.dragPosition = newPosition;
|
||||
this.$emit('move', this.toGridDelta(this.delta));
|
||||
}
|
||||
},
|
||||
endDrag(event) {
|
||||
document.body.removeEventListener('mousemove', this.continueDrag);
|
||||
document.body.removeEventListener('mouseup', this.endDrag);
|
||||
this.continueDrag(event);
|
||||
let [x, y] = this.dragPosition.position;
|
||||
let [width, height] = this.dragPosition.dimensions;
|
||||
this.$emit('endDrag', this.item, {x, y, width, height});
|
||||
endMove(event) {
|
||||
document.body.removeEventListener('mousemove', this.continueMove);
|
||||
document.body.removeEventListener('mouseup', this.endMove);
|
||||
this.continueMove(event);
|
||||
this.$emit('endMove');
|
||||
this.dragPosition = undefined;
|
||||
this.initialPosition = undefined;
|
||||
this.delta = undefined;
|
||||
this.isResizing = undefined;
|
||||
event.preventDefault();
|
||||
},
|
||||
toGridDelta(pixelDelta) {
|
||||
return pixelDelta.map((v, i) => {
|
||||
return Math.round(v / this.gridSize[i]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,9 +30,9 @@
|
||||
</line>
|
||||
</svg>
|
||||
|
||||
<div class="c-frame-edit">
|
||||
<div class="c-frame-edit__move"
|
||||
@mousedown="startDrag($event)"></div>
|
||||
<div class="c-frame-edit__move"
|
||||
@mousedown="startDrag($event)"></div>
|
||||
<div class="c-frame-edit" v-if="showFrameEdit">
|
||||
<div class="c-frame-edit__handle"
|
||||
:class="startHandleClass"
|
||||
@mousedown="startDrag($event, 'start')"></div>
|
||||
@ -66,8 +66,7 @@
|
||||
y: 10,
|
||||
x2: 10,
|
||||
y2: 5,
|
||||
stroke: '#717171',
|
||||
useGrid: true
|
||||
stroke: '#717171'
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
@ -76,24 +75,31 @@
|
||||
gridSize: Array,
|
||||
initSelect: Boolean,
|
||||
index: Number,
|
||||
multiSelect: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragPosition: undefined
|
||||
dragPosition: undefined,
|
||||
dragging: undefined,
|
||||
selection: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showFrameEdit() {
|
||||
let layoutItem = this.selection.length > 0 && this.selection[0][0].context.layoutItem;
|
||||
return !this.multiSelect && layoutItem && layoutItem.id === this.item.id;
|
||||
},
|
||||
position() {
|
||||
let {x, y, x2, y2} = this.item;
|
||||
if (this.dragPosition) {
|
||||
if (this.dragging && this.dragPosition) {
|
||||
({x, y, x2, y2} = this.dragPosition);
|
||||
}
|
||||
return {x, y, x2, y2};
|
||||
},
|
||||
style() {
|
||||
let {x, y, x2, y2} = this.position;
|
||||
let width = this.gridSize[0] * Math.abs(x - x2);
|
||||
let height = this.gridSize[1] * Math.abs(y - y2);
|
||||
let width = Math.max(this.gridSize[0] * Math.abs(x - x2), 1);
|
||||
let height = Math.max(this.gridSize[1] * Math.abs(y - y2), 1);
|
||||
let left = this.gridSize[0] * Math.min(x, x2);
|
||||
let top = this.gridSize[1] * Math.min(y, y2);
|
||||
return {
|
||||
@ -175,13 +181,27 @@
|
||||
event.preventDefault();
|
||||
let pxDeltaX = this.startPosition[0] - event.pageX;
|
||||
let pxDeltaY = this.startPosition[1] - event.pageY;
|
||||
this.dragPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
|
||||
let newPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
|
||||
|
||||
if (!this.dragging) {
|
||||
if (!_.isEqual(newPosition, this.dragPosition)) {
|
||||
let gridDelta = [event.pageX - this.startPosition[0], event.pageY - this.startPosition[1]];
|
||||
this.dragPosition = newPosition;
|
||||
this.$emit('move', this.toGridDelta(gridDelta));
|
||||
}
|
||||
} else {
|
||||
this.dragPosition = newPosition;
|
||||
}
|
||||
},
|
||||
endDrag(event) {
|
||||
document.body.removeEventListener('mousemove', this.continueDrag);
|
||||
document.body.removeEventListener('mouseup', this.endDrag);
|
||||
let {x, y, x2, y2} = this.dragPosition;
|
||||
this.$emit('endDrag', this.item, {x, y, x2, y2});
|
||||
if (!this.dragging) {
|
||||
this.$emit('endMove');
|
||||
} else {
|
||||
this.$emit('endLineResize', this.item, {x, y, x2, y2});
|
||||
}
|
||||
this.dragPosition = undefined;
|
||||
this.dragging = undefined;
|
||||
event.preventDefault();
|
||||
@ -191,6 +211,7 @@
|
||||
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]?
|
||||
let {x, y, x2, y2} = this.item;
|
||||
let dragPosition = {x, y, x2, y2};
|
||||
|
||||
if (this.dragging === 'start') {
|
||||
dragPosition.x -= gridDeltaX;
|
||||
dragPosition.y -= gridDeltaY;
|
||||
@ -205,6 +226,14 @@
|
||||
dragPosition.y2 -= gridDeltaY;
|
||||
}
|
||||
return dragPosition;
|
||||
},
|
||||
setSelection(selection) {
|
||||
this.selection = selection;
|
||||
},
|
||||
toGridDelta(pixelDelta) {
|
||||
return pixelDelta.map((v, i) => {
|
||||
return Math.round(v / this.gridSize[i]);
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -217,6 +246,7 @@
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.selection.on('change', this.setSelection);
|
||||
this.context = {
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
@ -228,6 +258,7 @@
|
||||
if (this.removeSelectable) {
|
||||
this.removeSelectable();
|
||||
}
|
||||
this.openmct.selection.off('change', this.setSelection);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -22,7 +22,9 @@
|
||||
<template>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
:title="domainObject && domainObject.name"
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')">
|
||||
<object-frame v-if="domainObject"
|
||||
:domain-object="domainObject"
|
||||
:object-path="objectPath"
|
||||
@ -66,8 +68,7 @@
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
identifier: domainObject.identifier,
|
||||
hasFrame: hasFrameByDefault(domainObject.type),
|
||||
useGrid: true
|
||||
hasFrame: hasFrameByDefault(domainObject.type)
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
|
@ -23,7 +23,8 @@
|
||||
<template>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')">
|
||||
<div class="c-telemetry-view"
|
||||
:style="styleObject"
|
||||
v-if="domainObject">
|
||||
@ -78,6 +79,7 @@
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
import printj from 'printj'
|
||||
|
||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
|
||||
DEFAULT_POSITION = [1, 1];
|
||||
@ -96,10 +98,9 @@
|
||||
displayMode: 'all',
|
||||
value: metadata.getDefaultDisplayValue(),
|
||||
stroke: "transparent",
|
||||
fill: "",
|
||||
fill: "transparent",
|
||||
color: "",
|
||||
size: "13px",
|
||||
useGrid: true
|
||||
size: "13px"
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
@ -143,6 +144,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.item.format) {
|
||||
return printj.sprintf(this.item.format, this.datum[this.valueMetadata.key]);
|
||||
}
|
||||
|
||||
return this.valueFormatter && this.valueFormatter.format(this.datum);
|
||||
},
|
||||
telemetryClass() {
|
||||
@ -168,6 +173,9 @@
|
||||
}
|
||||
|
||||
this.context.index = newIndex;
|
||||
},
|
||||
item(newItem) {
|
||||
this.context.layoutItem = newItem;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -176,7 +184,8 @@
|
||||
let options = {
|
||||
start: bounds.start,
|
||||
end: bounds.end,
|
||||
size: 1
|
||||
size: 1,
|
||||
strategy: 'latest'
|
||||
};
|
||||
this.openmct.telemetry.request(this.domainObject, options)
|
||||
.then(data => {
|
||||
@ -218,10 +227,14 @@
|
||||
this.context = {
|
||||
item: domainObject,
|
||||
layoutItem: this.item,
|
||||
index: this.index
|
||||
index: this.index,
|
||||
updateTelemetryFormat: this.updateTelemetryFormat
|
||||
};
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el, this.context, this.initSelect);
|
||||
},
|
||||
updateTelemetryFormat(format) {
|
||||
this.$emit('formatChanged', this.item, format);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -23,7 +23,8 @@
|
||||
<template>
|
||||
<layout-frame :item="item"
|
||||
:grid-size="gridSize"
|
||||
@endDrag="(item, updates) => $emit('endDrag', item, updates)">
|
||||
@move="(gridDelta) => $emit('move', gridDelta)"
|
||||
@endMove="() => $emit('endMove')">
|
||||
<div class="c-text-view"
|
||||
:style="style">
|
||||
{{ item.text }}
|
||||
@ -59,8 +60,7 @@
|
||||
y: 1,
|
||||
width: 10,
|
||||
height: 5,
|
||||
text: element.text,
|
||||
useGrid: true
|
||||
text: element.text
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
|
@ -25,6 +25,8 @@ import Vue from 'vue'
|
||||
import objectUtils from '../../api/objects/object-utils.js'
|
||||
import DisplayLayoutType from './DisplayLayoutType.js'
|
||||
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'
|
||||
import AlphaNumericFormatViewProvider from './AlphaNumericFormatViewProvider.js'
|
||||
|
||||
export default function DisplayLayoutPlugin(options) {
|
||||
return function (openmct) {
|
||||
openmct.objectViews.addProvider({
|
||||
@ -60,6 +62,7 @@ export default function DisplayLayoutPlugin(options) {
|
||||
getSelectionContext() {
|
||||
return {
|
||||
item: domainObject,
|
||||
supportsMultiSelect: true,
|
||||
addElement: component && component.$refs.displayLayout.addElement,
|
||||
removeItem: component && component.$refs.displayLayout.removeItem,
|
||||
orderItem: component && component.$refs.displayLayout.orderItem
|
||||
@ -75,7 +78,8 @@ export default function DisplayLayoutPlugin(options) {
|
||||
}
|
||||
});
|
||||
openmct.types.addType('layout', DisplayLayoutType());
|
||||
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct));
|
||||
openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct, options));
|
||||
openmct.inspectorViews.addProvider(new AlphaNumericFormatViewProvider(openmct, options));
|
||||
openmct.composition.addPolicy((parent, child) => {
|
||||
if (parent.type === 'layout' && child.type === 'folder') {
|
||||
return false;
|
||||
|
@ -42,7 +42,10 @@
|
||||
|
||||
<!-- Checkbox list, NOT editing -->
|
||||
<template v-if="filter.possibleValues && !isEditing">
|
||||
<span>{{persistedFilters[filter.comparator].join(', ')}}</span>
|
||||
<span
|
||||
v-if="persistedFilters[filter.comparator]">
|
||||
{{persistedFilters[filter.comparator].join(', ')}}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -69,16 +69,17 @@ export default {
|
||||
}
|
||||
} else {
|
||||
if (!this.updatedFilters[key]) {
|
||||
this.updatedFilters[key] = {};
|
||||
this.$set(this.updatedFilters, key, {});
|
||||
}
|
||||
this.updatedFilters[key][comparator] = [value ? valueName : undefined];
|
||||
this.$set(this.updatedFilters[key], comparator, [value ? valueName : undefined]);
|
||||
}
|
||||
|
||||
this.$emit('updateFilters', this.keyString, this.updatedFilters);
|
||||
},
|
||||
updateTextFilter(key, comparator, value) {
|
||||
if (!this.updatedFilters[key]) {
|
||||
this.updatedFilters[key] = {};
|
||||
this.$set(this.updatedFilters, key, {});
|
||||
this.$set(this.updatedFilters[key], comparator, '');
|
||||
}
|
||||
this.updatedFilters[key][comparator] = value;
|
||||
this.$emit('updateFilters', this.keyString, this.updatedFilters);
|
||||
|
@ -23,17 +23,18 @@ export default {
|
||||
FilterObject
|
||||
},
|
||||
inject: [
|
||||
'openmct',
|
||||
'providedObject'
|
||||
'openmct'
|
||||
],
|
||||
data() {
|
||||
let providedObject = this.openmct.selection.get()[0][0].context.item;
|
||||
let persistedFilters = {};
|
||||
|
||||
if (this.providedObject.configuration && this.providedObject.configuration.filters) {
|
||||
persistedFilters = this.providedObject.configuration.filters;
|
||||
if (providedObject.configuration && providedObject.configuration.filters) {
|
||||
persistedFilters = providedObject.configuration.filters;
|
||||
}
|
||||
|
||||
return {
|
||||
providedObject,
|
||||
persistedFilters,
|
||||
children: {}
|
||||
}
|
||||
@ -73,13 +74,14 @@ export default {
|
||||
this.composition.on('add', this.addChildren);
|
||||
this.composition.on('remove', this.removeChildren);
|
||||
this.composition.load();
|
||||
|
||||
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
|
||||
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.composition.off('add', this.addChildren);
|
||||
this.composition.off('remove', this.removeChildren);
|
||||
this.unobserve();
|
||||
this.unobserveAllMutation();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -33,23 +33,20 @@ define([
|
||||
key: 'filters-inspector',
|
||||
name: 'Filters Inspector View',
|
||||
canView: function (selection) {
|
||||
if (selection.length === 0) {
|
||||
if (selection.length === 0 || selection[0].length === 0) {
|
||||
return false;
|
||||
}
|
||||
let object = selection[0].context.item;
|
||||
let object = selection[0][0].context.item;
|
||||
|
||||
return object && supportedObjectTypesArray.some(type => object.type === type);
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
let providedObject = selection[0].context.item;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
provide: {
|
||||
openmct,
|
||||
providedObject
|
||||
openmct
|
||||
},
|
||||
components: {
|
||||
FiltersView: FiltersView.default
|
||||
@ -59,8 +56,10 @@ define([
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -106,9 +106,6 @@
|
||||
.c-fl {
|
||||
@include abs();
|
||||
display: flex;
|
||||
flex-direction: column; // TEMP: only needed to support temp-toolbar element
|
||||
|
||||
> * + * { margin-top: $interiorMargin; }
|
||||
|
||||
.temp-toolbar {
|
||||
flex: 0 0 auto;
|
||||
@ -116,7 +113,8 @@
|
||||
|
||||
&__container-holder {
|
||||
display: flex;
|
||||
flex: 1 1 100%; // Must needs to be 100% to work
|
||||
flex: 1 1 100%; // Must be 100% to work
|
||||
overflow: auto;
|
||||
|
||||
// Columns by default
|
||||
flex-direction: row;
|
||||
@ -292,11 +290,6 @@
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
|
||||
&__object-view {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__size-indicator {
|
||||
$size: 35px;
|
||||
|
||||
@ -422,6 +415,7 @@ import Container from '../utils/container';
|
||||
import Frame from '../utils/frame';
|
||||
import ResizeHandle from './resizeHandle.vue';
|
||||
import DropHint from './dropHint.vue';
|
||||
import RemoveAction from '../../remove/RemoveAction.js';
|
||||
|
||||
const MIN_CONTAINER_SIZE = 5;
|
||||
|
||||
@ -513,7 +507,7 @@ export default {
|
||||
remove associated domainObjects from composition
|
||||
*/
|
||||
container.frames.forEach(f => {
|
||||
this.composition.remove({identifier: f.domainObjectIdentifier});
|
||||
this.removeFromComposition(f.domainObjectIdentifier);
|
||||
});
|
||||
|
||||
this.containers.splice(containerIndex, 1);
|
||||
@ -528,6 +522,7 @@ export default {
|
||||
}
|
||||
|
||||
sizeToFill(this.containers);
|
||||
this.setSelectionToParent();
|
||||
this.persist();
|
||||
},
|
||||
moveFrame(toContainerIndex, toFrameIndex, frameId, fromContainerIndex) {
|
||||
@ -561,20 +556,23 @@ export default {
|
||||
deleteFrame(frameId) {
|
||||
let container = this.containers
|
||||
.filter(c => c.frames.some(f => f.id === frameId))[0];
|
||||
let containerIndex = this.containers.indexOf(container);
|
||||
let frame = container
|
||||
.frames
|
||||
.filter((f => f.id === frameId))[0];
|
||||
let frameIndex = container.frames.indexOf(frame);
|
||||
|
||||
/*
|
||||
remove associated domainObject from composition
|
||||
*/
|
||||
this.composition.remove({identifier: frame.domainObjectIdentifier});
|
||||
|
||||
container.frames.splice(frameIndex, 1);
|
||||
sizeToFill(container.frames);
|
||||
this.persist(containerIndex);
|
||||
this.removeFromComposition(frame.domainObjectIdentifier)
|
||||
.then(() => {
|
||||
sizeToFill(container.frames)
|
||||
this.setSelectionToParent();
|
||||
});
|
||||
},
|
||||
removeFromComposition(identifier) {
|
||||
return this.openmct.objects.get(identifier).then((childDomainObject) => {
|
||||
this.RemoveAction.removeFromComposition(this.domainObject, childDomainObject);
|
||||
});
|
||||
},
|
||||
setSelectionToParent() {
|
||||
this.$el.click();
|
||||
},
|
||||
allowContainerDrop(event, index) {
|
||||
if (!event.dataTransfer.types.includes('containerid')) {
|
||||
@ -665,6 +663,8 @@ export default {
|
||||
this.composition.on('remove', this.removeChildObject);
|
||||
this.composition.on('add', this.addFrame);
|
||||
|
||||
this.RemoveAction = new RemoveAction(this.openmct);
|
||||
|
||||
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
@ -79,12 +79,14 @@ export default {
|
||||
},
|
||||
setSelection() {
|
||||
this.$nextTick(function () {
|
||||
let childContext = this.$refs.objectFrame.getSelectionContext();
|
||||
childContext.item = this.domainObject;
|
||||
childContext.type = 'frame';
|
||||
childContext.frameId = this.frame.id;
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(
|
||||
this.$refs.frame, childContext, false);
|
||||
if (this.$refs && this.$refs.objectFrame) {
|
||||
let childContext = this.$refs.objectFrame.getSelectionContext();
|
||||
childContext.item = this.domainObject;
|
||||
childContext.type = 'frame';
|
||||
childContext.frameId = this.frame.id;
|
||||
this.unsubscribeSelection = this.openmct.selection.selectable(
|
||||
this.$refs.frame, childContext, false);
|
||||
}
|
||||
});
|
||||
},
|
||||
initDrag(event) {
|
||||
|
@ -79,10 +79,12 @@ export default {
|
||||
mounted() {
|
||||
document.addEventListener('dragstart', this.setDragging);
|
||||
document.addEventListener('dragend', this.unsetDragging);
|
||||
document.addEventListener('drop', this.unsetDragging);
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('dragstart', this.setDragging);
|
||||
document.removeEventListener('dragend', this.unsetDragging);
|
||||
document.removeEventListener('drop', this.unsetDragging);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -27,28 +27,22 @@ function ToolbarProvider(openmct) {
|
||||
key: "flex-layout",
|
||||
description: "A toolbar for objects inside a Flexible Layout.",
|
||||
forSelection: function (selection) {
|
||||
let context = selection[0].context;
|
||||
let context = selection[0][0].context;
|
||||
|
||||
return (context && context.type &&
|
||||
(context.type === 'flexible-layout' || context.type === 'container' || context.type === 'frame'));
|
||||
},
|
||||
toolbar: function (selection) {
|
||||
|
||||
let primary = selection[0],
|
||||
secondary = selection[1],
|
||||
tertiary = selection[2],
|
||||
let selectionPath = selection[0],
|
||||
primary = selectionPath[0],
|
||||
secondary = selectionPath[1],
|
||||
tertiary = selectionPath[2],
|
||||
deleteFrame,
|
||||
toggleContainer,
|
||||
deleteContainer,
|
||||
addContainer,
|
||||
toggleFrame,
|
||||
separator;
|
||||
|
||||
separator = {
|
||||
control: "separator",
|
||||
domainObject: selection[0].context.item,
|
||||
key: "separator"
|
||||
};
|
||||
toggleFrame;
|
||||
|
||||
toggleContainer = {
|
||||
control: 'toggle-button',
|
||||
@ -69,6 +63,12 @@ function ToolbarProvider(openmct) {
|
||||
]
|
||||
};
|
||||
|
||||
function getSeparator() {
|
||||
return {
|
||||
control: "separator"
|
||||
};
|
||||
}
|
||||
|
||||
if (primary.context.type === 'frame') {
|
||||
let frameId = primary.context.frameId;
|
||||
let layoutObject = tertiary.context.item;
|
||||
@ -77,11 +77,11 @@ function ToolbarProvider(openmct) {
|
||||
.containers;
|
||||
let container = containers
|
||||
.filter(c => c.frames.some(f => f.id === frameId))[0];
|
||||
let frame = container
|
||||
let containerIndex = containers.indexOf(container);
|
||||
let frame = container && container
|
||||
.frames
|
||||
.filter((f => f.id === frameId))[0];
|
||||
let containerIndex = containers.indexOf(container);
|
||||
let frameIndex = container.frames.indexOf(frame);
|
||||
let frameIndex = container && container.frames.indexOf(frame);
|
||||
|
||||
deleteFrame = {
|
||||
control: "button",
|
||||
@ -202,9 +202,9 @@ function ToolbarProvider(openmct) {
|
||||
let toolbar = [
|
||||
toggleContainer,
|
||||
addContainer,
|
||||
toggleFrame ? separator: undefined,
|
||||
toggleFrame ? getSeparator() : undefined,
|
||||
toggleFrame,
|
||||
deleteFrame || deleteContainer ? separator : undefined,
|
||||
deleteFrame || deleteContainer ? getSeparator() : undefined,
|
||||
deleteFrame,
|
||||
deleteContainer
|
||||
];
|
||||
|
@ -14,6 +14,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
if (!this.composition) {
|
||||
return;
|
||||
}
|
||||
@ -34,7 +35,7 @@ export default {
|
||||
this.items.push({
|
||||
model: child,
|
||||
type: type.definition,
|
||||
isAlias: this.domainObject.identifier.key !== child.location,
|
||||
isAlias: this.keystring !== child.location,
|
||||
objectPath: [child].concat(this.openmct.router.path),
|
||||
objectKeyString: this.openmct.objects.makeKeyString(child.identifier)
|
||||
});
|
||||
|
53
src/plugins/goToOriginalAction/goToOriginalAction.js
Normal file
53
src/plugins/goToOriginalAction/goToOriginalAction.js
Normal file
@ -0,0 +1,53 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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 GoToOriginalAction {
|
||||
constructor(openmct) {
|
||||
this.name = 'Go To Original';
|
||||
this.description = 'Go to the original unlinked instance of this object';
|
||||
|
||||
this._openmct = openmct;
|
||||
}
|
||||
invoke(objectPath) {
|
||||
this._openmct.objects.getOriginalPath(objectPath[0].identifier)
|
||||
.then((originalPath) => {
|
||||
let url = '#/browse/' + originalPath
|
||||
.map(function (o) {
|
||||
return o && this._openmct.objects.makeKeyString(o.identifier);
|
||||
}.bind(this))
|
||||
.reverse()
|
||||
.slice(1)
|
||||
.join('/');
|
||||
|
||||
window.location.href = url;
|
||||
});
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
let parentKeystring = objectPath[1] && this._openmct.objects.makeKeyString(objectPath[1].identifier);
|
||||
|
||||
if (!parentKeystring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (parentKeystring !== objectPath[0].location);
|
||||
}
|
||||
}
|
28
src/plugins/goToOriginalAction/plugin.js
Normal file
28
src/plugins/goToOriginalAction/plugin.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, 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 GoToOriginalAction from './goToOriginalAction';
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
openmct.contextMenu.registerAction(new GoToOriginalAction(openmct));
|
||||
};
|
||||
}
|
@ -64,37 +64,45 @@ define([
|
||||
Object.keys(panels).forEach(key => {
|
||||
let panel = panels[key];
|
||||
let domainObject = childObjects[key];
|
||||
let identifier = undefined;
|
||||
|
||||
if (isTelemetry(domainObject)) {
|
||||
items.push({
|
||||
width: panel.dimensions[0],
|
||||
height: panel.dimensions[1],
|
||||
x: panel.position[0],
|
||||
y: panel.position[1],
|
||||
useGrid: true,
|
||||
identifier: domainObject.identifier,
|
||||
id: uuid(),
|
||||
type: 'telemetry-view',
|
||||
displayMode: 'all',
|
||||
value: openmct.telemetry.getMetadata(domainObject).getDefaultDisplayValue(),
|
||||
stroke: "transparent",
|
||||
fill: "",
|
||||
color: "",
|
||||
size: "13px"
|
||||
});
|
||||
} else {
|
||||
items.push({
|
||||
width: panel.dimensions[0],
|
||||
height: panel.dimensions[1],
|
||||
x: panel.position[0],
|
||||
y: panel.position[1],
|
||||
useGrid: true,
|
||||
identifier: domainObject.identifier,
|
||||
id: uuid(),
|
||||
type: 'subobject-view',
|
||||
hasFrame: panel.hasFrame
|
||||
// If object is a telemetry point, convert it to a plot and
|
||||
// replace the object in migratedObject composition with the plot.
|
||||
identifier = {
|
||||
key: uuid(),
|
||||
namespace: migratedObject.identifier.namespace
|
||||
};
|
||||
let plotObject = {
|
||||
identifier: identifier,
|
||||
location: domainObject.location,
|
||||
name: domainObject.name,
|
||||
type: "telemetry.plot.overlay"
|
||||
};
|
||||
let plotType = openmct.types.get('telemetry.plot.overlay');
|
||||
plotType.definition.initialize(plotObject);
|
||||
plotObject.composition.push(domainObject.identifier);
|
||||
openmct.objects.mutate(plotObject, 'persisted', Date.now());
|
||||
|
||||
let keyString = openmct.objects.makeKeyString(domainObject.identifier);
|
||||
let clonedComposition = Object.assign([], migratedObject.composition);
|
||||
clonedComposition.forEach((identifier, index) => {
|
||||
if (openmct.objects.makeKeyString(identifier) === keyString) {
|
||||
migratedObject.composition[index] = plotObject.identifier;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
width: panel.dimensions[0],
|
||||
height: panel.dimensions[1],
|
||||
x: panel.position[0],
|
||||
y: panel.position[1],
|
||||
identifier: identifier || domainObject.identifier,
|
||||
id: uuid(),
|
||||
type: 'subobject-view',
|
||||
hasFrame: panel.hasFrame
|
||||
});
|
||||
});
|
||||
|
||||
migratedObject.configuration.items = items;
|
||||
@ -104,7 +112,7 @@ define([
|
||||
return migratedObject;
|
||||
}
|
||||
|
||||
function migrateFixedPositionConfiguration(elements, telemetryObjects) {
|
||||
function migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize) {
|
||||
const DEFAULT_STROKE = "transparent";
|
||||
const DEFAULT_SIZE = "13px";
|
||||
const DEFAULT_COLOR = "";
|
||||
@ -117,10 +125,16 @@ define([
|
||||
y: element.y,
|
||||
width: element.width,
|
||||
height: element.height,
|
||||
useGrid: element.useGrid,
|
||||
id: uuid()
|
||||
};
|
||||
|
||||
if (!element.useGrid) {
|
||||
item.x = Math.round(item.x / gridSize[0]);
|
||||
item.y = Math.round(item.y / gridSize[1]);
|
||||
item.width = Math.round(item.width / gridSize[0]);
|
||||
item.height = Math.round(item.height / gridSize[1]);
|
||||
}
|
||||
|
||||
if (element.type === "fixed.telemetry") {
|
||||
item.type = "telemetry-view";
|
||||
item.stroke = element.stroke || DEFAULT_STROKE;
|
||||
@ -163,7 +177,9 @@ define([
|
||||
return [
|
||||
{
|
||||
check(domainObject) {
|
||||
return domainObject.type === 'layout' && domainObject.configuration.layout;
|
||||
return domainObject.type === 'layout' &&
|
||||
domainObject.configuration &&
|
||||
domainObject.configuration.layout;
|
||||
},
|
||||
migrate(domainObject) {
|
||||
let childObjects = {};
|
||||
@ -182,7 +198,9 @@ define([
|
||||
},
|
||||
{
|
||||
check(domainObject) {
|
||||
return domainObject.type === 'telemetry.fixed' && domainObject.configuration['fixed-display'];
|
||||
return domainObject.type === 'telemetry.fixed' &&
|
||||
domainObject.configuration &&
|
||||
domainObject.configuration['fixed-display'];
|
||||
},
|
||||
migrate(domainObject) {
|
||||
const DEFAULT_GRID_SIZE = [64, 16];
|
||||
@ -192,10 +210,11 @@ define([
|
||||
name: domainObject.name,
|
||||
type: "layout"
|
||||
};
|
||||
let gridSize = domainObject.layoutGrid || DEFAULT_GRID_SIZE;
|
||||
let layoutType = openmct.types.get('layout');
|
||||
layoutType.definition.initialize(newLayoutObject);
|
||||
newLayoutObject.composition = domainObject.composition;
|
||||
newLayoutObject.configuration.layoutGrid = domainObject.layoutGrid || DEFAULT_GRID_SIZE;
|
||||
newLayoutObject.configuration.layoutGrid = gridSize;
|
||||
|
||||
let elements = domainObject.configuration['fixed-display'].elements;
|
||||
let telemetryObjects = {};
|
||||
@ -211,7 +230,7 @@ define([
|
||||
return Promise.all(promises)
|
||||
.then(function () {
|
||||
newLayoutObject.configuration.items =
|
||||
migrateFixedPositionConfiguration(elements, telemetryObjects);
|
||||
migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize);
|
||||
return newLayoutObject;
|
||||
});
|
||||
}
|
||||
@ -219,6 +238,7 @@ define([
|
||||
{
|
||||
check(domainObject) {
|
||||
return domainObject.type === 'table' &&
|
||||
domainObject.configuration &&
|
||||
domainObject.configuration.table;
|
||||
},
|
||||
migrate(domainObject) {
|
||||
|
@ -43,12 +43,16 @@
|
||||
<div class="l-view-section">
|
||||
<div class="c-loading--overlay loading"
|
||||
ng-show="!!currentRequest.pending"></div>
|
||||
<div class="gl-plot child-frame"
|
||||
<div class="gl-plot child-frame u-inspectable"
|
||||
ng-repeat="telemetryObject in telemetryObjects"
|
||||
ng-class="{
|
||||
's-status-timeconductor-unsynced': telemetryObject
|
||||
.getCapability('status')
|
||||
.get('timeconductor-unsynced')
|
||||
}"
|
||||
mct-selectable="{
|
||||
item: telemetryObject.useCapability('adapter'),
|
||||
oldItem: telemetryObject
|
||||
}">
|
||||
<mct-overlay-plot domain-object="telemetryObject"></mct-overlay-plot>
|
||||
</div>
|
||||
|
@ -115,11 +115,13 @@ define([
|
||||
|
||||
Collection.prototype.remove = function (model) {
|
||||
var index = this.indexOf(model);
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error('model not found in collection.');
|
||||
}
|
||||
this.models.splice(index, 1);
|
||||
|
||||
this.emit('remove', model, index);
|
||||
this.models.splice(index, 1);
|
||||
};
|
||||
|
||||
Collection.prototype.destroy = function (model) {
|
||||
|
@ -100,19 +100,33 @@ define([
|
||||
removeTelemetryObject: function (identifier) {
|
||||
var plotObject = this.plot.get('domainObject');
|
||||
if (plotObject.type === 'telemetry.plot.overlay') {
|
||||
var index = _.findIndex(plotObject.configuration.series, function (s) {
|
||||
|
||||
var persistedIndex = _.findIndex(plotObject.configuration.series, function (s) {
|
||||
return _.isEqual(identifier, s.identifier);
|
||||
});
|
||||
this.remove(this.at(index));
|
||||
// Because this is triggered by a composition change, we have
|
||||
// to defer mutation of our plot object, otherwise we might
|
||||
// mutate an outdated version of the plotObject.
|
||||
setTimeout(function () {
|
||||
var newPlotObject = this.plot.get('domainObject');
|
||||
var cSeries = newPlotObject.configuration.series.slice();
|
||||
cSeries.splice(index, 1);
|
||||
this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries);
|
||||
}.bind(this));
|
||||
|
||||
var configIndex = _.findIndex(this.models, function (m) {
|
||||
return _.isEqual(m.domainObject.identifier, identifier);
|
||||
});
|
||||
|
||||
/*
|
||||
when cancelling out of edit mode, the config store and domain object are out of sync
|
||||
thus it is necesarry to check both and remove the models that are no longer in composition
|
||||
*/
|
||||
if (persistedIndex === -1) {
|
||||
this.remove(this.at(configIndex));
|
||||
} else {
|
||||
this.remove(this.at(persistedIndex));
|
||||
// Because this is triggered by a composition change, we have
|
||||
// to defer mutation of our plot object, otherwise we might
|
||||
// mutate an outdated version of the plotObject.
|
||||
setTimeout(function () {
|
||||
var newPlotObject = this.plot.get('domainObject');
|
||||
var cSeries = newPlotObject.configuration.series.slice();
|
||||
cSeries.splice(persistedIndex, 1);
|
||||
this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries);
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
},
|
||||
onSeriesAdd: function (series) {
|
||||
|
@ -25,23 +25,11 @@ define([
|
||||
|
||||
function ConfigStore() {
|
||||
this.store = {};
|
||||
this.tracking = {};
|
||||
}
|
||||
|
||||
ConfigStore.prototype.track = function (id) {
|
||||
if (!this.tracking[id]) {
|
||||
this.tracking[id] = 0;
|
||||
}
|
||||
this.tracking[id] += 1;
|
||||
};
|
||||
|
||||
ConfigStore.prototype.untrack = function (id) {
|
||||
this.tracking[id] -= 1;
|
||||
if (this.tracking[id] <= 0) {
|
||||
delete this.tracking[id];
|
||||
this.store[id].destroy();
|
||||
delete this.store[id];
|
||||
}
|
||||
ConfigStore.prototype.deleteStore = function (id) {
|
||||
this.store[id].destroy();
|
||||
delete this.store[id];
|
||||
};
|
||||
|
||||
ConfigStore.prototype.add = function (id, config) {
|
||||
|
@ -49,7 +49,6 @@ define([
|
||||
};
|
||||
|
||||
PlotOptionsController.prototype.destroy = function () {
|
||||
configStore.untrack(this.configId);
|
||||
this.stopListening();
|
||||
this.unlisten();
|
||||
};
|
||||
@ -60,7 +59,7 @@ define([
|
||||
this.$timeout(this.setUpScope.bind(this));
|
||||
return;
|
||||
}
|
||||
configStore.track(this.configId);
|
||||
|
||||
this.config = this.$scope.config = config;
|
||||
this.$scope.plotSeries = [];
|
||||
|
||||
@ -70,12 +69,15 @@ define([
|
||||
this.listenTo(this.$scope, '$destroy', this.destroy, this);
|
||||
this.listenTo(config.series, 'add', this.addSeries, this);
|
||||
this.listenTo(config.series, 'remove', this.resetAllSeries, this);
|
||||
|
||||
config.series.forEach(this.addSeries, this);
|
||||
};
|
||||
|
||||
PlotOptionsController.prototype.addSeries = function (series, index) {
|
||||
this.$scope.plotSeries[index] = series;
|
||||
series.locateOldObject(this.$scope.domainObject);
|
||||
this.$timeout(function () {
|
||||
this.$scope.plotSeries[index] = series;
|
||||
series.locateOldObject(this.$scope.domainObject);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
PlotOptionsController.prototype.resetAllSeries = function (series, index) {
|
||||
|
@ -282,11 +282,19 @@ define([
|
||||
};
|
||||
|
||||
MCTPlotController.prototype.zoom = function (zoomDirection, zoomFactor) {
|
||||
var currentXaxis = this.$scope.xAxis.get('displayRange'),
|
||||
currentYaxis = this.$scope.yAxis.get('displayRange');
|
||||
|
||||
// when there is no plot data, the ranges can be undefined
|
||||
// in which case we should not perform zoom
|
||||
if (!currentXaxis || !currentYaxis) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.freeze();
|
||||
this.trackHistory();
|
||||
var currentXaxis = this.$scope.xAxis.get('displayRange'),
|
||||
currentYaxis = this.$scope.yAxis.get('displayRange'),
|
||||
xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor,
|
||||
|
||||
var xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor,
|
||||
yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor;
|
||||
|
||||
if (zoomDirection === 'in') {
|
||||
@ -322,12 +330,19 @@ define([
|
||||
return;
|
||||
}
|
||||
|
||||
let xDisplayRange = this.$scope.xAxis.get('displayRange'),
|
||||
yDisplayRange = this.$scope.yAxis.get('displayRange');
|
||||
|
||||
// when there is no plot data, the ranges can be undefined
|
||||
// in which case we should not perform zoom
|
||||
if (!xDisplayRange || !yDisplayRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.freeze();
|
||||
window.clearTimeout(this.stillZooming);
|
||||
|
||||
let xDisplayRange = this.$scope.xAxis.get('displayRange'),
|
||||
yDisplayRange = this.$scope.yAxis.get('displayRange'),
|
||||
xAxisDist = (xDisplayRange.max - xDisplayRange.min),
|
||||
let xAxisDist = (xDisplayRange.max - xDisplayRange.min),
|
||||
yAxisDist = (yDisplayRange.max - yDisplayRange.min),
|
||||
xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x,
|
||||
xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min,
|
||||
|
@ -148,7 +148,6 @@ define([
|
||||
});
|
||||
configStore.add(configId, config);
|
||||
}
|
||||
configStore.track(configId);
|
||||
return config;
|
||||
};
|
||||
|
||||
@ -157,7 +156,8 @@ define([
|
||||
};
|
||||
|
||||
PlotController.prototype.destroy = function () {
|
||||
configStore.untrack(this.config.id);
|
||||
configStore.deleteStore(this.config.id);
|
||||
|
||||
this.stopListening();
|
||||
if (this.checkForSize) {
|
||||
clearInterval(this.checkForSize);
|
||||
|
@ -79,6 +79,15 @@ define([
|
||||
$scope.$broadcast('plot:tickWidth', _.max(tickWidthMap));
|
||||
}
|
||||
}
|
||||
|
||||
function compositionReorder(reorderPlan) {
|
||||
let oldComposition = telemetryObjects.slice();
|
||||
|
||||
reorderPlan.forEach((reorder) => {
|
||||
telemetryObjects[reorder.newIndex] = oldComposition[reorder.oldIndex];
|
||||
});
|
||||
}
|
||||
|
||||
thisRequest.pending += 1;
|
||||
openmct.objects.get(domainObject.getId())
|
||||
.then(function (obj) {
|
||||
@ -89,10 +98,12 @@ define([
|
||||
composition = openmct.composition.get(obj);
|
||||
composition.on('add', addChild);
|
||||
composition.on('remove', removeChild);
|
||||
composition.on('reorder', compositionReorder);
|
||||
composition.load();
|
||||
unlisten = function () {
|
||||
composition.off('add', addChild);
|
||||
composition.off('remove', removeChild);
|
||||
composition.off('reorder', compositionReorder);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
define([
|
||||
'lodash',
|
||||
'./utcTimeSystem/plugin',
|
||||
'./localTimeSystem/plugin',
|
||||
'../../example/generator/plugin',
|
||||
'./autoflow/AutoflowTabularPlugin',
|
||||
'./timeConductor/plugin',
|
||||
@ -41,10 +42,12 @@ define([
|
||||
'./tabs/plugin',
|
||||
'./LADTable/plugin',
|
||||
'./filters/plugin',
|
||||
'./objectMigration/plugin'
|
||||
'./objectMigration/plugin',
|
||||
'./goToOriginalAction/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
LocalTimeSystem,
|
||||
GeneratorPlugin,
|
||||
AutoflowPlugin,
|
||||
TimeConductorPlugin,
|
||||
@ -63,7 +66,8 @@ define([
|
||||
Tabs,
|
||||
LADTable,
|
||||
Filters,
|
||||
ObjectMigration
|
||||
ObjectMigration,
|
||||
GoToOriginalAction
|
||||
) {
|
||||
var bundleMap = {
|
||||
LocalStorage: 'platform/persistence/local',
|
||||
@ -79,6 +83,7 @@ define([
|
||||
});
|
||||
|
||||
plugins.UTCTimeSystem = UTCTimeSystem;
|
||||
plugins.LocalTimeSystem = LocalTimeSystem;
|
||||
|
||||
plugins.ImportExport = ImportExport;
|
||||
|
||||
@ -160,6 +165,7 @@ define([
|
||||
plugins.LADTable = LADTable;
|
||||
plugins.Filters = Filters;
|
||||
plugins.ObjectMigration = ObjectMigration.default;
|
||||
plugins.GoToOriginalAction = GoToOriginalAction.default;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
@ -85,6 +85,10 @@ export default class RemoveAction {
|
||||
);
|
||||
|
||||
this.openmct.objects.mutate(parent, 'composition', composition);
|
||||
|
||||
if (this.inNavigationPath(child) && this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.save();
|
||||
}
|
||||
}
|
||||
|
||||
appliesTo(objectPath) {
|
||||
|
@ -70,16 +70,14 @@ define([
|
||||
*/
|
||||
function onValueInput(event) {
|
||||
var elem = event.target,
|
||||
value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber),
|
||||
value = isNaN(Number(elem.value)) ? elem.value : Number(elem.value),
|
||||
inputIndex = self.valueInputs.indexOf(elem);
|
||||
|
||||
if (elem.tagName.toUpperCase() === 'INPUT') {
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: 'values[' + inputIndex + ']',
|
||||
index: self.index
|
||||
});
|
||||
}
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: 'values[' + inputIndex + ']',
|
||||
index: self.index
|
||||
});
|
||||
}
|
||||
|
||||
this.listenTo(this.deleteButton, 'click', this.remove, this);
|
||||
@ -108,8 +106,7 @@ define([
|
||||
Object.values(this.selects).forEach(function (select) {
|
||||
$('.t-configuration', self.domElement).append(select.getDOM());
|
||||
});
|
||||
|
||||
this.listenTo($(this.domElement), 'input', onValueInput);
|
||||
this.listenTo($('.t-value-inputs', this.domElement), 'input', onValueInput);
|
||||
}
|
||||
|
||||
Condition.prototype.getDOM = function (container) {
|
||||
@ -167,7 +164,9 @@ define([
|
||||
|
||||
/**
|
||||
* When an operation is selected, create the appropriate value inputs
|
||||
* and add them to the view
|
||||
* and add them to the view. If an operation is of type enum, create
|
||||
* a drop-down menu instead.
|
||||
*
|
||||
* @param {string} operation The key of currently selected operation
|
||||
*/
|
||||
Condition.prototype.generateValueInputs = function (operation) {
|
||||
@ -176,25 +175,49 @@ define([
|
||||
inputCount,
|
||||
inputType,
|
||||
newInput,
|
||||
index = 0;
|
||||
index = 0,
|
||||
emitChange = false;
|
||||
|
||||
inputArea.html('');
|
||||
this.valueInputs = [];
|
||||
this.config.values = [];
|
||||
|
||||
if (evaluator.getInputCount(operation)) {
|
||||
inputCount = evaluator.getInputCount(operation);
|
||||
inputType = evaluator.getInputType(operation);
|
||||
|
||||
while (index < inputCount) {
|
||||
if (!this.config.values[index]) {
|
||||
this.config.values[index] = (inputType === 'number' ? 0 : '');
|
||||
if (inputType === 'select') {
|
||||
newInput = $('<select>' + this.generateSelectOptions() + '</select>');
|
||||
emitChange = true;
|
||||
} else {
|
||||
this.config.values[index] = inputType === 'number' ? 0 : '';
|
||||
newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>');
|
||||
}
|
||||
newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>');
|
||||
|
||||
this.valueInputs.push(newInput.get(0));
|
||||
inputArea.append(newInput);
|
||||
index += 1;
|
||||
}
|
||||
|
||||
if (emitChange) {
|
||||
this.eventEmitter.emit('change', {
|
||||
value: Number(newInput[0].options[0].value),
|
||||
property: 'values[0]',
|
||||
index: this.index
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Condition.prototype.generateSelectOptions = function () {
|
||||
let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object);
|
||||
let options = '';
|
||||
telemetryMetadata[this.config.key].enumerations.forEach(enumeration => {
|
||||
options += '<option value="' + enumeration.value + '">'+ enumeration.string + '</option>';
|
||||
});
|
||||
return options;
|
||||
};
|
||||
|
||||
return Condition;
|
||||
});
|
||||
|
@ -24,7 +24,8 @@ define([], function () {
|
||||
*/
|
||||
this.inputTypes = {
|
||||
number: 'number',
|
||||
string: 'text'
|
||||
string: 'text',
|
||||
enum: 'select'
|
||||
};
|
||||
|
||||
/**
|
||||
@ -34,7 +35,8 @@ define([], function () {
|
||||
*/
|
||||
this.inputValidators = {
|
||||
number: this.validateNumberInput,
|
||||
string: this.validateStringInput
|
||||
string: this.validateStringInput,
|
||||
enum: this.validateNumberInput
|
||||
};
|
||||
|
||||
/**
|
||||
@ -201,7 +203,7 @@ define([], function () {
|
||||
return typeof input[0] === 'undefined';
|
||||
},
|
||||
text: 'is undefined',
|
||||
appliesTo: ['string', 'number'],
|
||||
appliesTo: ['string', 'number', 'enum'],
|
||||
inputCount: 0,
|
||||
getDescription: function () {
|
||||
return ' is undefined';
|
||||
@ -212,11 +214,33 @@ define([], function () {
|
||||
return typeof input[0] !== 'undefined';
|
||||
},
|
||||
text: 'is defined',
|
||||
appliesTo: ['string', 'number'],
|
||||
appliesTo: ['string', 'number', 'enum'],
|
||||
inputCount: 0,
|
||||
getDescription: function () {
|
||||
return ' is defined';
|
||||
}
|
||||
},
|
||||
enumValueIs: {
|
||||
operation: function (input) {
|
||||
return input[0] === input[1];
|
||||
},
|
||||
text: 'is',
|
||||
appliesTo: ['enum'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' == ' + values[0];
|
||||
}
|
||||
},
|
||||
enumValueIsNot: {
|
||||
operation: function (input) {
|
||||
return input[0] !== input[1];
|
||||
},
|
||||
text: 'is not',
|
||||
appliesTo: ['enum'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' != ' + values[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -310,13 +334,16 @@ define([], function () {
|
||||
validator;
|
||||
|
||||
if (cache[object] && typeof cache[object][key] !== 'undefined') {
|
||||
telemetryValue = [cache[object][key]];
|
||||
let value = cache[object][key];
|
||||
telemetryValue = [isNaN(Number(value)) ? value : Number(value)];
|
||||
}
|
||||
|
||||
op = this.operations[operation] && this.operations[operation].operation;
|
||||
input = telemetryValue && telemetryValue.concat(values);
|
||||
validator = op && this.inputValidators[this.operations[operation].appliesTo[0]];
|
||||
|
||||
if (op && input && validator) {
|
||||
if (this.operations[operation].appliesTo.length === 2) {
|
||||
if (this.operations[operation].appliesTo.length > 1) {
|
||||
return (this.validateNumberInput(input) || this.validateStringInput(input)) && op(input);
|
||||
} else {
|
||||
return validator(input) && op(input);
|
||||
@ -372,7 +399,7 @@ define([], function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true only of the given operation applies to a given type
|
||||
* Returns true only if the given operation applies to a given type
|
||||
* @param {string} key The key of the operation
|
||||
* @param {string} type The value type to query
|
||||
* @returns {boolean} True if the condition applies, false otherwise
|
||||
|
@ -130,7 +130,9 @@ define ([
|
||||
this.telemetryTypesById[objectId] = {};
|
||||
Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) {
|
||||
var type;
|
||||
if (valueMetadata.hints.hasOwnProperty('range')) {
|
||||
if (valueMetadata.enumerations !== undefined) {
|
||||
type = 'enum';
|
||||
} else if (valueMetadata.hints.hasOwnProperty('range')) {
|
||||
type = 'number';
|
||||
} else if (valueMetadata.hints.hasOwnProperty('domain')) {
|
||||
type = 'number';
|
||||
@ -163,11 +165,18 @@ define ([
|
||||
* @param {datum} datum The new data from the telemetry source
|
||||
* @private
|
||||
*/
|
||||
ConditionManager.prototype.handleSubscriptionCallback = function (objId, datum) {
|
||||
this.subscriptionCache[objId] = datum;
|
||||
ConditionManager.prototype.handleSubscriptionCallback = function (objId, telemetryDatum) {
|
||||
this.subscriptionCache[objId] = this.createNormalizedDatum(objId, telemetryDatum);
|
||||
this.eventEmitter.emit('receiveTelemetry');
|
||||
};
|
||||
|
||||
ConditionManager.prototype.createNormalizedDatum = function (objId, telemetryDatum) {
|
||||
return Object.values(this.telemetryMetadataById[objId]).reduce((normalizedDatum, metadatum) => {
|
||||
normalizedDatum[metadatum.key] = telemetryDatum[metadatum.source];
|
||||
return normalizedDatum;
|
||||
}, {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Event handler for an add event in this Summary Widget's composition.
|
||||
* Sets up subscription handlers and parses its property types.
|
||||
@ -236,7 +245,9 @@ define ([
|
||||
id.namespace === identifier.namespace;
|
||||
});
|
||||
delete this.compositionObjs[objectId];
|
||||
delete this.subscriptionCache[objectId];
|
||||
this.subscriptions[objectId](); //unsubscribe from telemetry source
|
||||
delete this.subscriptions[objectId];
|
||||
this.eventEmitter.emit('remove', identifier);
|
||||
|
||||
if (_.isEmpty(this.compositionObjs)) {
|
||||
|
@ -110,9 +110,11 @@ define([
|
||||
|
||||
type = self.manager.getTelemetryPropertyType(self.config.object, key);
|
||||
|
||||
self.operationKeys = operations.filter(function (operation) {
|
||||
return self.evaluator.operationAppliesTo(operation, type);
|
||||
});
|
||||
if (type !== undefined) {
|
||||
self.operationKeys = operations.filter(function (operation) {
|
||||
return self.evaluator.operationAppliesTo(operation, type);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
OperationSelect.prototype.destroy = function () {
|
||||
|
@ -38,7 +38,7 @@ define([
|
||||
return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
|
||||
return {
|
||||
key: ts.key,
|
||||
name: 'UTC',
|
||||
name: ts.name,
|
||||
format: ts.timeFormat,
|
||||
hints: {
|
||||
domain: i
|
||||
@ -64,7 +64,7 @@ define([
|
||||
// Generally safe assumption is that we have one domain per timeSystem.
|
||||
values: this.getDomains().concat([
|
||||
{
|
||||
name: 'state',
|
||||
name: 'State',
|
||||
key: 'state',
|
||||
source: 'ruleIndex',
|
||||
format: 'enum',
|
||||
|
@ -43,7 +43,7 @@ define([
|
||||
return evaluator.requestLatest(options)
|
||||
.then(function (latestDatum) {
|
||||
this.pool.release(evaluator);
|
||||
return [latestDatum];
|
||||
return latestDatum ? [latestDatum] : [];
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
@ -174,7 +174,7 @@ define([
|
||||
return typeof input[0] === 'undefined';
|
||||
},
|
||||
text: 'is undefined',
|
||||
appliesTo: ['string', 'number'],
|
||||
appliesTo: ['string', 'number', 'enum'],
|
||||
inputCount: 0,
|
||||
getDescription: function () {
|
||||
return ' is undefined';
|
||||
@ -185,11 +185,33 @@ define([
|
||||
return typeof input[0] !== 'undefined';
|
||||
},
|
||||
text: 'is defined',
|
||||
appliesTo: ['string', 'number'],
|
||||
appliesTo: ['string', 'number', 'enum'],
|
||||
inputCount: 0,
|
||||
getDescription: function () {
|
||||
return ' is defined';
|
||||
}
|
||||
},
|
||||
enumValueIs: {
|
||||
operation: function (input) {
|
||||
return input[0] === input[1];
|
||||
},
|
||||
text: 'is',
|
||||
appliesTo: ['enum'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' == ' + values[0];
|
||||
}
|
||||
},
|
||||
enumValueIsNot: {
|
||||
operation: function (input) {
|
||||
return input[0] !== input[1];
|
||||
},
|
||||
text: 'is not',
|
||||
appliesTo: ['enum'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' != ' + values[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -52,7 +52,10 @@ define([
|
||||
strategy: 'latest',
|
||||
size: 1
|
||||
}).then(function (results) {
|
||||
if (this.destroyed || this.hasUpdated || this.renderTracker !== renderTracker) {
|
||||
if (this.destroyed ||
|
||||
this.hasUpdated ||
|
||||
this.renderTracker !== renderTracker ||
|
||||
results.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.updateState(results[results.length - 1]);
|
||||
|
@ -7,7 +7,8 @@
|
||||
}">
|
||||
<div class="c-drop-hint"
|
||||
@drop="onDrop"
|
||||
ref="dropHint">
|
||||
@dragenter="dragenter"
|
||||
@dragleave="dragleave">
|
||||
</div>
|
||||
<div class="c-tabs-view__empty-message"
|
||||
v-if="!tabsList.length > 0">Drag objects here to add them to this view.</div>
|
||||
@ -25,7 +26,7 @@
|
||||
<div class="c-tabs-view__object-holder"
|
||||
v-for="(tab, index) in tabsList"
|
||||
:key="index"
|
||||
:class="{'invisible': !isCurrent(tab)}">
|
||||
:class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}">
|
||||
<div v-if="currentTab"
|
||||
class="c-tabs-view__object-name l-browse-bar__object-name--w"
|
||||
:class="currentTab.type.definition.cssClass">
|
||||
@ -68,6 +69,14 @@
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&--hidden {
|
||||
height: 1000px;
|
||||
width: 1000px;
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
top: -9999px;
|
||||
}
|
||||
}
|
||||
|
||||
&__object-name {
|
||||
@ -78,7 +87,10 @@
|
||||
}
|
||||
|
||||
&__object {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
flex: 1 1 auto;
|
||||
height: 0; // Chrome 73 oveflow bug fix
|
||||
}
|
||||
|
||||
&__empty-message {
|
||||
@ -186,13 +198,6 @@ export default {
|
||||
|
||||
document.addEventListener('dragstart', this.dragstart);
|
||||
document.addEventListener('dragend', this.dragend);
|
||||
|
||||
let dropHint = this.$refs.dropHint;
|
||||
|
||||
if (dropHint) {
|
||||
dropHint.addEventListener('dragenter', this.dragenter);
|
||||
dropHint.addEventListener('dragleave', this.dragleave);
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
this.composition.off('add', this.addItem);
|
||||
@ -201,12 +206,6 @@ export default {
|
||||
|
||||
document.removeEventListener('dragstart', this.dragstart);
|
||||
document.removeEventListener('dragend', this.dragend);
|
||||
},
|
||||
beforeDestroy() {
|
||||
let dropHint = this.$refs.dropHint;
|
||||
|
||||
dropHint.removeEventListener('dragenter', this.dragenter);
|
||||
dropHint.removeEventListener('dragleave', this.dragleave);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -31,6 +31,7 @@ define([
|
||||
|
||||
openmct.types.addType('tabs', {
|
||||
name: "Tabs View",
|
||||
description: 'Add multiple objects of any type to this view, and quickly navigate between them with tabs',
|
||||
creatable: true,
|
||||
cssClass: 'icon-tabs-view',
|
||||
initialize(domainObject) {
|
||||
|
@ -37,15 +37,15 @@ define([
|
||||
key: 'table-configuration',
|
||||
name: 'Telemetry Table Configuration',
|
||||
canView: function (selection) {
|
||||
if (selection.length === 0) {
|
||||
if (selection.length !== 1 || selection[0].length === 0) {
|
||||
return false;
|
||||
}
|
||||
let object = selection[0].context.item;
|
||||
let object = selection[0][0].context.item;
|
||||
return object && object.type === 'table';
|
||||
},
|
||||
view: function (selection) {
|
||||
let component;
|
||||
let domainObject = selection[0].context.item;
|
||||
let domainObject = selection[0][0].context.item;
|
||||
let tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||
return {
|
||||
show: function (element) {
|
||||
@ -62,8 +62,11 @@ define([
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
|
||||
tableConfiguration = undefined;
|
||||
}
|
||||
}
|
||||
|
@ -45,8 +45,10 @@ define([
|
||||
this.subscriptions = {};
|
||||
this.tableComposition = undefined;
|
||||
this.telemetryObjects = [];
|
||||
this.rowBuffer = [];
|
||||
this.outstandingRequests = 0;
|
||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||
this.paused = false;
|
||||
|
||||
this.addTelemetryObject = this.addTelemetryObject.bind(this);
|
||||
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
|
||||
@ -137,12 +139,17 @@ define([
|
||||
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
|
||||
return this.openmct.telemetry.request(telemetryObject, requestOptions)
|
||||
.then(telemetryData => {
|
||||
//Check that telemetry object has not been removed since telemetry was requested.
|
||||
if (!this.telemetryObjects.includes(telemetryObject)) {
|
||||
return;
|
||||
}
|
||||
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
let columnMap = this.getColumnMapForObject(keyString);
|
||||
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||
|
||||
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
||||
this.boundedRows.add(telemetryRows);
|
||||
}).finally(() => {
|
||||
this.decrementOutstandingRequests();
|
||||
});
|
||||
}
|
||||
@ -193,7 +200,15 @@ define([
|
||||
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||
|
||||
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
|
||||
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
||||
//Check that telemetry object has not been removed since telemetry was requested.
|
||||
if (!this.telemetryObjects.includes(telemetryObject)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.paused) {
|
||||
let row = new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator);
|
||||
this.boundedRows.add(row);
|
||||
}
|
||||
}, subscribeOptions);
|
||||
}
|
||||
|
||||
@ -225,12 +240,23 @@ define([
|
||||
}
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.paused = true;
|
||||
this.boundedRows.unsubscribeFromBounds();
|
||||
}
|
||||
|
||||
unpause() {
|
||||
this.paused = false;
|
||||
this.boundedRows.subscribeToBounds();
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.boundedRows.destroy();
|
||||
this.filteredRows.destroy();
|
||||
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
|
||||
this.openmct.time.off('bounds', this.refreshData);
|
||||
this.openmct.time.on('timeSystem', this.refreshData);
|
||||
this.openmct.time.off('timeSystem', this.refreshData);
|
||||
if (this.filterObserver) {
|
||||
this.filterObserver();
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ define(
|
||||
) {
|
||||
|
||||
class BoundedTableRowCollection extends SortedTableRowCollection {
|
||||
constructor (openmct) {
|
||||
constructor(openmct) {
|
||||
super();
|
||||
|
||||
|
||||
this.futureBuffer = new SortedTableRowCollection();
|
||||
this.openmct = openmct;
|
||||
|
||||
@ -43,15 +43,17 @@ define(
|
||||
this.sortByTimeSystem(openmct.time.timeSystem());
|
||||
|
||||
this.lastBounds = openmct.time.bounds();
|
||||
openmct.time.on('bounds', this.bounds);
|
||||
|
||||
this.subscribeToBounds();
|
||||
}
|
||||
|
||||
addOne (item) {
|
||||
addOne(item) {
|
||||
let parsedValue = this.getValueForSortColumn(item);
|
||||
// Insert into either in-bounds array, or the future buffer.
|
||||
// Data in the future buffer will be re-evaluated for possible
|
||||
// Data in the future buffer will be re-evaluated for possible
|
||||
// insertion on next bounds change
|
||||
let beforeStartOfBounds = this.parseTime(item.datum[this.sortOptions.key]) < this.lastBounds.start;
|
||||
let afterEndOfBounds = this.parseTime(item.datum[this.sortOptions.key]) > this.lastBounds.end;
|
||||
let beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
||||
let afterEndOfBounds = parsedValue > this.lastBounds.end;
|
||||
|
||||
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
||||
return super.addOne(item);
|
||||
@ -86,13 +88,13 @@ define(
|
||||
* @fires TelemetryCollection#discarded
|
||||
* @param bounds
|
||||
*/
|
||||
bounds (bounds) {
|
||||
bounds(bounds) {
|
||||
let startChanged = this.lastBounds.start !== bounds.start;
|
||||
let endChanged = this.lastBounds.end !== bounds.end;
|
||||
|
||||
|
||||
let startIndex = 0;
|
||||
let endIndex = 0;
|
||||
|
||||
|
||||
let discarded = [];
|
||||
let added = [];
|
||||
let testValue = {
|
||||
@ -135,9 +137,21 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
getValueForSortColumn(row) {
|
||||
return this.parseTime(row.datum[this.sortOptions.key]);
|
||||
}
|
||||
|
||||
unsubscribeFromBounds() {
|
||||
this.openmct.time.off('bounds', this.bounds);
|
||||
}
|
||||
|
||||
subscribeToBounds() {
|
||||
this.openmct.time.on('bounds', this.bounds);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.unsubscribeFromBounds();
|
||||
}
|
||||
}
|
||||
return BoundedTableRowCollection;
|
||||
});
|
||||
return BoundedTableRowCollection;
|
||||
});
|
||||
|
@ -60,7 +60,7 @@ define(
|
||||
if (rowsAdded.length > 0) {
|
||||
this.emit('add', rowsAdded);
|
||||
}
|
||||
this.dupeCheck = true;
|
||||
this.dupeCheck = true;
|
||||
} else {
|
||||
let wasAdded = this.addOne(rows);
|
||||
if (wasAdded) {
|
||||
@ -115,11 +115,10 @@ define(
|
||||
if (this.rows.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const sortOptionsKey = this.sortOptions.key;
|
||||
const testRowValue = testRow.datum[sortOptionsKey];
|
||||
const firstValue = this.rows[0].datum[sortOptionsKey];
|
||||
const lastValue = this.rows[this.rows.length - 1].datum[sortOptionsKey];
|
||||
|
||||
const testRowValue = this.getValueForSortColumn(testRow);
|
||||
const firstValue = this.getValueForSortColumn(this.rows[0]);
|
||||
const lastValue = this.getValueForSortColumn(this.rows[this.rows.length - 1]);
|
||||
|
||||
lodashFunction = lodashFunction || _.sortedIndex;
|
||||
|
||||
@ -133,7 +132,7 @@ define(
|
||||
return 0;
|
||||
} else {
|
||||
return lodashFunction(rows, testRow, (thisRow) => {
|
||||
return thisRow.datum[sortOptionsKey];
|
||||
return this.getValueForSortColumn(thisRow);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@ -147,7 +146,7 @@ define(
|
||||
} else {
|
||||
// Use a custom comparison function to support descending sort.
|
||||
return lodashFunction(rows, testRow, (thisRow) => {
|
||||
const thisRowValue = thisRow.datum[sortOptionsKey];
|
||||
const thisRowValue = this.getValueForSortColumn(thisRow);
|
||||
if (testRowValue === thisRowValue) {
|
||||
return EQUAL;
|
||||
} else if (testRowValue < thisRowValue) {
|
||||
@ -206,7 +205,7 @@ define(
|
||||
this.emit('sort');
|
||||
}
|
||||
// Return duplicate to avoid direct modification of underlying object
|
||||
return Object.assign({}, this.sortOptions);
|
||||
return Object.assign({}, this.sortOptions);
|
||||
}
|
||||
|
||||
removeAllRowsForObject(objectKeyString) {
|
||||
@ -218,25 +217,32 @@ define(
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
this.emit('remove', removed);
|
||||
}
|
||||
|
||||
getValueForSortColumn(row) {
|
||||
return row.datum[this.sortOptions.key];
|
||||
}
|
||||
|
||||
remove(removedRows) {
|
||||
this.rows = this.rows.filter(row => {
|
||||
return removedRows.indexOf(row) === -1;
|
||||
});
|
||||
|
||||
this.emit('remove', removedRows);
|
||||
}
|
||||
|
||||
getRows () {
|
||||
getRows() {
|
||||
return this.rows;
|
||||
}
|
||||
|
||||
clear() {
|
||||
let removedRows = this.rows;
|
||||
this.rows = [];
|
||||
|
||||
this.emit('remove', removedRows);
|
||||
}
|
||||
}
|
||||
return SortedTableRowCollection;
|
||||
});
|
||||
return SortedTableRowCollection;
|
||||
});
|
||||
|
@ -34,39 +34,13 @@
|
||||
isSortable ? 'is-sortable' : '',
|
||||
isSortable && sortOptions.key === headerKey ? 'is-sorting' : '',
|
||||
isSortable && sortOptions.direction].join(' ')">
|
||||
<div v-if="isEditing" class="c-telemetry-table__resize-hotzone c-telemetry-table__resize-hotzone--right"
|
||||
<div class="c-telemetry-table__resize-hitarea"
|
||||
@mousedown="resizeColumnStart"
|
||||
></div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</th>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
@import "~styles/table";
|
||||
|
||||
$hotzone-size: 6px;
|
||||
|
||||
.c-telemetry-table__headers__content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c-table.c-telemetry-table {
|
||||
.c-telemetry-table__resize-hotzone {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: $hotzone-size;
|
||||
min-width: $hotzone-size;
|
||||
cursor: col-resize;
|
||||
border: none;
|
||||
right: 0px;
|
||||
margin-right: -$tabularTdPadLR - 1 - $hotzone-size / 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
const MOVE_COLUMN_DT_TYPE = 'movecolumnfromindex';
|
||||
|
@ -20,7 +20,13 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<tr :style="{ top: rowTop }" :class="rowLimitClass">
|
||||
<tr :style="{ top: rowTop }"
|
||||
class="noselect"
|
||||
:class="[
|
||||
rowLimitClass,
|
||||
{'is-selected': marked}
|
||||
]"
|
||||
@click="markRow">
|
||||
<td v-for="(title, key) in headers"
|
||||
:key="key"
|
||||
:style="columnWidths[key] === undefined ? {} : { width: columnWidths[key] + 'px', 'max-width': columnWidths[key] + 'px'}"
|
||||
@ -30,6 +36,15 @@
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.noselect {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently
|
||||
supported by Chrome and Opera */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@ -69,6 +84,11 @@ export default {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
marked: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -79,6 +99,17 @@ export default {
|
||||
this.formattedRow = row.getFormattedDatum(this.headers);
|
||||
this.rowLimitClass = row.getRowLimitClass();
|
||||
this.cellLimitClasses = row.getCellLimitClasses();
|
||||
},
|
||||
markRow: function (event) {
|
||||
if (event.shiftKey) {
|
||||
this.$emit('markMultipleConcurrent', this.rowIndex);
|
||||
} else {
|
||||
if (this.marked) {
|
||||
this.$emit('unmark', this.rowIndex);
|
||||
} else {
|
||||
this.$emit('mark', this.rowIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// TODO: use computed properties
|
||||
|
@ -20,79 +20,111 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
|
||||
:class="{'loading': loading}">
|
||||
<div class="c-table__control-bar c-control-bar">
|
||||
<div class="c-table-wrapper">
|
||||
<div class="c-table-control-bar c-control-bar">
|
||||
<button class="c-button icon-download labeled"
|
||||
v-on:click="exportAsCSV()"
|
||||
v-on:click="exportAllDataAsCSV()"
|
||||
title="Export This View's Data">
|
||||
<span class="c-button__label">Export As CSV</span>
|
||||
<span class="c-button__label">Export Table Data</span>
|
||||
</button>
|
||||
<button class="c-button icon-download labeled"
|
||||
v-show="markedRows.length"
|
||||
v-on:click="exportMarkedDataAsCSV()"
|
||||
title="Export Marked Rows As CSV">
|
||||
<span class="c-button__label">Export Marked Rows</span>
|
||||
</button>
|
||||
<button class="c-button icon-x labeled"
|
||||
v-show="markedRows.length"
|
||||
v-on:click="unmarkAllRows()"
|
||||
title="Unmark All Rows">
|
||||
<span class="c-button__label">Unmark All Rows</span>
|
||||
</button>
|
||||
<div class="c-separator"></div>
|
||||
<button class="c-button icon-pause pause-play labeled"
|
||||
:class=" paused ? 'icon-play is-paused' : 'icon-pause'"
|
||||
v-on:click="togglePauseByButton()"
|
||||
:title="paused ? 'Continue Data Flow' : 'Pause Data Flow'">
|
||||
<span class="c-button__label">
|
||||
{{paused ? 'Play' : 'Pause'}}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div>
|
||||
<!-- Headers table -->
|
||||
<div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}">
|
||||
<table class="c-table__headers c-telemetry-table__headers">
|
||||
<thead>
|
||||
<tr class="c-telemetry-table__headers__name">
|
||||
<table-column-header
|
||||
v-for="(title, key, headerIndex) in headers"
|
||||
:key="key"
|
||||
:headerKey="key"
|
||||
:headerIndex="headerIndex"
|
||||
@sort="sortBy(key)"
|
||||
@resizeColumn="resizeColumn"
|
||||
@dropTargetOffsetChanged="setDropTargetOffset"
|
||||
@dropTargetActive="dropTargetActive"
|
||||
@reorderColumn="reorderColumn"
|
||||
@resizeColumnEnd="updateConfiguredColumnWidths"
|
||||
:columnWidth="columnWidths[key]"
|
||||
:sortOptions="sortOptions"
|
||||
:isEditing="isEditing"
|
||||
>{{title}}</table-column-header>
|
||||
</tr>
|
||||
<tr class="c-telemetry-table__headers__filter">
|
||||
<table-column-header
|
||||
v-for="(title, key, headerIndex) in headers"
|
||||
:key="key"
|
||||
:headerKey="key"
|
||||
:headerIndex="headerIndex"
|
||||
@resizeColumn="resizeColumn"
|
||||
@dropTargetOffsetChanged="setDropTargetOffset"
|
||||
@dropTargetActive="dropTargetActive"
|
||||
@reorderColumn="reorderColumn"
|
||||
@resizeColumnEnd="updateConfiguredColumnWidths"
|
||||
:columnWidth="columnWidths[key]"
|
||||
:isEditing="isEditing"
|
||||
>
|
||||
<search class="c-table__search"
|
||||
v-model="filters[key]"
|
||||
v-on:input="filterChanged(key)"
|
||||
v-on:clear="clearFilter(key)" />
|
||||
</table-column-header>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Content table -->
|
||||
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}">
|
||||
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div>
|
||||
<table class="c-table__body c-telemetry-table__body"
|
||||
:style="{ height: totalHeight + 'px'}">
|
||||
<tbody>
|
||||
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
|
||||
:headers="headers"
|
||||
:columnWidths="columnWidths"
|
||||
:rowIndex="rowIndex"
|
||||
:rowOffset="rowOffset"
|
||||
:rowHeight="rowHeight"
|
||||
:row="row">
|
||||
</telemetry-table-row>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Sizing table -->
|
||||
<table class="c-telemetry-table__sizing js-telemetry-table__sizing" :style="sizingTableWidth">
|
||||
|
||||
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
|
||||
:class="{
|
||||
'loading': loading,
|
||||
'paused' : paused
|
||||
}">
|
||||
<div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div>
|
||||
<!-- Headers table -->
|
||||
<div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}">
|
||||
<table class="c-table__headers c-telemetry-table__headers">
|
||||
<thead>
|
||||
<tr class="c-telemetry-table__headers__labels">
|
||||
<table-column-header
|
||||
v-for="(title, key, headerIndex) in headers"
|
||||
:key="key"
|
||||
:headerKey="key"
|
||||
:headerIndex="headerIndex"
|
||||
@sort="sortBy(key)"
|
||||
@resizeColumn="resizeColumn"
|
||||
@dropTargetOffsetChanged="setDropTargetOffset"
|
||||
@dropTargetActive="dropTargetActive"
|
||||
@reorderColumn="reorderColumn"
|
||||
@resizeColumnEnd="updateConfiguredColumnWidths"
|
||||
:columnWidth="columnWidths[key]"
|
||||
:sortOptions="sortOptions"
|
||||
:isEditing="isEditing"
|
||||
><span class="c-telemetry-table__headers__label">{{title}}</span>
|
||||
</table-column-header>
|
||||
</tr>
|
||||
<tr class="c-telemetry-table__headers__filter">
|
||||
<table-column-header
|
||||
v-for="(title, key, headerIndex) in headers"
|
||||
:key="key"
|
||||
:headerKey="key"
|
||||
:headerIndex="headerIndex"
|
||||
@resizeColumn="resizeColumn"
|
||||
@dropTargetOffsetChanged="setDropTargetOffset"
|
||||
@dropTargetActive="dropTargetActive"
|
||||
@reorderColumn="reorderColumn"
|
||||
@resizeColumnEnd="updateConfiguredColumnWidths"
|
||||
:columnWidth="columnWidths[key]"
|
||||
:isEditing="isEditing"
|
||||
>
|
||||
<search class="c-table__search"
|
||||
v-model="filters[key]"
|
||||
v-on:input="filterChanged(key)"
|
||||
v-on:clear="clearFilter(key)" />
|
||||
</table-column-header>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Content table -->
|
||||
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}">
|
||||
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div>
|
||||
<table class="c-table__body c-telemetry-table__body js-telemetry-table__content"
|
||||
:style="{ height: totalHeight + 'px'}">
|
||||
<tbody>
|
||||
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
|
||||
:key="rowIndex"
|
||||
:headers="headers"
|
||||
:columnWidths="columnWidths"
|
||||
:rowIndex="rowIndex"
|
||||
:rowOffset="rowOffset"
|
||||
:rowHeight="rowHeight"
|
||||
:row="row"
|
||||
:marked="row.marked"
|
||||
@mark="markRow"
|
||||
@unmark="unmarkRow"
|
||||
@markMultipleConcurrent="markMultipleConcurrentRows">
|
||||
</telemetry-table-row>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Sizing table -->
|
||||
<table class="c-telemetry-table__sizing js-telemetry-table__sizing" :style="sizingTableWidth">
|
||||
<tr>
|
||||
<template v-for="(title, key) in headers">
|
||||
<th :key="key" :style="{ width: configuredColumnWidths[key] + 'px', 'max-width': configuredColumnWidths[key] + 'px'}">{{title}}</th>
|
||||
@ -104,7 +136,8 @@
|
||||
:row="sizingRowData">
|
||||
</telemetry-table-row>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- closes c-table-wrapper -->
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@ -159,6 +192,32 @@
|
||||
thead {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__labels {
|
||||
// Top row, has labels
|
||||
.c-telemetry-table__headers__content {
|
||||
// Holds __label, sort indicator and resize-hitarea
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__headers__label {
|
||||
overflow: hidden;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
&__resize-hitarea {
|
||||
// In table-column-header.vue
|
||||
@include abs();
|
||||
display: none; // Set to display: block in .is-editing section below
|
||||
left: auto; right: -1 * $tabularTdPadLR;
|
||||
width: $tableResizeColHitareaD;
|
||||
cursor: col-resize;
|
||||
transform: translateX(50%); // Move so this element sits over border between columns
|
||||
}
|
||||
|
||||
/******************************* ELEMENTS */
|
||||
@ -189,6 +248,10 @@
|
||||
align-items: stretch;
|
||||
position: absolute;
|
||||
height: 18px; // Needed when a row has empty values in its cells
|
||||
|
||||
&.is-selected {
|
||||
background-color: $colorSelectedBg;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
@ -221,7 +284,7 @@
|
||||
|
||||
/******************************* EDITING */
|
||||
.is-editing {
|
||||
.c-telemetry-table__headers__name {
|
||||
.c-telemetry-table__headers__labels {
|
||||
th[draggable],
|
||||
th[draggable] > * {
|
||||
cursor: move;
|
||||
@ -233,6 +296,14 @@
|
||||
> * { background: $b; }
|
||||
}
|
||||
}
|
||||
|
||||
.c-telemetry-table__resize-hitarea {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.paused {
|
||||
border: 1px solid #ff9900;
|
||||
}
|
||||
|
||||
/******************************* LEGACY */
|
||||
@ -253,7 +324,7 @@ import _ from 'lodash';
|
||||
const VISIBLE_ROW_COUNT = 100;
|
||||
const ROW_HEIGHT = 17;
|
||||
const RESIZE_POLL_INTERVAL = 200;
|
||||
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
|
||||
const AUTO_SCROLL_TRIGGER_HEIGHT = 100;
|
||||
const RESIZE_HOT_ZONE = 10;
|
||||
const MOVE_TRIGGER_WAIT = 500;
|
||||
const VERTICAL_SCROLL_WIDTH = 30;
|
||||
@ -297,7 +368,10 @@ export default {
|
||||
dropOffsetLeft: undefined,
|
||||
isDropTargetActive: false,
|
||||
isAutosizeEnabled: configuration.autosize,
|
||||
scrollW: 0
|
||||
scrollW: 0,
|
||||
markCounter: 0,
|
||||
paused: false,
|
||||
markedRows: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -333,42 +407,48 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
updateVisibleRows() {
|
||||
if (!this.updatingView) {
|
||||
this.updatingView = true;
|
||||
requestAnimationFrame(()=> {
|
||||
|
||||
let start = 0;
|
||||
let end = VISIBLE_ROW_COUNT;
|
||||
let filteredRows = this.table.filteredRows.getRows();
|
||||
let filteredRowsLength = filteredRows.length;
|
||||
let start = 0;
|
||||
let end = VISIBLE_ROW_COUNT;
|
||||
let filteredRows = this.table.filteredRows.getRows();
|
||||
let filteredRowsLength = filteredRows.length;
|
||||
|
||||
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
|
||||
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
|
||||
end = filteredRowsLength;
|
||||
} else {
|
||||
let firstVisible = this.calculateFirstVisibleRow();
|
||||
let lastVisible = this.calculateLastVisibleRow();
|
||||
let totalVisible = lastVisible - firstVisible;
|
||||
|
||||
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
|
||||
end = filteredRowsLength;
|
||||
} else {
|
||||
let firstVisible = this.calculateFirstVisibleRow();
|
||||
let lastVisible = this.calculateLastVisibleRow();
|
||||
let totalVisible = lastVisible - firstVisible;
|
||||
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
|
||||
start = firstVisible - Math.floor(numberOffscreen / 2);
|
||||
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
||||
|
||||
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
|
||||
start = firstVisible - Math.floor(numberOffscreen / 2);
|
||||
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
|
||||
} else if (end >= filteredRowsLength) {
|
||||
end = filteredRowsLength;
|
||||
start = end - VISIBLE_ROW_COUNT + 1;
|
||||
}
|
||||
}
|
||||
this.rowOffset = start;
|
||||
this.visibleRows = filteredRows.slice(start, end);
|
||||
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
|
||||
} else if (end >= filteredRowsLength) {
|
||||
end = filteredRowsLength;
|
||||
start = end - VISIBLE_ROW_COUNT + 1;
|
||||
}
|
||||
this.updatingView = false;
|
||||
});
|
||||
}
|
||||
this.rowOffset = start;
|
||||
this.visibleRows = filteredRows.slice(start, end);
|
||||
},
|
||||
calculateFirstVisibleRow() {
|
||||
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
|
||||
let scrollTop = this.scrollable.scrollTop;
|
||||
return Math.floor(scrollTop / this.rowHeight);
|
||||
},
|
||||
calculateLastVisibleRow() {
|
||||
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
|
||||
return Math.floor(bottomScroll / this.rowHeight);
|
||||
let scrollBottom = this.scrollable.scrollTop + this.scrollable.offsetHeight;
|
||||
return Math.ceil(scrollBottom / this.rowHeight);
|
||||
},
|
||||
updateHeaders() {
|
||||
this.headers = this.table.configuration.getVisibleHeaders();
|
||||
@ -412,81 +492,92 @@ export default {
|
||||
}
|
||||
this.table.sortBy(this.sortOptions);
|
||||
},
|
||||
scroll() {
|
||||
if (!this.processingScroll) {
|
||||
this.processingScroll = true;
|
||||
requestAnimationFrame(()=> {
|
||||
this.updateVisibleRows();
|
||||
this.synchronizeScrollX();
|
||||
scroll () {
|
||||
this.updateVisibleRows();
|
||||
this.synchronizeScrollX();
|
||||
|
||||
if (this.shouldSnapToBottom()) {
|
||||
this.autoScroll = true;
|
||||
} else {
|
||||
// If user scrolls away from bottom, disable auto-scroll.
|
||||
// Auto-scroll will be re-enabled if user scrolls to bottom again.
|
||||
this.autoScroll = false;
|
||||
}
|
||||
this.processingScroll = false;
|
||||
});
|
||||
if (this.shouldSnapToBottom()) {
|
||||
this.autoScroll = true;
|
||||
} else {
|
||||
// If user scrolls away from bottom, disable auto-scroll.
|
||||
// Auto-scroll will be re-enabled if user scrolls to bottom again.
|
||||
this.autoScroll = false;
|
||||
}
|
||||
},
|
||||
shouldSnapToBottom() {
|
||||
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
|
||||
},
|
||||
scrollToBottom() {
|
||||
this.scrollable.scrollTop = this.scrollable.scrollHeight;
|
||||
this.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
|
||||
},
|
||||
synchronizeScrollX() {
|
||||
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
||||
},
|
||||
filterChanged(columnKey) {
|
||||
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
||||
this.setHeight();
|
||||
},
|
||||
clearFilter(columnKey) {
|
||||
this.filters[columnKey] = '';
|
||||
this.table.filteredRows.setColumnFilter(columnKey, '');
|
||||
this.setHeight();
|
||||
},
|
||||
rowsAdded(rows) {
|
||||
rowsAdded (rows) {
|
||||
this.setHeight();
|
||||
|
||||
let sizingRow;
|
||||
if (Array.isArray(rows)) {
|
||||
sizingRow = rows[0];
|
||||
} else {
|
||||
sizingRow = rows;
|
||||
}
|
||||
|
||||
if (!this.sizingRows[sizingRow.objectKeyString]) {
|
||||
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
|
||||
this.$nextTick().then(this.calculateColumnWidths);
|
||||
}
|
||||
|
||||
if (!this.updatingView) {
|
||||
this.updatingView = true;
|
||||
requestAnimationFrame(()=> {
|
||||
this.updateVisibleRows();
|
||||
if (this.autoScroll) {
|
||||
this.$nextTick().then(this.scrollToBottom);
|
||||
}
|
||||
this.updatingView = false;
|
||||
});
|
||||
if (this.autoScroll) {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
this.updateVisibleRows();
|
||||
},
|
||||
rowsRemoved(rows) {
|
||||
if (!this.updatingView) {
|
||||
this.updatingView = true;
|
||||
requestAnimationFrame(()=> {
|
||||
this.updateVisibleRows();
|
||||
this.updatingView = false;
|
||||
});
|
||||
}
|
||||
rowsRemoved (rows) {
|
||||
this.setHeight();
|
||||
this.updateVisibleRows();
|
||||
},
|
||||
exportAsCSV() {
|
||||
/**
|
||||
* Calculates height based on total number of rows, and sets table height.
|
||||
*/
|
||||
setHeight() {
|
||||
let filteredRowsLength = this.table.filteredRows.getRows().length;
|
||||
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
|
||||
// Set element height directly to avoid having to wait for Vue to update DOM
|
||||
// which causes subsequent scroll to use an out of date height.
|
||||
this.contentTable.style.height = this.totalHeight + 'px';
|
||||
},
|
||||
exportAsCSV(data) {
|
||||
const headerKeys = Object.keys(this.headers);
|
||||
const justTheData = this.table.filteredRows.getRows()
|
||||
.map(row => row.getFormattedDatum(this.headers));
|
||||
this.csvExporter.export(justTheData, {
|
||||
|
||||
this.csvExporter.export(data, {
|
||||
filename: this.table.domainObject.name + '.csv',
|
||||
headers: headerKeys
|
||||
});
|
||||
},
|
||||
exportAllDataAsCSV() {
|
||||
const justTheData = this.table.filteredRows.getRows()
|
||||
.map(row => row.getFormattedDatum(this.headers));
|
||||
|
||||
this.exportAsCSV(justTheData);
|
||||
},
|
||||
exportMarkedDataAsCSV() {
|
||||
const data = this.table.filteredRows.getRows()
|
||||
.filter(row => row.marked === true)
|
||||
.map(row => row.getFormattedDatum(this.headers));
|
||||
|
||||
this.exportAsCSV(data);
|
||||
},
|
||||
outstandingRequests(loading) {
|
||||
this.loading = loading;
|
||||
},
|
||||
@ -557,21 +648,136 @@ export default {
|
||||
let el = this.$el;
|
||||
let width = el.clientWidth;
|
||||
let height = el.clientHeight;
|
||||
let scrollTop = this.scrollable.scrollTop;
|
||||
|
||||
this.resizePollHandle = setInterval(() => {
|
||||
if ((el.clientWidth !== width || el.clientHeight !== height) && this.isAutosizeEnabled) {
|
||||
this.calculateTableSize();
|
||||
// On some resize events scrollTop is reset to 0. Possibly due to a transition we're using?
|
||||
// Need to preserve scroll position in this case.
|
||||
if (this.autoScroll) {
|
||||
this.scrollToBottom();
|
||||
} else {
|
||||
this.scrollable.scrollTop = scrollTop;
|
||||
}
|
||||
width = el.clientWidth;
|
||||
height = el.clientHeight;
|
||||
}
|
||||
scrollTop = this.scrollable.scrollTop;
|
||||
}, RESIZE_POLL_INTERVAL);
|
||||
},
|
||||
pause(pausedByButton) {
|
||||
if (pausedByButton) {
|
||||
this.pausedByButton = true;
|
||||
}
|
||||
this.paused = true;
|
||||
this.table.pause();
|
||||
},
|
||||
unpause(unpausedByButton) {
|
||||
if (unpausedByButton) {
|
||||
this.paused = false;
|
||||
this.table.unpause();
|
||||
this.markedRows = [];
|
||||
this.pausedByButton = false;
|
||||
} else {
|
||||
if (!this.pausedByButton) {
|
||||
this.paused = false;
|
||||
this.table.unpause();
|
||||
this.markedRows = [];
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
togglePauseByButton() {
|
||||
if (this.paused) {
|
||||
this.unpause(true);
|
||||
} else {
|
||||
this.pause(true);
|
||||
}
|
||||
},
|
||||
undoMarkedRows(unpause) {
|
||||
this.markedRows.forEach(r => r.marked = false);
|
||||
this.markedRows = [];
|
||||
},
|
||||
unmarkRow(rowIndex) {
|
||||
this.undoMarkedRows();
|
||||
this.unpause();
|
||||
},
|
||||
markRow(rowIndex) {
|
||||
let insertMethod = 'unshift';
|
||||
|
||||
if (this.markedRows.length && !this.ctrlKeyPressed) {
|
||||
this.undoMarkedRows();
|
||||
insertMethod = 'push';
|
||||
}
|
||||
|
||||
let markedRow = this.visibleRows[rowIndex];
|
||||
|
||||
this.$set(markedRow, 'marked', true);
|
||||
this.pause();
|
||||
|
||||
this.markedRows[insertMethod](markedRow);
|
||||
},
|
||||
unmarkAllRows(skipUnpause) {
|
||||
this.markedRows.forEach(row => row.marked = false);
|
||||
this.markedRows = [];
|
||||
this.unpause();
|
||||
},
|
||||
markMultipleConcurrentRows(rowIndex) {
|
||||
|
||||
if (!this.markedRows.length) {
|
||||
this.markRow(rowIndex);
|
||||
} else {
|
||||
if (this.markedRows.length > 1) {
|
||||
this.markedRows.forEach((r,i) => {
|
||||
if (i !== 0) {
|
||||
r.marked = false;
|
||||
}
|
||||
});
|
||||
this.markedRows.splice(1);
|
||||
}
|
||||
let lastRowToBeMarked = this.visibleRows[rowIndex];
|
||||
|
||||
let allRows = this.table.filteredRows.getRows(),
|
||||
firstRowIndex = allRows.indexOf(this.markedRows[0]),
|
||||
lastRowIndex = allRows.indexOf(lastRowToBeMarked);
|
||||
|
||||
//supports backward selection
|
||||
if (lastRowIndex < firstRowIndex) {
|
||||
let temp = lastRowIndex;
|
||||
|
||||
lastRowIndex = firstRowIndex;
|
||||
firstRowIndex = temp - 1;
|
||||
}
|
||||
|
||||
for (var i = firstRowIndex + 1; i <= lastRowIndex; i++) {
|
||||
let row = allRows[i];
|
||||
row.marked = true;
|
||||
this.markedRows.push(row);
|
||||
}
|
||||
|
||||
this.$forceUpdate();
|
||||
}
|
||||
},
|
||||
keydown(event) {
|
||||
if ((event.ctrlKey || event.metaKey)) {
|
||||
this.ctrlKeyPressed = true;
|
||||
}
|
||||
},
|
||||
keyup(event) {
|
||||
if ((event.ctrlKey || event.key.toLowerCase() === 'meta')) {
|
||||
console.log('up');
|
||||
this.ctrlKeyPressed = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.filterChanged = _.debounce(this.filterChanged, 500);
|
||||
},
|
||||
mounted() {
|
||||
this.rowsAdded = _.throttle(this.rowsAdded, 200);
|
||||
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
|
||||
this.scroll = _.throttle(this.scroll, 100);
|
||||
|
||||
this.table.on('object-added', this.addObject);
|
||||
this.table.on('object-removed', this.removeObject);
|
||||
@ -585,6 +791,7 @@ export default {
|
||||
//Default sort
|
||||
this.sortOptions = this.table.filteredRows.sortBy();
|
||||
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
|
||||
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
|
||||
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
|
||||
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
|
||||
|
||||
@ -595,6 +802,9 @@ export default {
|
||||
this.calculateScrollbarWidth();
|
||||
|
||||
this.table.initialize();
|
||||
|
||||
document.addEventListener('keydown', this.keydown);
|
||||
document.addEventListener('keyup', this.keyup);
|
||||
},
|
||||
destroyed() {
|
||||
this.table.off('object-added', this.addObject);
|
||||
@ -613,6 +823,9 @@ export default {
|
||||
this.table.configuration.destroy();
|
||||
|
||||
this.table.destroy();
|
||||
|
||||
document.removeEventListener('keydown', this.keydown);
|
||||
document.removeEventListener('keyup', this.keyup);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -20,7 +20,15 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['EventEmitter'], function (EventEmitter) {
|
||||
define(
|
||||
[
|
||||
'EventEmitter',
|
||||
'lodash'
|
||||
],
|
||||
function (
|
||||
EventEmitter,
|
||||
_
|
||||
) {
|
||||
|
||||
/**
|
||||
* Manages selection state for Open MCT
|
||||
@ -47,21 +55,87 @@ define(['EventEmitter'], function (EventEmitter) {
|
||||
* Selects the selectable object and emits the 'change' event.
|
||||
*
|
||||
* @param {object} selectable an object with element and context properties
|
||||
* @param {Boolean} isMultiSelectEvent flag indication shift key is pressed or not
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.select = function (selectable) {
|
||||
Selection.prototype.select = function (selectable, isMultiSelectEvent) {
|
||||
if (!Array.isArray(selectable)) {
|
||||
selectable = [selectable];
|
||||
}
|
||||
|
||||
if (this.selected[0] && this.selected[0].element) {
|
||||
this.selected[0].element.removeAttribute('s-selected');
|
||||
let multiSelect = isMultiSelectEvent &&
|
||||
this.parentSupportsMultiSelect(selectable) &&
|
||||
this.isPeer(selectable) &&
|
||||
!this.selectionContainsParent(selectable);
|
||||
|
||||
if (multiSelect) {
|
||||
this.handleMultiSelect(selectable);
|
||||
} else {
|
||||
this.setSelectionStyles(selectable);
|
||||
this.selected = [selectable];
|
||||
}
|
||||
|
||||
if (this.selected[1] && this.selected[1].element) {
|
||||
this.selected[1].element.removeAttribute('s-selected-parent');
|
||||
this.emit('change', this.selected);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.handleMultiSelect = function (selectable) {
|
||||
if (this.elementSelected(selectable)) {
|
||||
this.remove(selectable);
|
||||
} else {
|
||||
this.addSelectionAttributes(selectable);
|
||||
this.selected.push(selectable);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.elementSelected = function (selectable) {
|
||||
return this.selected.some(selectionPath => _.isEqual(selectionPath, selectable));
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.remove = function (selectable) {
|
||||
this.selected = this.selected.filter(selectionPath => !_.isEqual(selectionPath, selectable));
|
||||
|
||||
if (this.selected.length === 0) {
|
||||
this.removeSelectionAttributes(selectable);
|
||||
selectable[1].element.click(); // Select the parent if there is no selection.
|
||||
} else {
|
||||
this.removeSelectionAttributes(selectable, true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.setSelectionStyles = function (selectable) {
|
||||
this.selected.map(selectionPath => {
|
||||
this.removeSelectionAttributes(selectionPath);
|
||||
});
|
||||
this.addSelectionAttributes(selectable);
|
||||
};
|
||||
|
||||
Selection.prototype.removeSelectionAttributes = function (selectionPath, keepParentStyle) {
|
||||
if (selectionPath[0] && selectionPath[0].element) {
|
||||
selectionPath[0].element.removeAttribute('s-selected');
|
||||
}
|
||||
|
||||
if (selectionPath[1] && selectionPath[1].element && !keepParentStyle) {
|
||||
selectionPath[1].element.removeAttribute('s-selected-parent');
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Adds selection attributes to the selected element and its parent.
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.addSelectionAttributes = function (selectable) {
|
||||
if (selectable[0] && selectable[0].element) {
|
||||
selectable[0].element.setAttribute('s-selected', "");
|
||||
}
|
||||
@ -69,16 +143,36 @@ define(['EventEmitter'], function (EventEmitter) {
|
||||
if (selectable[1] && selectable[1].element) {
|
||||
selectable[1].element.setAttribute('s-selected-parent', "");
|
||||
}
|
||||
};
|
||||
|
||||
this.selected = selectable;
|
||||
this.emit('change', this.selected);
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.parentSupportsMultiSelect = function (selectable) {
|
||||
return selectable[1] && selectable[1].context.supportsMultiSelect;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.selectionContainsParent = function (selectable) {
|
||||
return this.selected.some(selectionPath => _.isEqual(selectionPath[0], selectable[1]));
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.isPeer = function (selectable) {
|
||||
return this.selected.some(selectionPath => _.isEqual(selectionPath[1], selectable[1]));
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.capture = function (selectable) {
|
||||
if (!this.capturing) {
|
||||
let capturingContainsSelectable = this.capturing && this.capturing.includes(selectable);
|
||||
|
||||
if (!this.capturing || capturingContainsSelectable) {
|
||||
this.capturing = [];
|
||||
}
|
||||
|
||||
@ -88,13 +182,14 @@ define(['EventEmitter'], function (EventEmitter) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Selection.prototype.selectCapture = function (selectable) {
|
||||
Selection.prototype.selectCapture = function (selectable, event) {
|
||||
if (!this.capturing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.select(this.capturing.reverse());
|
||||
let reversedCapturing = this.capturing.reverse();
|
||||
delete this.capturing;
|
||||
this.select(reversedCapturing, event.shiftKey);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -112,7 +207,7 @@ define(['EventEmitter'], function (EventEmitter) {
|
||||
* @public
|
||||
*/
|
||||
Selection.prototype.selectable = function (element, context, select) {
|
||||
var selectable = {
|
||||
let selectable = {
|
||||
context: context,
|
||||
element: element
|
||||
};
|
||||
|
@ -54,10 +54,11 @@
|
||||
|
||||
&:after {
|
||||
// App logo
|
||||
top: 0;
|
||||
right: 15%;
|
||||
bottom: 0;
|
||||
left: 15%;
|
||||
$d: 25%;
|
||||
top: $d;
|
||||
right: $d;
|
||||
bottom: $d;
|
||||
left: $d;
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,12 +76,20 @@
|
||||
|
||||
&__image,
|
||||
&__text {
|
||||
height: 50%;
|
||||
flex: 1 1 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__image {
|
||||
height: 35%;
|
||||
}
|
||||
|
||||
&__text {
|
||||
height: 65%;
|
||||
overflow: auto;
|
||||
> * + * {
|
||||
border-top: 1px solid $colorInteriorBorder;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&--licenses {
|
||||
@ -107,7 +116,7 @@
|
||||
|
||||
h1, h2, h3 {
|
||||
font-weight: normal;
|
||||
margin-bottom: 1em;
|
||||
margin-bottom: .25em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
@ -79,7 +79,7 @@ $colorKeyHov: #26d8ff;
|
||||
$colorKeyFilter: invert(36%) sepia(76%) saturate(2514%) hue-rotate(170deg) brightness(99%) contrast(101%);
|
||||
$colorKeyFilterHov: invert(63%) sepia(88%) saturate(3029%) hue-rotate(154deg) brightness(101%) contrast(100%);
|
||||
$colorKeySelectedBg: $colorKey;
|
||||
$uiColor: #00b2ff; // Resize bars, splitter bars, etc.
|
||||
$uiColor: #0093ff; // Resize bars, splitter bars, etc.
|
||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||
$colorA: #ccc;
|
||||
$colorAHov: #fff;
|
||||
@ -143,6 +143,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
|
||||
|
||||
/************************************************** EDITING */
|
||||
$editUIColor: $uiColor; // Base color
|
||||
$editUIColorBg: $editUIColor;
|
||||
$editUIColorFg: #fff;
|
||||
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
|
||||
$editUIBaseColor: #344b8d; // Base color, toolbar bg
|
||||
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
|
||||
@ -159,8 +161,8 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
|
||||
$editFrameColorSelected: #ccc; // Border of selected frames
|
||||
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
|
||||
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
|
||||
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px;
|
||||
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element
|
||||
$editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
|
||||
$editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
|
||||
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
|
||||
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
|
||||
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
|
||||
@ -168,6 +170,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
|
||||
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
|
||||
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
|
||||
$editFrameMovebarH: 10px; // Height of move bar in layout frame
|
||||
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
|
||||
|
||||
// Icons
|
||||
$colorIconAlias: #4af6f3;
|
||||
@ -224,6 +227,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
|
||||
$paletteItemBorderOuterColorSelected: black;
|
||||
$paletteItemBorderInnerColorSelected: white;
|
||||
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
|
||||
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.7)); // Used in .c-click-icon--mixed
|
||||
$mixedSettingBgSize: 5px;
|
||||
|
||||
// Forms
|
||||
$colorCheck: $colorKey;
|
||||
@ -385,8 +390,10 @@ $colorLoadingFg: #776ba2;
|
||||
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
|
||||
|
||||
// Transitions
|
||||
$transIn: all 50ms ease-in-out;
|
||||
$transOut: all 250ms ease-in-out;
|
||||
$transInTime: 50ms;
|
||||
$transOutTime: 250ms;
|
||||
$transIn: all $transInTime ease-in-out;
|
||||
$transOut: all $transOutTime ease-in-out;
|
||||
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
|
||||
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
|
||||
|
@ -147,6 +147,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
|
||||
|
||||
/************************************************** EDITING */
|
||||
$editUIColor: $uiColor; // Base color
|
||||
$editUIColorBg: $editUIColor;
|
||||
$editUIColorFg: #fff;
|
||||
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
|
||||
$editUIBaseColor: #344b8d; // Base color, toolbar bg
|
||||
$editUIBaseColorHov: pullForward($editUIBaseColor, 20%);
|
||||
@ -163,8 +165,8 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
|
||||
$editFrameColorSelected: #ccc; // Border of selected frames
|
||||
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
|
||||
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
|
||||
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px;
|
||||
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element
|
||||
$editFrameSelectedShdw: rgba(black, 0.4) 0 1px 5px 1px;
|
||||
$editFrameSelectedBorder: 1px solid $editFrameColorHov; // Selected frame element
|
||||
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
|
||||
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
|
||||
$editFrameHovMovebarColorBg: pullForward($editFrameMovebarColorBg, 10%); // Hover style
|
||||
@ -172,6 +174,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
|
||||
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
|
||||
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
|
||||
$editFrameMovebarH: 10px; // Height of move bar in layout frame
|
||||
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
|
||||
|
||||
// Icons
|
||||
$colorIconAlias: #4af6f3;
|
||||
@ -228,6 +231,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
|
||||
$paletteItemBorderOuterColorSelected: black;
|
||||
$paletteItemBorderInnerColorSelected: white;
|
||||
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
|
||||
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.7)); // Used in .c-click-icon--mixed
|
||||
$mixedSettingBgSize: 5px;
|
||||
|
||||
// Forms
|
||||
$colorCheck: $colorKey;
|
||||
@ -389,8 +394,10 @@ $colorLoadingFg: #776ba2;
|
||||
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
|
||||
|
||||
// Transitions
|
||||
$transIn: all 50ms ease-in-out;
|
||||
$transOut: all 250ms ease-in-out;
|
||||
$transInTime: 50ms;
|
||||
$transOutTime: 250ms;
|
||||
$transIn: all $transInTime ease-in-out;
|
||||
$transOut: all $transOutTime ease-in-out;
|
||||
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
|
||||
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
|
||||
|
@ -143,6 +143,8 @@ $browseSelectedBorder: 1px solid rgba($colorBodyFg, 0.4);
|
||||
|
||||
/************************************************** EDITING */
|
||||
$editUIColor: $uiColor; // Base color
|
||||
$editUIColorBg: $editUIColor;
|
||||
$editUIColorFg: #fff;
|
||||
$editUIColorHov: pullForward(saturate($uiColor, 10%), 20%); // Hover color when $editUIColor is applied as a base color
|
||||
$editUIBaseColor: #cae1ff; // Base color, toolbar bg
|
||||
$editUIBaseColorHov: pushBack($editUIBaseColor, 20%);
|
||||
@ -159,7 +161,7 @@ $editFrameBorderHov: 1px solid $editFrameColorHov; // Hover on selectable frames
|
||||
$editFrameColorSelected: #333; // Border of selected frames
|
||||
$editFrameColorHandleBg: $colorBodyBg; // Resize handle 'offset' color to make handle standout
|
||||
$editFrameColorHandleFg: $editFrameColorSelected; // Resize handle main color
|
||||
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 10px 1px;
|
||||
$editFrameSelectedShdw: rgba(black, 0.5) 0 1px 5px 2px;
|
||||
$editFrameSelectedBorder: 1px dashed $editFrameColorSelected; // Selected frame element
|
||||
$editFrameMovebarColorBg: $editFrameColor; // Movebar bg color
|
||||
$editFrameMovebarColorFg: pullForward($editFrameMovebarColorBg, 20%); // Grippy lines, container size text
|
||||
@ -168,6 +170,7 @@ $editFrameHovMovebarColorFg: pullForward($editFrameMovebarColorFg, 10%);
|
||||
$editFrameSelectedMovebarColorBg: pullForward($editFrameMovebarColorBg, 15%); // Selected style
|
||||
$editFrameSelectedMovebarColorFg: pullForward($editFrameMovebarColorFg, 15%);
|
||||
$editFrameMovebarH: 10px; // Height of move bar in layout frame
|
||||
$editMarqueeBorder: 1px dashed $editFrameColorSelected;
|
||||
|
||||
// Icons
|
||||
$colorIconAlias: #4af6f3;
|
||||
@ -206,7 +209,7 @@ $colorDisclosureCtrlHov: rgba($colorBodyFg, 0.7);
|
||||
$btnStdH: 24px;
|
||||
$colorCursorGuide: rgba(black, 0.6);
|
||||
$shdwCursorGuide: rgba(white, 0.4) 0 0 2px;
|
||||
$colorLocalControlOvrBg: rgba($colorBodyBg, 0.8);
|
||||
$colorLocalControlOvrBg: rgba($colorBodyFg, 0.8);
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
||||
@ -224,6 +227,8 @@ $menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
|
||||
$paletteItemBorderOuterColorSelected: black;
|
||||
$paletteItemBorderInnerColorSelected: white;
|
||||
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
|
||||
$mixedSettingBg: (transparent rgba($editUIBaseColorHov, 0.9)); // Used in .c-click-icon--mixed
|
||||
$mixedSettingBgSize: 10px;
|
||||
|
||||
// Forms
|
||||
$colorCheck: $colorKey;
|
||||
@ -242,8 +247,8 @@ $colorInputPlaceholder: pushBack($colorBodyFg, 20%);
|
||||
$colorFormText: pushBack($colorBodyFg, 10%);
|
||||
$colorInputIcon: pushBack($colorBodyFg, 25%);
|
||||
$colorFieldHint: pullForward($colorBodyFg, 40%);
|
||||
$shdwInput: inset rgba(black, 0.7) 0 0 1px;
|
||||
$shdwInputHov: inset rgba(black, 0.7) 0 0 2px;
|
||||
$shdwInput: inset rgba(black, 0.5) 0 0 2px;
|
||||
$shdwInputHov: inset rgba(black, 0.8) 0 0 2px;
|
||||
$shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px;
|
||||
$formTBPad: $interiorMargin;
|
||||
$formLRPad: $interiorMargin;
|
||||
@ -385,8 +390,10 @@ $colorLoadingFg: #776ba2;
|
||||
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
|
||||
|
||||
// Transitions
|
||||
$transIn: all 50ms ease-in-out;
|
||||
$transOut: all 250ms ease-in-out;
|
||||
$transInTime: 50ms;
|
||||
$transOutTime: 250ms;
|
||||
$transIn: all $transInTime ease-in-out;
|
||||
$transOut: all $transOutTime ease-in-out;
|
||||
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
|
||||
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
|
||||
|
@ -28,6 +28,7 @@ $dirImgs: 'images/';
|
||||
$controlFadeMs: 100ms;
|
||||
$browseToEditAnimMs: 400ms;
|
||||
$editBorderPulseMs: 500ms;
|
||||
$moveBarOutDelay: 500ms;
|
||||
|
||||
/************************** SPATIAL */
|
||||
$interiorMarginSm: 3px;
|
||||
@ -84,6 +85,8 @@ $waitSpinnerTreeBorderW: 3px;
|
||||
/*************** Messages */
|
||||
$messageIconD: 80px;
|
||||
$messageListIconD: 32px;
|
||||
/*************** Tables */
|
||||
$tableResizeColHitareaD: 6px;
|
||||
|
||||
/************************** MOBILE */
|
||||
$mobileMenuIconD: 24px; // Used
|
||||
|
@ -85,6 +85,15 @@ button {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
$c1: nth($mixedSettingBg, 1);
|
||||
$c2: nth($mixedSettingBg, 2);
|
||||
$mixedBgD: $mixedSettingBgSize $mixedSettingBgSize;
|
||||
|
||||
&--mixed {
|
||||
// E.g. click-icons in toolbar that apply to multiple selected items with different settings
|
||||
@include bgStripes2Color($c1, $c2, $bgSize: $mixedBgD);
|
||||
}
|
||||
|
||||
&--swatched {
|
||||
// Color control, show swatch element
|
||||
display: flex;
|
||||
@ -93,9 +102,9 @@ button {
|
||||
justify-content: center;
|
||||
|
||||
> [class*='swatch'] {
|
||||
box-shadow: inset rgba(black, 0.2) 0 0 1px;
|
||||
box-shadow: inset rgba($editUIBaseColorFg, 0.2) 0 0 0 1px;
|
||||
flex: 0 0 auto;
|
||||
height: 4px;
|
||||
height: 5px;
|
||||
width: 100%;
|
||||
margin-top: 1px;
|
||||
}
|
||||
@ -105,6 +114,13 @@ button {
|
||||
flex: 1 1 auto;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
&--mixed {
|
||||
// Styling for swatched buttons when settings are mixed
|
||||
> [class*='swatch'] {
|
||||
@include bgStripes2Color($c1, $c2, $bgSize: $mixedBgD);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,8 +164,6 @@ button {
|
||||
display: block;
|
||||
font-family: symbolsfont;
|
||||
font-size: 1rem * $s;
|
||||
transform-origin: center;
|
||||
transition: $transOut;
|
||||
}
|
||||
}
|
||||
|
||||
@ -379,6 +393,11 @@ select {
|
||||
color: $colorMenuIc;
|
||||
font-size: 1em;
|
||||
margin-right: $interiorMargin;
|
||||
min-width: 1em;
|
||||
}
|
||||
|
||||
&:not([class]):before {
|
||||
content: ''; // Add this element so that menu items without an icon still indent properly
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -566,6 +585,10 @@ select {
|
||||
margin-right: $m;
|
||||
}
|
||||
|
||||
.c-separator {
|
||||
@include cToolbarSeparator();
|
||||
}
|
||||
|
||||
.c-toolbar {
|
||||
> * + * {
|
||||
margin-left: 2px;
|
||||
@ -721,6 +744,7 @@ input[type="range"] {
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** LOCAL CONTROLS */
|
||||
.h-local-controls {
|
||||
// Holder for local controls
|
||||
&--horz {
|
||||
|
@ -51,6 +51,22 @@ mct-plot {
|
||||
}
|
||||
}
|
||||
|
||||
.is-editing {
|
||||
.gl-plot.child-frame {
|
||||
&:hover {
|
||||
background: rgba($editUIColorBg, 0.1);
|
||||
box-shadow: inset rgba($editUIColorBg, 0.8) 0 0 0 1px;
|
||||
}
|
||||
|
||||
&[s-selected] {
|
||||
border: 1px solid $editUIColorFg !important;
|
||||
color: $editUIColorFg !important;
|
||||
box-shadow: $editFrameSelectedShdw;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gl-plot {
|
||||
color: $colorPlotFg;
|
||||
display: flex;
|
||||
@ -548,3 +564,10 @@ mct-plot {
|
||||
top: 0; bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.s-status-timeconductor-unsynced {
|
||||
.t-object-alert.t-alert-unsynced {
|
||||
@extend .icon-alert-triangle;
|
||||
color: $colorPausedBg;
|
||||
}
|
||||
}
|
||||
|
@ -482,6 +482,8 @@ body.mobile.phone {
|
||||
|
||||
.widget-edit-holder {
|
||||
display: flex; // Overrides `display: none` during Browse mode
|
||||
flex: 1 1 auto;
|
||||
|
||||
.flex-accordion-holder {
|
||||
// Needed because otherwise accordion elements "creep" when contents expand and contract
|
||||
display: block !important;
|
||||
@ -497,12 +499,6 @@ body.mobile.phone {
|
||||
// Test data is expanded and rules are collapsed
|
||||
// Make text data take up all the vertical space
|
||||
.flex-accordion-holder { display: flex; }
|
||||
.widget-test-data {
|
||||
flex-grow: 999999;
|
||||
}
|
||||
.w-widget-test-data-items {
|
||||
max-height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.expanded-widget-rules {
|
||||
|
@ -108,7 +108,7 @@
|
||||
}
|
||||
|
||||
@mixin bgStripes($c: yellow, $a: 0.1, $bgsize: 5px, $angle: 90deg) {
|
||||
background-image: linear-gradient($angle,
|
||||
background: linear-gradient($angle,
|
||||
rgba($c, $a) 25%, transparent 25%,
|
||||
transparent 50%, rgba($c, $a) 50%,
|
||||
rgba($c, $a) 75%, transparent 75%,
|
||||
@ -117,6 +117,16 @@
|
||||
background-size: $bgsize $bgsize;
|
||||
}
|
||||
|
||||
@mixin bgStripes2Color($c1, $c2, $bgSize, $angle: -45deg) {
|
||||
background: linear-gradient(-45deg,
|
||||
$c1 0%, $c1 25%,
|
||||
$c2 25%, $c2 50%,
|
||||
$c1 50%, $c1 75%,
|
||||
$c2 75%, $c2 100%
|
||||
) repeat;
|
||||
background-size: $bgSize;
|
||||
}
|
||||
|
||||
@mixin disabled() {
|
||||
opacity: $controlDisabledOpacity;
|
||||
pointer-events: none !important;
|
||||
@ -415,12 +425,13 @@
|
||||
// Background is displayed on hover
|
||||
// Padding is included to facilitate a bigger hit area
|
||||
// Make the icon bigger relative to its container
|
||||
$pLR: 4px;
|
||||
$pTB: 4px;
|
||||
|
||||
@include cControl();
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
$pLR: 4px;
|
||||
$pTB: 4px;
|
||||
transition: $transOut;
|
||||
border-radius: $controlCr;
|
||||
padding: $pTB $pLR;
|
||||
@ -564,7 +575,6 @@
|
||||
@mixin test($c: deeppink, $a: 0.3) {
|
||||
background: rgba($c, $a) !important;
|
||||
background-color: rgba($c, $a) !important;
|
||||
box-shadow: deeppink 0 0 10px 1px !important;
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,19 +20,38 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*************************************************** MIXINS */
|
||||
@mixin statusStyle($bg, $fg, $ic) {
|
||||
background: $bg !important;
|
||||
background-color: $bg !important;
|
||||
color: $fg !important;
|
||||
@mixin statusStyle($bg, $fg, $imp: false) {
|
||||
$impStr: null;
|
||||
@if $imp {
|
||||
$impStr: !important;
|
||||
}
|
||||
background: $bg $impStr;
|
||||
background-color: $bg $impStr;
|
||||
color: $fg $impStr;
|
||||
}
|
||||
|
||||
@mixin statusIcon($ic, $glyph: null, $imp: false) {
|
||||
$impStr: null;
|
||||
@if $imp {
|
||||
$impStr: !important;
|
||||
}
|
||||
&:before {
|
||||
color: $ic;
|
||||
display: inline-block;
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.7em;
|
||||
font-size: 0.8em;
|
||||
margin-right: $interiorMargin;
|
||||
@if $glyph != null {
|
||||
content: $glyph $impStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin statusStyleCombined($bg, $fg, $ic) {
|
||||
@include statusStyle($bg, $fg, $imp: true);
|
||||
@include statusIcon($ic);
|
||||
}
|
||||
|
||||
@mixin elementStatusColors($c) {
|
||||
// Sets bg and icon colors for elements
|
||||
background: rgba($c, 0.5) !important;
|
||||
@ -47,16 +66,45 @@
|
||||
}
|
||||
}
|
||||
|
||||
.is-limit--yellow {
|
||||
@include statusStyle($colorLimitYellowBg, $colorLimitYellowFg, $colorLimitYellowIc);
|
||||
&.is-limit--upr:before { content: $glyph-icon-arrow-up; }
|
||||
&.is-limit--lwr:before { content: $glyph-icon-arrow-down; }
|
||||
@mixin andUprLwr {
|
||||
&.is-limit--upr:before { content: $glyph-icon-arrow-up !important; }
|
||||
&.is-limit--lwr:before { content: $glyph-icon-arrow-down !important; }
|
||||
}
|
||||
|
||||
.is-limit--red {
|
||||
@include statusStyle($colorLimitRedBg, $colorLimitRedFg, $colorLimitRedIc);
|
||||
&.is-limit--upr:before { content: $glyph-icon-arrow-double-up; }
|
||||
&.is-limit--lwr:before { content: $glyph-icon-arrow-double-down; }
|
||||
/*************************************************** STYLES */
|
||||
*:not(tr) {
|
||||
&.is-limit--yellow {
|
||||
@include statusStyle($colorLimitYellowBg, $colorLimitYellowFg, true);
|
||||
@include statusIcon($colorLimitYellowIc, $glyph-icon-alert-rect);
|
||||
@include andUprLwr();
|
||||
}
|
||||
|
||||
&.is-limit--red {
|
||||
@include statusStyle($colorLimitRedBg, $colorLimitRedFg, true);
|
||||
@include statusIcon($colorLimitRedIc, $glyph-icon-alert-triangle);
|
||||
@include andUprLwr();
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
&.is-limit--yellow {
|
||||
@include statusStyle($colorLimitYellowBg, $colorLimitYellowFg);
|
||||
td:first-child {
|
||||
@include statusIcon($colorLimitYellowIc, $glyph-icon-alert-rect);
|
||||
}
|
||||
td { color: $colorLimitYellowFg; }
|
||||
}
|
||||
|
||||
&.is-limit--red {
|
||||
@include statusStyle($colorLimitRedBg, $colorLimitRedFg);
|
||||
td:first-child {
|
||||
@include statusIcon($colorLimitRedIc, $glyph-icon-alert-triangle);
|
||||
}
|
||||
td { color: $colorLimitRedFg; }
|
||||
}
|
||||
|
||||
&.is-limit--upr { td:first-child:before { content: $glyph-icon-arrow-up !important; } }
|
||||
&.is-limit--lwr { td:first-child:before { content: $glyph-icon-arrow-down !important; } }
|
||||
}
|
||||
|
||||
/*************************************************** STATUS */
|
||||
|
@ -53,30 +53,68 @@ table {
|
||||
a { color: $colorBtnMajorBg; }
|
||||
}
|
||||
|
||||
.is-editing {
|
||||
td.is-selectable {
|
||||
&:hover {
|
||||
background: rgba($editUIColorBg, 0.1);
|
||||
box-shadow: inset rgba($editUIColorBg, 0.8) 0 0 0 1px;
|
||||
}
|
||||
|
||||
&[s-selected] {
|
||||
background: $editUIColorBg !important;
|
||||
border: 1px solid $editUIColorFg !important;
|
||||
color: $editUIColorFg !important;
|
||||
box-shadow: $editFrameSelectedShdw;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** C-TABLE */
|
||||
div.c-table {
|
||||
// When c-table is used as a wrapper element in more complex table views
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.c-table-wrapper {
|
||||
// Wraps .c-control-bar and .c-table
|
||||
@include abs();
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .c-table {
|
||||
height: auto;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-top: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
.c-table-control-bar {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
.c-table {
|
||||
// Can be used by any type of table, scrolling, LAD, etc.
|
||||
$min-w: 50px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
&__control-bar,
|
||||
&__headers-w {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/******************************* ELEMENTS */
|
||||
|
||||
&__control-bar {
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
thead tr,
|
||||
[class*="__header"] {
|
||||
&.c-table__headers {
|
||||
background: $colorTabHeaderBg;
|
||||
|
||||
th {
|
||||
|
@ -33,3 +33,4 @@
|
||||
@import "table";
|
||||
@import "legacy";
|
||||
@import "legacy-plots";
|
||||
@import "legacy-messages";
|
||||
|
@ -26,21 +26,19 @@
|
||||
'has-complex-content': complexContent
|
||||
}">
|
||||
<div class="c-so-view__header">
|
||||
<div class="c-so-view__header__start">
|
||||
<div class="c-so-view__header__name"
|
||||
:class="cssClass">
|
||||
{{ domainObject && domainObject.name }}
|
||||
</div>
|
||||
<context-menu-drop-down
|
||||
<div class="c-so-view__header__icon" :class="cssClass"></div>
|
||||
<div class="c-so-view__header__name">
|
||||
{{ domainObject && domainObject.name }}
|
||||
</div>
|
||||
<context-menu-drop-down
|
||||
:object-path="objectPath">
|
||||
</context-menu-drop-down>
|
||||
</div>
|
||||
<div class="c-so-view__header__end">
|
||||
<div class="c-button icon-expand local-controls--hidden"
|
||||
title="View Large"
|
||||
@click="expand">
|
||||
</div>
|
||||
</div>
|
||||
</context-menu-drop-down>
|
||||
</div>
|
||||
<div class="c-so-view__local-controls c-so-view__view-large h-local-controls c-local-controls--show-on-hover">
|
||||
<button class="c-button icon-expand"
|
||||
title="View Large"
|
||||
@click="expand">
|
||||
</button>
|
||||
</div>
|
||||
<object-view
|
||||
class="c-so-view__object-view"
|
||||
@ -65,45 +63,53 @@
|
||||
align-items: center;
|
||||
margin-bottom: $interiorMargin;
|
||||
|
||||
&__start,
|
||||
&__end {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include headerFont(1em);
|
||||
display: flex;
|
||||
&:before {
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--no-frame > .c-so-view__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
@include headerFont(1.2em);
|
||||
flex: 0 1 auto;
|
||||
|
||||
&:before {
|
||||
// Object type icon
|
||||
&__icon {
|
||||
flex: 0 0 auto;
|
||||
margin-right: $interiorMarginSm;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include headerFont(1em);
|
||||
@include ellipsize();
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.c-so-view--no-frame) {
|
||||
background: $colorBodyBg;
|
||||
border: $browseFrameBorder;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
|
||||
&--no-frame {
|
||||
> .c-so-view__header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .c-so-view__local-controls {
|
||||
top: $interiorMarginSm; right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
&__local-controls {
|
||||
position: absolute;
|
||||
top: $interiorMargin; right: $interiorMargin;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__view-large {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.has-complex-content {
|
||||
> .c-so-view__view-large { display: block; }
|
||||
}
|
||||
|
||||
/*************************** OBJECT VIEW */
|
||||
&__object-view {
|
||||
flex: 1 1 auto;
|
||||
height: 0; // Chrome 73 overflow bug fix
|
||||
overflow: auto;
|
||||
|
||||
.c-object-view {
|
||||
@ -117,7 +123,12 @@
|
||||
.c-click-icon,
|
||||
.c-button {
|
||||
// Shrink buttons a bit when they appear in a frame
|
||||
font-size: 0.8em;
|
||||
align-items: baseline;
|
||||
font-size: 0.85em;
|
||||
padding: 3px 5px;
|
||||
&:before {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -128,7 +139,9 @@
|
||||
|
||||
const SIMPLE_CONTENT_TYPES = [
|
||||
'clock',
|
||||
'summary-widget'
|
||||
'timer',
|
||||
'summary-widget',
|
||||
'hyperlink'
|
||||
];
|
||||
|
||||
export default {
|
||||
|
@ -59,7 +59,8 @@ export default {
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
navigateToPath: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -34,7 +34,10 @@ export default {
|
||||
this.currentObject = this.object;
|
||||
this.updateView();
|
||||
this.$el.addEventListener('dragover', this.onDragOver);
|
||||
this.$el.addEventListener('drop', this.onDrop);
|
||||
this.$el.addEventListener('drop', this.editIfEditable, {
|
||||
capture: true
|
||||
});
|
||||
this.$el.addEventListener('drop', this.addObjectToParent);
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
@ -54,6 +57,10 @@ export default {
|
||||
this.removeSelectable();
|
||||
delete this.removeSelectable;
|
||||
}
|
||||
|
||||
if (this.composition) {
|
||||
this.composition._destroy();
|
||||
}
|
||||
},
|
||||
invokeEditModeHandler(editMode) {
|
||||
this.currentView.onEditModeChange(editMode);
|
||||
@ -67,25 +74,28 @@ export default {
|
||||
if (!this.currentObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.composition = this.openmct.composition.get(this.currentObject);
|
||||
if (this.composition) {
|
||||
this.composition._synchronize();
|
||||
this.loadComposition();
|
||||
}
|
||||
|
||||
this.viewContainer = document.createElement('div');
|
||||
this.viewContainer.classList.add('c-object-view','u-contents');
|
||||
this.$el.append(this.viewContainer);
|
||||
let provider = this.openmct.objectViews.getByProviderKey(this.viewKey);
|
||||
|
||||
let provider = this.getViewProvider();
|
||||
if (!provider) {
|
||||
provider = this.openmct.objectViews.get(this.currentObject)[0];
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (provider.edit && this.showEditView) {
|
||||
if (this.openmct.editor.isEditing()) {
|
||||
this.currentView = provider.edit(this.currentObject);
|
||||
} else {
|
||||
this.currentView = provider.view(this.currentObject, false);
|
||||
}
|
||||
|
||||
this.openmct.editor.on('isEditing', this.toggleEditView);
|
||||
this.releaseEditModeHandler = () => this.openmct.editor.off('isEditing', this.toggleEditView);
|
||||
} else {
|
||||
@ -113,6 +123,10 @@ export default {
|
||||
delete this.removeSelectable;
|
||||
}
|
||||
|
||||
if (this.composition) {
|
||||
this.composition._destroy();
|
||||
}
|
||||
|
||||
this.currentObject = object;
|
||||
this.unlisten = this.openmct.objects.observe(this.currentObject, '*', (mutatedObject) => {
|
||||
this.currentObject = mutatedObject;
|
||||
@ -121,6 +135,9 @@ export default {
|
||||
this.viewKey = viewKey;
|
||||
this.updateView(immediatelySelect);
|
||||
},
|
||||
loadComposition() {
|
||||
return this.composition.load();
|
||||
},
|
||||
getSelectionContext() {
|
||||
if (this.currentView.getSelectionContext) {
|
||||
return this.currentView.getSelectionContext();
|
||||
@ -133,15 +150,37 @@ export default {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
onDrop(event) {
|
||||
if (this.hasComposableDomainObject(event)) {
|
||||
addObjectToParent(event) {
|
||||
if (this.hasComposableDomainObject(event) && this.composition) {
|
||||
let composableDomainObject = this.getComposableDomainObject(event);
|
||||
this.currentObject.composition.push(composableDomainObject.identifier);
|
||||
this.openmct.objects.mutate(this.currentObject, 'composition', this.currentObject.composition);
|
||||
this.loadComposition().then(() => {
|
||||
this.composition.add(composableDomainObject);
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
},
|
||||
getViewProvider() {
|
||||
let provider = this.openmct.objectViews.getByProviderKey(this.viewKey);
|
||||
|
||||
if (!provider) {
|
||||
provider = this.openmct.objectViews.get(this.currentObject)[0];
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return provider;
|
||||
},
|
||||
editIfEditable(event) {
|
||||
let provider = this.getViewProvider();
|
||||
if (provider &&
|
||||
provider.canEdit &&
|
||||
provider.canEdit(this.currentObject) &&
|
||||
!this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.edit();
|
||||
}
|
||||
},
|
||||
hasComposableDomainObject(event) {
|
||||
return event.dataTransfer.types.includes('openmct/composable-domain-object')
|
||||
},
|
||||
|
@ -67,6 +67,7 @@
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import Search from '../components/search.vue';
|
||||
import ObjectLabel from '../components/ObjectLabel.vue';
|
||||
|
||||
@ -82,7 +83,8 @@ export default {
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
parentObject: undefined,
|
||||
currentSearch: '',
|
||||
isDragging: false
|
||||
isDragging: false,
|
||||
selection: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -99,10 +101,15 @@ export default {
|
||||
this.showSelection(this.openmct.selection.get());
|
||||
},
|
||||
showSelection(selection) {
|
||||
if (_.isEqual(this.selection, selection)) {
|
||||
return;
|
||||
}
|
||||
this.selection = selection;
|
||||
this.elements = [];
|
||||
this.elementsCache = {};
|
||||
this.listeners = [];
|
||||
this.parentObject = selection[0].context.item;
|
||||
this.parentObject = selection && selection[0] && selection[0][0].context.item;
|
||||
|
||||
if (this.mutationUnobserver) {
|
||||
this.mutationUnobserver();
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user