mirror of
https://github.com/nasa/openmct.git
synced 2025-02-07 11:30:28 +00:00
Merge branch 'master' into iso-date-format
This commit is contained in:
commit
cdf9c50b8a
@ -100,6 +100,6 @@ module.exports = (config) => {
|
|||||||
},
|
},
|
||||||
concurrency: 1,
|
concurrency: 1,
|
||||||
singleRun: true,
|
singleRun: true,
|
||||||
browserNoActivityTimeout: 90000
|
browserNoActivityTimeout: 400000
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.0.0-snapshot",
|
"version": "1.3.0-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -46,7 +46,7 @@ define([
|
|||||||
this.eventEmitter = new EventEmitter();
|
this.eventEmitter = new EventEmitter();
|
||||||
this.providers = {};
|
this.providers = {};
|
||||||
this.rootRegistry = new RootRegistry();
|
this.rootRegistry = new RootRegistry();
|
||||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
this.rootProvider = new RootObjectProvider.default(this.rootRegistry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,28 +20,42 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
class RootObjectProvider {
|
||||||
], function (
|
constructor(rootRegistry) {
|
||||||
) {
|
if (!RootObjectProvider.instance) {
|
||||||
|
|
||||||
function RootObjectProvider(rootRegistry) {
|
|
||||||
this.rootRegistry = rootRegistry;
|
this.rootRegistry = rootRegistry;
|
||||||
}
|
this.rootObject = {
|
||||||
|
|
||||||
RootObjectProvider.prototype.get = function () {
|
|
||||||
return this.rootRegistry.getRoots()
|
|
||||||
.then(function (roots) {
|
|
||||||
return {
|
|
||||||
identifier: {
|
identifier: {
|
||||||
key: "ROOT",
|
key: "ROOT",
|
||||||
namespace: ""
|
namespace: ""
|
||||||
},
|
},
|
||||||
name: 'The root object',
|
name: 'The root object',
|
||||||
type: 'root',
|
type: 'root',
|
||||||
composition: roots
|
composition: []
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
RootObjectProvider.instance = this;
|
||||||
|
} else if (rootRegistry) {
|
||||||
|
// if called twice, update instance rootRegistry
|
||||||
|
RootObjectProvider.instance.rootRegistry = rootRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
return RootObjectProvider;
|
return RootObjectProvider.instance; // eslint-disable-line no-constructor-return
|
||||||
});
|
}
|
||||||
|
|
||||||
|
updateName(name) {
|
||||||
|
this.rootObject.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get() {
|
||||||
|
let roots = await this.rootRegistry.getRoots();
|
||||||
|
this.rootObject.composition = roots;
|
||||||
|
|
||||||
|
return this.rootObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function instance(rootRegistry) {
|
||||||
|
return new RootObjectProvider(rootRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default instance;
|
||||||
|
@ -19,24 +19,25 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
define([
|
import RootObjectProvider from '../RootObjectProvider';
|
||||||
'../RootObjectProvider'
|
|
||||||
], function (
|
describe('RootObjectProvider', function () {
|
||||||
RootObjectProvider
|
// let rootRegistry;
|
||||||
) {
|
|
||||||
describe('RootObjectProvider', function () {
|
|
||||||
let rootRegistry;
|
|
||||||
let rootObjectProvider;
|
let rootObjectProvider;
|
||||||
|
let roots = ['some root'];
|
||||||
|
let rootRegistry = {
|
||||||
|
getRoots: () => {
|
||||||
|
return Promise.resolve(roots);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
rootRegistry = jasmine.createSpyObj('rootRegistry', ['getRoots']);
|
|
||||||
rootRegistry.getRoots.and.returnValue(Promise.resolve(['some root']));
|
|
||||||
rootObjectProvider = new RootObjectProvider(rootRegistry);
|
rootObjectProvider = new RootObjectProvider(rootRegistry);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports fetching root', function () {
|
it('supports fetching root', async () => {
|
||||||
return rootObjectProvider.get()
|
let root = await rootObjectProvider.get();
|
||||||
.then(function (root) {
|
|
||||||
expect(root).toEqual({
|
expect(root).toEqual({
|
||||||
identifier: {
|
identifier: {
|
||||||
key: "ROOT",
|
key: "ROOT",
|
||||||
@ -47,6 +48,4 @@ define([
|
|||||||
composition: ['some root']
|
composition: ['some root']
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -68,7 +68,7 @@ export default class Condition extends EventEmitter {
|
|||||||
this.description = '';
|
this.description = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
getResult(datum) {
|
updateResult(datum) {
|
||||||
if (!datum || !datum.id) {
|
if (!datum || !datum.id) {
|
||||||
console.log('no data received');
|
console.log('no data received');
|
||||||
|
|
||||||
@ -79,9 +79,9 @@ export default class Condition extends EventEmitter {
|
|||||||
|
|
||||||
this.criteria.forEach(criterion => {
|
this.criteria.forEach(criterion => {
|
||||||
if (this.isAnyOrAllTelemetry(criterion)) {
|
if (this.isAnyOrAllTelemetry(criterion)) {
|
||||||
criterion.getResult(datum, this.conditionManager.telemetryObjects);
|
criterion.updateResult(datum, this.conditionManager.telemetryObjects);
|
||||||
} else {
|
} else {
|
||||||
criterion.getResult(datum);
|
criterion.updateResult(datum);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,7 +57,9 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.telemetryObjects[id] = Object.assign({}, endpoint, {telemetryMetaData: this.openmct.telemetry.getMetadata(endpoint).valueMetadatas});
|
const metadata = this.openmct.telemetry.getMetadata(endpoint);
|
||||||
|
|
||||||
|
this.telemetryObjects[id] = Object.assign({}, endpoint, {telemetryMetaData: metadata ? metadata.valueMetadatas : []});
|
||||||
this.subscriptions[id] = this.openmct.telemetry.subscribe(
|
this.subscriptions[id] = this.openmct.telemetry.subscribe(
|
||||||
endpoint,
|
endpoint,
|
||||||
this.telemetryReceived.bind(this, endpoint)
|
this.telemetryReceived.bind(this, endpoint)
|
||||||
@ -322,8 +324,11 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
let timestamp = {};
|
let timestamp = {};
|
||||||
timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
|
timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
|
||||||
|
|
||||||
this.conditions.forEach(condition => {
|
//We want to stop when the first condition evaluates to true.
|
||||||
condition.getResult(normalizedDatum);
|
this.conditions.some((condition) => {
|
||||||
|
condition.updateResult(normalizedDatum);
|
||||||
|
|
||||||
|
return condition.result === true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updateCurrentCondition(timestamp);
|
this.updateCurrentCondition(timestamp);
|
||||||
|
@ -149,7 +149,7 @@ describe("The condition", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("gets the result of a condition when new telemetry data is received", function () {
|
it("gets the result of a condition when new telemetry data is received", function () {
|
||||||
conditionObj.getResult({
|
conditionObj.updateResult({
|
||||||
value: '0',
|
value: '0',
|
||||||
utc: 'Hi',
|
utc: 'Hi',
|
||||||
id: testTelemetryObject.identifier.key
|
id: testTelemetryObject.identifier.key
|
||||||
@ -158,7 +158,7 @@ describe("The condition", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("gets the result of a condition when new telemetry data is received", function () {
|
it("gets the result of a condition when new telemetry data is received", function () {
|
||||||
conditionObj.getResult({
|
conditionObj.updateResult({
|
||||||
value: '1',
|
value: '1',
|
||||||
utc: 'Hi',
|
utc: 'Hi',
|
||||||
id: testTelemetryObject.identifier.key
|
id: testTelemetryObject.identifier.key
|
||||||
@ -167,14 +167,14 @@ describe("The condition", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the old result new telemetry data is not used by it", function () {
|
it("keeps the old result new telemetry data is not used by it", function () {
|
||||||
conditionObj.getResult({
|
conditionObj.updateResult({
|
||||||
value: '0',
|
value: '0',
|
||||||
utc: 'Hi',
|
utc: 'Hi',
|
||||||
id: testTelemetryObject.identifier.key
|
id: testTelemetryObject.identifier.key
|
||||||
});
|
});
|
||||||
expect(conditionObj.result).toBeTrue();
|
expect(conditionObj.result).toBeTrue();
|
||||||
|
|
||||||
conditionObj.getResult({
|
conditionObj.updateResult({
|
||||||
value: '1',
|
value: '1',
|
||||||
utc: 'Hi',
|
utc: 'Hi',
|
||||||
id: '1234'
|
id: '1234'
|
||||||
|
@ -122,7 +122,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
|||||||
return datum;
|
return datum;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResult(data, telemetryObjects) {
|
updateResult(data, telemetryObjects) {
|
||||||
const validatedData = this.isValid() ? data : {};
|
const validatedData = this.isValid() ? data : {};
|
||||||
|
|
||||||
if (validatedData) {
|
if (validatedData) {
|
||||||
|
@ -120,7 +120,7 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
return datum;
|
return datum;
|
||||||
}
|
}
|
||||||
|
|
||||||
getResult(data) {
|
updateResult(data) {
|
||||||
const validatedData = this.isValid() ? data : {};
|
const validatedData = this.isValid() ? data : {};
|
||||||
if (this.isStalenessCheck()) {
|
if (this.isStalenessCheck()) {
|
||||||
if (this.stalenessSubscription) {
|
if (this.stalenessSubscription) {
|
||||||
@ -201,8 +201,10 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
let metadataObject;
|
let metadataObject;
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
const telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
const telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||||
|
if (telemetryMetadata) {
|
||||||
metadataObject = telemetryMetadata.valueMetadatas.find((valueMetadata) => valueMetadata.key === metadata);
|
metadataObject = telemetryMetadata.valueMetadatas.find((valueMetadata) => valueMetadata.key === metadata);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return metadataObject;
|
return metadataObject;
|
||||||
}
|
}
|
||||||
@ -211,7 +213,7 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
let inputValue;
|
let inputValue;
|
||||||
if (metadataObject) {
|
if (metadataObject) {
|
||||||
if (metadataObject.enumerations && input.length) {
|
if (metadataObject.enumerations && input.length) {
|
||||||
const enumeration = metadataObject.enumerations[input[0]];
|
const enumeration = metadataObject.enumerations.find((item) => item.value.toString() === input[0].toString());
|
||||||
if (enumeration !== undefined && enumeration.string) {
|
if (enumeration !== undefined && enumeration.string) {
|
||||||
inputValue = [enumeration.string];
|
inputValue = [enumeration.string];
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ describe("The telemetry criterion", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns a result on new data from relevant telemetry providers", function () {
|
it("returns a result on new data from relevant telemetry providers", function () {
|
||||||
telemetryCriterion.getResult({
|
telemetryCriterion.updateResult({
|
||||||
value: 'Hello',
|
value: 'Hello',
|
||||||
utc: 'Hi',
|
utc: 'Hi',
|
||||||
id: testTelemetryObject.identifier.key
|
id: testTelemetryObject.identifier.key
|
||||||
|
@ -45,8 +45,9 @@ describe('the plugin', function () {
|
|||||||
type: "test-object",
|
type: "test-object",
|
||||||
name: "Test Object",
|
name: "Test Object",
|
||||||
telemetry: {
|
telemetry: {
|
||||||
valueMetadatas: [{
|
values: [{
|
||||||
key: "some-key",
|
key: "some-key2",
|
||||||
|
source: "some-key2",
|
||||||
name: "Some attribute",
|
name: "Some attribute",
|
||||||
hints: {
|
hints: {
|
||||||
range: 2
|
range: 2
|
||||||
@ -64,6 +65,13 @@ describe('the plugin', function () {
|
|||||||
source: "value",
|
source: "value",
|
||||||
name: "Test",
|
name: "Test",
|
||||||
format: "string"
|
format: "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "some-key",
|
||||||
|
source: "some-key",
|
||||||
|
hints: {
|
||||||
|
domain: 1
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -458,7 +466,7 @@ describe('the plugin', function () {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
xit('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
|
it('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
|
||||||
|
|
||||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||||
@ -480,7 +488,7 @@ describe('the plugin', function () {
|
|||||||
}, 400);
|
}, 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
xit('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
|
it('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
|
||||||
const date = Date.now();
|
const date = Date.now();
|
||||||
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["0.4"];
|
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["0.4"];
|
||||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||||
@ -500,10 +508,133 @@ describe('the plugin', function () {
|
|||||||
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
|
key: 'cf4456a9-296a-4e6b-b182-62ed29cd15b9'
|
||||||
},
|
},
|
||||||
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
|
conditionId: '2532d90a-e0d6-4935-b546-3123522da2de',
|
||||||
utc: undefined
|
utc: date
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('the condition evaluation', () => {
|
||||||
|
let conditionSetDomainObject;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
conditionSetDomainObject = {
|
||||||
|
"configuration": {
|
||||||
|
"conditionTestData": [
|
||||||
|
{
|
||||||
|
"telemetry": "",
|
||||||
|
"metadata": "",
|
||||||
|
"input": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditionCollection": [
|
||||||
|
{
|
||||||
|
"id": "39584410-cbf9-499e-96dc-76f27e69885f",
|
||||||
|
"configuration": {
|
||||||
|
"name": "Unnamed Condition0",
|
||||||
|
"output": "Any telemetry less than 0",
|
||||||
|
"trigger": "all",
|
||||||
|
"criteria": [
|
||||||
|
{
|
||||||
|
"id": "35400132-63b0-425c-ac30-8197df7d5864",
|
||||||
|
"telemetry": "any",
|
||||||
|
"operation": "lessThan",
|
||||||
|
"input": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"metadata": "some-key"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"summary": "Match if all criteria are met: Any telemetry value is less than 0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "39584410-cbf9-499e-96dc-76f27e69885d",
|
||||||
|
"configuration": {
|
||||||
|
"name": "Unnamed Condition",
|
||||||
|
"output": "Any telemetry greater than 0",
|
||||||
|
"trigger": "all",
|
||||||
|
"criteria": [
|
||||||
|
{
|
||||||
|
"id": "35400132-63b0-425c-ac30-8197df7d5862",
|
||||||
|
"telemetry": "any",
|
||||||
|
"operation": "greaterThan",
|
||||||
|
"input": [
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"metadata": "some-key"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"summary": "Match if all criteria are met: Any telemetry value is greater than 0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "39584410-cbf9-499e-96dc-76f27e69885e",
|
||||||
|
"configuration": {
|
||||||
|
"name": "Unnamed Condition1",
|
||||||
|
"output": "Any telemetry greater than 1",
|
||||||
|
"trigger": "all",
|
||||||
|
"criteria": [
|
||||||
|
{
|
||||||
|
"id": "35400132-63b0-425c-ac30-8197df7d5863",
|
||||||
|
"telemetry": "any",
|
||||||
|
"operation": "greaterThan",
|
||||||
|
"input": [
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"metadata": "some-key"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"summary": "Match if all criteria are met: Any telemetry value is greater than 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"isDefault": true,
|
||||||
|
"id": "2532d90a-e0d6-4935-b546-3123522da2de",
|
||||||
|
"configuration": {
|
||||||
|
"name": "Default",
|
||||||
|
"output": "Default",
|
||||||
|
"trigger": "all",
|
||||||
|
"criteria": [
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"summary": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"composition": [
|
||||||
|
{
|
||||||
|
"namespace": "",
|
||||||
|
"key": "test-object"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"telemetry": {
|
||||||
|
},
|
||||||
|
"name": "Condition Set",
|
||||||
|
"type": "conditionSet",
|
||||||
|
"identifier": {
|
||||||
|
"namespace": "",
|
||||||
|
"key": "cf4456a9-296a-4e6b-b182-62ed29cd15b9"
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should stop evaluating conditions when a condition evaluates to true', () => {
|
||||||
|
const date = Date.now();
|
||||||
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||||
|
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||||
|
conditionMgr.telemetryObjects = {
|
||||||
|
"test-object": testTelemetryObject
|
||||||
|
};
|
||||||
|
conditionMgr.updateConditionTelemetryObjects();
|
||||||
|
conditionMgr.telemetryReceived(testTelemetryObject, {
|
||||||
|
"some-key": 2,
|
||||||
|
utc: date
|
||||||
|
});
|
||||||
|
let result = conditionMgr.conditions.map(condition => condition.result);
|
||||||
|
expect(result[2]).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
29
src/plugins/defaultRootName/plugin.js
Normal file
29
src/plugins/defaultRootName/plugin.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 RootObjectProvider from '../../api/objects/RootObjectProvider.js';
|
||||||
|
|
||||||
|
export default function (name) {
|
||||||
|
return function (openmct) {
|
||||||
|
let rootObjectProvider = new RootObjectProvider();
|
||||||
|
rootObjectProvider.updateName(name);
|
||||||
|
};
|
||||||
|
}
|
99
src/plugins/defaultRootName/pluginSpec.js
Normal file
99
src/plugins/defaultRootName/pluginSpec.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import {
|
||||||
|
createOpenMct,
|
||||||
|
resetApplicationState
|
||||||
|
} from 'utils/testing';
|
||||||
|
|
||||||
|
xdescribe("the plugin", () => {
|
||||||
|
let openmct;
|
||||||
|
let compositionAPI;
|
||||||
|
let newFolderAction;
|
||||||
|
let mockObjectPath;
|
||||||
|
let mockDialogService;
|
||||||
|
let mockComposition;
|
||||||
|
let mockPromise;
|
||||||
|
let newFolderName = 'New Folder';
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
openmct = createOpenMct();
|
||||||
|
|
||||||
|
openmct.on('start', done);
|
||||||
|
openmct.startHeadless();
|
||||||
|
|
||||||
|
newFolderAction = openmct.contextMenu._allActions.filter(action => {
|
||||||
|
return action.key === 'newFolder';
|
||||||
|
})[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('installs the new folder action', () => {
|
||||||
|
expect(newFolderAction).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when invoked', () => {
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
compositionAPI = openmct.composition;
|
||||||
|
mockObjectPath = [{
|
||||||
|
name: 'mock folder',
|
||||||
|
type: 'folder',
|
||||||
|
identifier: {
|
||||||
|
key: 'mock-folder',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
mockPromise = {
|
||||||
|
then: (callback) => {
|
||||||
|
callback({name: newFolderName});
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mockDialogService = jasmine.createSpyObj('dialogService', ['getUserInput']);
|
||||||
|
mockComposition = jasmine.createSpyObj('composition', ['add']);
|
||||||
|
|
||||||
|
mockDialogService.getUserInput.and.returnValue(mockPromise);
|
||||||
|
|
||||||
|
spyOn(openmct.$injector, 'get').and.returnValue(mockDialogService);
|
||||||
|
spyOn(compositionAPI, 'get').and.returnValue(mockComposition);
|
||||||
|
spyOn(openmct.objects, 'mutate');
|
||||||
|
|
||||||
|
newFolderAction.invoke(mockObjectPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets user input for folder name', () => {
|
||||||
|
expect(mockDialogService.getUserInput).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new folder object', () => {
|
||||||
|
expect(openmct.objects.mutate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds new folder object to parent composition', () => {
|
||||||
|
expect(mockComposition.add).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -13,7 +13,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $colorItemTreeHoverBg;
|
background: $colorListItemBgHov;
|
||||||
filter: $filterHov;
|
filter: $filterHov;
|
||||||
transition: $transIn;
|
transition: $transIn;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,8 @@ define([
|
|||||||
'./themes/snow',
|
'./themes/snow',
|
||||||
'./URLTimeSettingsSynchronizer/plugin',
|
'./URLTimeSettingsSynchronizer/plugin',
|
||||||
'./notificationIndicator/plugin',
|
'./notificationIndicator/plugin',
|
||||||
'./newFolderAction/plugin'
|
'./newFolderAction/plugin',
|
||||||
|
'./defaultRootName/plugin'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
@ -91,7 +92,8 @@ define([
|
|||||||
Snow,
|
Snow,
|
||||||
URLTimeSettingsSynchronizer,
|
URLTimeSettingsSynchronizer,
|
||||||
NotificationIndicator,
|
NotificationIndicator,
|
||||||
NewFolderAction
|
NewFolderAction,
|
||||||
|
DefaultRootName
|
||||||
) {
|
) {
|
||||||
const bundleMap = {
|
const bundleMap = {
|
||||||
LocalStorage: 'platform/persistence/local',
|
LocalStorage: 'platform/persistence/local',
|
||||||
@ -204,6 +206,7 @@ define([
|
|||||||
plugins.NotificationIndicator = NotificationIndicator.default;
|
plugins.NotificationIndicator = NotificationIndicator.default;
|
||||||
plugins.NewFolderAction = NewFolderAction.default;
|
plugins.NewFolderAction = NewFolderAction.default;
|
||||||
plugins.ISOTimeFormat = ISOTimeFormat.default;
|
plugins.ISOTimeFormat = ISOTimeFormat.default;
|
||||||
|
plugins.DefaultRootName = DefaultRootName.default;
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
});
|
});
|
||||||
|
121
src/plugins/remove/pluginSpec.js
Normal file
121
src/plugins/remove/pluginSpec.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 RemoveActionPlugin from './plugin.js';
|
||||||
|
import RemoveAction from './RemoveAction.js';
|
||||||
|
import {
|
||||||
|
createOpenMct,
|
||||||
|
resetApplicationState,
|
||||||
|
getMockObjects
|
||||||
|
} from 'utils/testing';
|
||||||
|
|
||||||
|
describe("The Remove Action plugin", () => {
|
||||||
|
|
||||||
|
let openmct;
|
||||||
|
let removeAction;
|
||||||
|
let childObject;
|
||||||
|
let parentObject;
|
||||||
|
|
||||||
|
// this setups up the app
|
||||||
|
beforeEach((done) => {
|
||||||
|
const appHolder = document.createElement('div');
|
||||||
|
appHolder.style.width = '640px';
|
||||||
|
appHolder.style.height = '480px';
|
||||||
|
|
||||||
|
openmct = createOpenMct();
|
||||||
|
|
||||||
|
childObject = getMockObjects({
|
||||||
|
objectKeyStrings: ['folder'],
|
||||||
|
overwrite: {
|
||||||
|
folder: {
|
||||||
|
name: "Child Folder",
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "child-folder-object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).folder;
|
||||||
|
parentObject = getMockObjects({
|
||||||
|
objectKeyStrings: ['folder'],
|
||||||
|
overwrite: {
|
||||||
|
folder: {
|
||||||
|
name: "Parent Folder",
|
||||||
|
composition: [childObject.identifier]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).folder;
|
||||||
|
|
||||||
|
// already installed by default, but never hurts, just adds to context menu
|
||||||
|
openmct.install(RemoveActionPlugin());
|
||||||
|
|
||||||
|
openmct.on('start', done);
|
||||||
|
openmct.startHeadless(appHolder);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
resetApplicationState(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be defined", () => {
|
||||||
|
expect(RemoveActionPlugin).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when removing an object from a parent composition", () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
removeAction = new RemoveAction(openmct);
|
||||||
|
spyOn(removeAction, 'removeFromComposition').and.callThrough();
|
||||||
|
spyOn(removeAction, 'inNavigationPath').and.returnValue(false);
|
||||||
|
spyOn(openmct.objects, 'mutate').and.callThrough();
|
||||||
|
removeAction.removeFromComposition(parentObject, childObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removeFromComposition should be called with the parent and child", () => {
|
||||||
|
expect(removeAction.removeFromComposition).toHaveBeenCalled();
|
||||||
|
expect(removeAction.removeFromComposition).toHaveBeenCalledWith(parentObject, childObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("it should mutate the parent object", () => {
|
||||||
|
expect(openmct.objects.mutate).toHaveBeenCalled();
|
||||||
|
expect(openmct.objects.mutate.calls.argsFor(0)[0]).toEqual(parentObject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when determining the object is applicable", () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
removeAction = new RemoveAction(openmct);
|
||||||
|
spyOn(removeAction, 'appliesTo').and.callThrough();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true when the parent is creatable and has composition", () => {
|
||||||
|
let applies = removeAction.appliesTo([childObject, parentObject]);
|
||||||
|
expect(applies).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be false when the child is locked", () => {
|
||||||
|
childObject.locked = true;
|
||||||
|
let applies = removeAction.appliesTo([childObject, parentObject]);
|
||||||
|
expect(applies).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -3,8 +3,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rotation-centered {
|
@keyframes rotation-centered {
|
||||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes clock-hands {
|
@keyframes clock-hands {
|
||||||
|
@ -80,8 +80,8 @@ $uiColor: #0093ff; // Resize bars, splitter bars, etc.
|
|||||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||||
$colorA: #ccc;
|
$colorA: #ccc;
|
||||||
$colorAHov: #fff;
|
$colorAHov: #fff;
|
||||||
$filterHov: brightness(1.3); // Tree, location items
|
$filterHov: brightness(1.3) contrast(1.5); // Tree, location items
|
||||||
$colorSelectedBg: pushBack($colorKey, 10%);
|
$colorSelectedBg: rgba($colorKey, 0.3);
|
||||||
$colorSelectedFg: pullForward($colorBodyFg, 20%);
|
$colorSelectedFg: pullForward($colorBodyFg, 20%);
|
||||||
|
|
||||||
// Object labels
|
// Object labels
|
||||||
@ -351,7 +351,7 @@ $colorSummaryFgEm: $colorBodyFg;
|
|||||||
// Plot
|
// Plot
|
||||||
$colorPlotBg: rgba(black, 0.1);
|
$colorPlotBg: rgba(black, 0.1);
|
||||||
$colorPlotFg: $colorBodyFg;
|
$colorPlotFg: $colorBodyFg;
|
||||||
$colorPlotHash: black;
|
$colorPlotHash: $colorPlotFg;
|
||||||
$opacityPlotHash: 0.2;
|
$opacityPlotHash: 0.2;
|
||||||
$stylePlotHash: dashed;
|
$stylePlotHash: dashed;
|
||||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
@ -361,13 +361,14 @@ $legendTableHeadBg: $colorTabHeaderBg;
|
|||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
$colorTreeBg: transparent;
|
$colorTreeBg: transparent;
|
||||||
$colorItemTreeHoverBg: rgba(white, 0.07);
|
$colorItemTreeHoverBg: rgba(#fff, 0.03);
|
||||||
$colorItemTreeHoverFg: pullForward($colorBodyFg, 20%);
|
$colorItemTreeHoverFg: #fff;
|
||||||
$colorItemTreeIcon: $colorKey; // Used
|
$colorItemTreeIcon: $colorKey; // Used
|
||||||
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
||||||
$colorItemTreeFg: $colorBodyFg;
|
$colorItemTreeFg: $colorBodyFg;
|
||||||
$colorItemTreeSelectedBg: $colorSelectedBg;
|
$colorItemTreeSelectedBg: $colorSelectedBg;
|
||||||
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
|
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
|
||||||
|
$filterItemTreeSelected: $filterHov;
|
||||||
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
|
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
|
||||||
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
|
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
|
||||||
$colorItemTreeEditingFg: $editUIColor;
|
$colorItemTreeEditingFg: $editUIColor;
|
||||||
@ -402,7 +403,7 @@ $splitterBtnColorBg: $colorBtnBg;
|
|||||||
$splitterBtnColorFg: #999;
|
$splitterBtnColorFg: #999;
|
||||||
$splitterBtnLabelColorFg: #666;
|
$splitterBtnLabelColorFg: #666;
|
||||||
$splitterCollapsedBtnColorBg: #222;
|
$splitterCollapsedBtnColorBg: #222;
|
||||||
$splitterCollapsedBtnColorFg: #666;
|
$splitterCollapsedBtnColorFg: #555;
|
||||||
$splitterCollapsedBtnColorBgHov: $colorKey;
|
$splitterCollapsedBtnColorBgHov: $colorKey;
|
||||||
$splitterCollapsedBtnColorFgHov: $colorKeyFg;
|
$splitterCollapsedBtnColorFgHov: $colorKeyFg;
|
||||||
|
|
||||||
|
@ -80,12 +80,12 @@ $colorKeyHov: #26d8ff;
|
|||||||
$colorKeyFilter: invert(36%) sepia(76%) saturate(2514%) hue-rotate(170deg) brightness(99%) contrast(101%);
|
$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%);
|
$colorKeyFilterHov: invert(63%) sepia(88%) saturate(3029%) hue-rotate(154deg) brightness(101%) contrast(100%);
|
||||||
$colorKeySelectedBg: $colorKey;
|
$colorKeySelectedBg: $colorKey;
|
||||||
$uiColor: #00b2ff; // Resize bars, splitter bars, etc.
|
$uiColor: #0093ff; // Resize bars, splitter bars, etc.
|
||||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||||
$colorA: #ccc;
|
$colorA: #ccc;
|
||||||
$colorAHov: #fff;
|
$colorAHov: #fff;
|
||||||
$filterHov: brightness(1.3); // Tree, location items
|
$filterHov: brightness(1.3) contrast(1.5); // Tree, location items
|
||||||
$colorSelectedBg: pushBack($colorKey, 10%);
|
$colorSelectedBg: rgba($colorKey, 0.3);
|
||||||
$colorSelectedFg: pullForward($colorBodyFg, 20%);
|
$colorSelectedFg: pullForward($colorBodyFg, 20%);
|
||||||
|
|
||||||
// Object labels
|
// Object labels
|
||||||
@ -365,13 +365,14 @@ $legendTableHeadBg: rgba($colorBodyFg, 0.15);
|
|||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
$colorTreeBg: transparent;
|
$colorTreeBg: transparent;
|
||||||
$colorItemTreeHoverBg: rgba(white, 0.07);
|
$colorItemTreeHoverBg: rgba(#fff, 0.03);
|
||||||
$colorItemTreeHoverFg: pullForward($colorBodyFg, 20%);
|
$colorItemTreeHoverFg: #fff;
|
||||||
$colorItemTreeIcon: $colorKey; // Used
|
$colorItemTreeIcon: $colorKey; // Used
|
||||||
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
||||||
$colorItemTreeFg: $colorBodyFg;
|
$colorItemTreeFg: $colorBodyFg;
|
||||||
$colorItemTreeSelectedBg: $colorSelectedBg;
|
$colorItemTreeSelectedBg: $colorSelectedBg;
|
||||||
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
|
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
|
||||||
|
$filterItemTreeSelected: $filterHov;
|
||||||
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
|
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
|
||||||
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
|
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
|
||||||
$colorItemTreeEditingFg: $editUIColor;
|
$colorItemTreeEditingFg: $editUIColor;
|
||||||
@ -406,7 +407,7 @@ $splitterBtnColorBg: $colorBtnBg;
|
|||||||
$splitterBtnColorFg: #999;
|
$splitterBtnColorFg: #999;
|
||||||
$splitterBtnLabelColorFg: #666;
|
$splitterBtnLabelColorFg: #666;
|
||||||
$splitterCollapsedBtnColorBg: #222;
|
$splitterCollapsedBtnColorBg: #222;
|
||||||
$splitterCollapsedBtnColorFg: #666;
|
$splitterCollapsedBtnColorFg: #555;
|
||||||
$splitterCollapsedBtnColorBgHov: $colorKey;
|
$splitterCollapsedBtnColorBgHov: $colorKey;
|
||||||
$splitterCollapsedBtnColorFgHov: $colorKeyFg;
|
$splitterCollapsedBtnColorFgHov: $colorKeyFg;
|
||||||
|
|
||||||
|
@ -78,10 +78,10 @@ $colorKeyFilterHov: invert(69%) sepia(87%) saturate(3243%) hue-rotate(151deg) br
|
|||||||
$colorKeySelectedBg: $colorKey;
|
$colorKeySelectedBg: $colorKey;
|
||||||
$uiColor: #289fec; // Resize bars, splitter bars, etc.
|
$uiColor: #289fec; // Resize bars, splitter bars, etc.
|
||||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||||
$colorA: #999;
|
$colorA: $colorBodyFg;
|
||||||
$colorAHov: $colorKey;
|
$colorAHov: $colorKey;
|
||||||
$filterHov: brightness(0.8) contrast(2); // Tree, location items
|
$filterHov: brightness(0.8) contrast(2); // Tree, location items
|
||||||
$colorSelectedBg: pushBack($colorKey, 40%);
|
$colorSelectedBg: rgba($colorKey, 0.2);
|
||||||
$colorSelectedFg: pullForward($colorBodyFg, 10%);
|
$colorSelectedFg: pullForward($colorBodyFg, 10%);
|
||||||
|
|
||||||
// Object labels
|
// Object labels
|
||||||
@ -94,7 +94,7 @@ $shellPanePad: $interiorMargin, 7px;
|
|||||||
$drawerBg: darken($colorBodyBg, 5%);
|
$drawerBg: darken($colorBodyBg, 5%);
|
||||||
$drawerFg: darken($colorBodyFg, 5%);
|
$drawerFg: darken($colorBodyFg, 5%);
|
||||||
$sideBarBg: $drawerBg;
|
$sideBarBg: $drawerBg;
|
||||||
$sideBarHeaderBg: rgba(black, 0.25);
|
$sideBarHeaderBg: rgba(black, 0.1);
|
||||||
$sideBarHeaderFg: rgba($colorBodyFg, 0.7);
|
$sideBarHeaderFg: rgba($colorBodyFg, 0.7);
|
||||||
|
|
||||||
// Status colors, mainly used for messaging and item ancillary symbols
|
// Status colors, mainly used for messaging and item ancillary symbols
|
||||||
@ -351,8 +351,8 @@ $colorSummaryFgEm: white;
|
|||||||
// Plot
|
// Plot
|
||||||
$colorPlotBg: rgba(black, 0.05);
|
$colorPlotBg: rgba(black, 0.05);
|
||||||
$colorPlotFg: $colorBodyFg;
|
$colorPlotFg: $colorBodyFg;
|
||||||
$colorPlotHash: black;
|
$colorPlotHash: $colorPlotFg;
|
||||||
$opacityPlotHash: 0.2;
|
$opacityPlotHash: 0.3;
|
||||||
$stylePlotHash: dashed;
|
$stylePlotHash: dashed;
|
||||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||||
@ -368,7 +368,8 @@ $colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
|||||||
$colorItemTreeFg: $colorBodyFg;
|
$colorItemTreeFg: $colorBodyFg;
|
||||||
$colorItemTreeSelectedBg: $colorSelectedBg;
|
$colorItemTreeSelectedBg: $colorSelectedBg;
|
||||||
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
|
$colorItemTreeSelectedFg: $colorItemTreeHoverFg;
|
||||||
$colorItemTreeSelectedIcon: $colorItemTreeSelectedFg;
|
$filterItemTreeSelected: contrast(1.4);
|
||||||
|
$colorItemTreeSelectedIcon: $colorItemTreeIcon;
|
||||||
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
|
$colorItemTreeEditingBg: pushBack($editUIColor, 20%);
|
||||||
$colorItemTreeEditingFg: $editUIColor;
|
$colorItemTreeEditingFg: $editUIColor;
|
||||||
$colorItemTreeEditingIcon: $editUIColor;
|
$colorItemTreeEditingIcon: $editUIColor;
|
||||||
|
@ -44,6 +44,7 @@ $overlayOuterMarginFullscreen: 0%;
|
|||||||
$overlayOuterMarginDialog: 20%;
|
$overlayOuterMarginDialog: 20%;
|
||||||
$overlayInnerMargin: 25px;
|
$overlayInnerMargin: 25px;
|
||||||
$mainViewPad: 0px;
|
$mainViewPad: 0px;
|
||||||
|
$treeNavArrowD: 20px;
|
||||||
/*************** Items */
|
/*************** Items */
|
||||||
$itemPadLR: 5px;
|
$itemPadLR: 5px;
|
||||||
$gridItemDesk: 175px;
|
$gridItemDesk: 175px;
|
||||||
@ -81,8 +82,8 @@ $formLabelMinW: 120px;
|
|||||||
$formLabelW: 30%;
|
$formLabelW: 30%;
|
||||||
/*************** Wait Spinner */
|
/*************** Wait Spinner */
|
||||||
$waitSpinnerD: 32px;
|
$waitSpinnerD: 32px;
|
||||||
$waitSpinnerTreeD: 20px;
|
|
||||||
$waitSpinnerBorderW: 5px;
|
$waitSpinnerBorderW: 5px;
|
||||||
|
$waitSpinnerTreeD: 20px;
|
||||||
$waitSpinnerTreeBorderW: 3px;
|
$waitSpinnerTreeBorderW: 3px;
|
||||||
/*************** Messages */
|
/*************** Messages */
|
||||||
$messageIconD: 80px;
|
$messageIconD: 80px;
|
||||||
|
@ -97,7 +97,8 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-click-link {
|
.c-click-link,
|
||||||
|
.c-icon-link {
|
||||||
// A clickable element, typically inline, with an icon and label
|
// A clickable element, typically inline, with an icon and label
|
||||||
@include cControl();
|
@include cControl();
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -112,8 +113,15 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-icon-link {
|
||||||
|
&:before {
|
||||||
|
// Icon
|
||||||
|
//color: $colorBtnMajorBg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.c-icon-button {
|
.c-icon-button {
|
||||||
.c-icon-button__label {
|
&__label {
|
||||||
margin-left: $interiorMargin;
|
margin-left: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -915,14 +923,6 @@ input[type="range"] {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--overlay-content {
|
|
||||||
> .c-button {
|
|
||||||
background: $colorLocalControlOvrBg;
|
|
||||||
border-radius: $controlCr;
|
|
||||||
box-shadow: $colorLocalControlOvrBg 0 0 0 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-local-controls {
|
.c-local-controls {
|
||||||
|
@ -101,8 +101,9 @@ a {
|
|||||||
color: $colorA;
|
color: $colorA;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
&:hover {
|
|
||||||
color: $colorAHov;
|
&:focus {
|
||||||
|
outline: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,19 +281,23 @@ body.desktop .has-local-controls {
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: $spinnerL + $d/2 + $interiorMargin;
|
margin-left: $treeNavArrowD + $interiorMargin;
|
||||||
background: $colorLoadingBg;
|
|
||||||
min-height: 5px + $d;
|
min-height: 5px + $d;
|
||||||
|
|
||||||
.c-tree__item__label {
|
.c-tree__item__label {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
margin-left: $interiorMargin;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
&:before {
|
&:before {
|
||||||
|
left: auto;
|
||||||
|
top: auto;
|
||||||
|
transform: translate(0);
|
||||||
height: $d;
|
height: $d;
|
||||||
width: $d;
|
width: $d;
|
||||||
border-width: 4px;
|
border-width: 3px;
|
||||||
left: $spinnerL;
|
//left: $spinnerL;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
&:after {
|
&:after {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -29,7 +29,8 @@
|
|||||||
stroke-width: 1 !important;
|
stroke-width: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cartesianlayer .gridlayer {
|
.cartesianlayer {
|
||||||
|
.gridlayer {
|
||||||
.x,
|
.x,
|
||||||
.y {
|
.y {
|
||||||
path {
|
path {
|
||||||
@ -39,10 +40,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path.xy2-y {
|
||||||
|
stroke: $colorPlotHash !important; // Using this instead of $colorPlotAreaBorder because that is an rgba
|
||||||
|
opacity: $opacityPlotHash !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.xtick,
|
.xtick,
|
||||||
.ytick,
|
.ytick,
|
||||||
.g-xtitle,
|
[class^="g-"] text[class*="title"] {
|
||||||
.g-ytitle {
|
// Matches <g class="g-*"> <text class="*title">
|
||||||
text {
|
text {
|
||||||
fill: $colorPlotFg !important;
|
fill: $colorPlotFg !important;
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
class="c-disclosure-triangle"
|
:class="[
|
||||||
:class="{
|
controlClass,
|
||||||
'c-disclosure-triangle--expanded' : value,
|
{ 'c-disclosure-triangle--expanded' : value },
|
||||||
'is-enabled' : enabled
|
{'is-enabled' : enabled }
|
||||||
}"
|
]"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
></span>
|
></span>
|
||||||
</template>
|
</template>
|
||||||
@ -25,6 +25,10 @@ export default {
|
|||||||
propagate: {
|
propagate: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
controlClass: {
|
||||||
|
type: String,
|
||||||
|
default: 'c-disclosure-triangle'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
<div class="l-browse-bar__start">
|
<div class="l-browse-bar__start">
|
||||||
<button
|
<button
|
||||||
v-if="hasParent"
|
v-if="hasParent"
|
||||||
class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-pointer-left"
|
class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-arrow-nav-to-parent"
|
||||||
|
title="Navigate up to parent"
|
||||||
@click="goToParent"
|
@click="goToParent"
|
||||||
></button>
|
></button>
|
||||||
<div
|
<div
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
<CreateButton class="l-shell__create-button" />
|
<CreateButton class="l-shell__create-button" />
|
||||||
<indicators class="l-shell__head-section l-shell__indicators" />
|
<indicators class="l-shell__head-section l-shell__indicators" />
|
||||||
<button
|
<button
|
||||||
class="l-shell__head__collapse-button c-button"
|
class="l-shell__head__collapse-button c-icon-button"
|
||||||
|
:class="headExpanded ? 'l-shell__head__collapse-button--collapse' : 'l-shell__head__collapse-button--expand'"
|
||||||
|
:title="`Click to ${headExpanded ? 'collapse' : 'expand'} items`"
|
||||||
@click="toggleShellHead"
|
@click="toggleShellHead"
|
||||||
></button>
|
></button>
|
||||||
<notification-banner />
|
<notification-banner />
|
||||||
@ -47,12 +49,23 @@
|
|||||||
label="Browse"
|
label="Browse"
|
||||||
collapsable
|
collapsable
|
||||||
>
|
>
|
||||||
<mct-tree class="l-shell__tree" />
|
<button
|
||||||
|
slot="controls"
|
||||||
|
class="c-icon-button l-shell__sync-tree-button icon-target"
|
||||||
|
title="Show selected item in tree"
|
||||||
|
@click="handleSyncTreeNavigation"
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<mct-tree
|
||||||
|
:sync-tree-navigation="triggerSync"
|
||||||
|
class="l-shell__tree"
|
||||||
|
/>
|
||||||
</pane>
|
</pane>
|
||||||
<pane class="l-shell__pane-main">
|
<pane class="l-shell__pane-main">
|
||||||
<browse-bar
|
<browse-bar
|
||||||
ref="browseBar"
|
ref="browseBar"
|
||||||
class="l-shell__main-view-browse-bar"
|
class="l-shell__main-view-browse-bar"
|
||||||
|
@sync-tree-navigation="handleSyncTreeNavigation"
|
||||||
/>
|
/>
|
||||||
<toolbar
|
<toolbar
|
||||||
v-if="toolbar"
|
v-if="toolbar"
|
||||||
@ -126,7 +139,8 @@ export default {
|
|||||||
conductorComponent: undefined,
|
conductorComponent: undefined,
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
hasToolbar: false,
|
hasToolbar: false,
|
||||||
headExpanded
|
headExpanded,
|
||||||
|
triggerSync: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -200,6 +214,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.hasToolbar = structure.length > 0;
|
this.hasToolbar = structure.length > 0;
|
||||||
|
},
|
||||||
|
handleSyncTreeNavigation() {
|
||||||
|
this.triggerSync = !this.triggerSync;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
color: $colorKey !important;
|
color: $colorKey !important;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -18px;
|
right: -18px;
|
||||||
top: 0;
|
top: $interiorMarginSm;
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
width: $mobileMenuIconD;
|
width: $mobileMenuIconD;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@ -100,6 +100,11 @@
|
|||||||
&__pane-tree {
|
&__pane-tree {
|
||||||
background: linear-gradient(90deg, transparent 70%, rgba(black, 0.2) 99%, rgba(black, 0.3));
|
background: linear-gradient(90deg, transparent 70%, rgba(black, 0.2) 99%, rgba(black, 0.3));
|
||||||
|
|
||||||
|
[class*="expand-button"],
|
||||||
|
[class*="sync-tree-button"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
&[class*="--collapsed"] {
|
&[class*="--collapsed"] {
|
||||||
[class*="collapse-button"] {
|
[class*="collapse-button"] {
|
||||||
right: -8px;
|
right: -8px;
|
||||||
@ -153,7 +158,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__head {
|
&__head {
|
||||||
align-items: stretch;
|
align-items: center;
|
||||||
background: $colorHeadBg;
|
background: $colorHeadBg;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: $interiorMargin $interiorMargin + 2;
|
padding: $interiorMargin $interiorMargin + 2;
|
||||||
@ -162,14 +167,21 @@
|
|||||||
margin-left: $interiorMargin;
|
margin-left: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*='__head__collapse-button'] {
|
.l-shell__head__collapse-button {
|
||||||
align-self: start;
|
color: $colorBtnMajorBg;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin-top: 6px;
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
&--collapse {
|
||||||
&:before {
|
&:before {
|
||||||
content: $glyph-icon-arrow-down;
|
content: $glyph-icon-items-collapse;
|
||||||
font-size: 1.1em;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--expand {
|
||||||
|
&:before {
|
||||||
|
content: $glyph-icon-items-expand;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,12 +196,6 @@
|
|||||||
.c-indicator__label {
|
.c-indicator__label {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*='__head__collapse-button'] {
|
|
||||||
&:before {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,6 +310,10 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
&__start,
|
&__start,
|
||||||
&__end,
|
&__end,
|
||||||
&__actions {
|
&__actions {
|
||||||
@ -327,8 +337,12 @@
|
|||||||
|
|
||||||
&__start {
|
&__start {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin-right: $interiorMargin;
|
//margin-right: $interiorMargin;
|
||||||
min-width: 0; // Forces interior to compress when pushed on
|
min-width: 0; // Forces interior to compress when pushed on
|
||||||
|
|
||||||
|
[class*='button'] {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__end {
|
&__end {
|
||||||
@ -337,15 +351,15 @@
|
|||||||
|
|
||||||
&__nav-to-parent-button,
|
&__nav-to-parent-button,
|
||||||
&__disclosure-button {
|
&__disclosure-button {
|
||||||
flex: 0 0 auto;
|
//flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__nav-to-parent-button {
|
&__nav-to-parent-button {
|
||||||
// This is an icon-button
|
// This is an icon-button
|
||||||
$p: $interiorMargin;
|
//$p: $interiorMargin;
|
||||||
margin-right: $interiorMargin;
|
//margin-right: $interiorMargin;
|
||||||
padding-left: $p;
|
//padding-left: $p;
|
||||||
padding-right: $p;
|
//padding-right: $p;
|
||||||
|
|
||||||
.is-editing & {
|
.is-editing & {
|
||||||
display: none;
|
display: none;
|
||||||
@ -362,7 +376,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__object-name--w {
|
&__object-name--w {
|
||||||
@include headerFont(1.4em);
|
@include headerFont(1.5em);
|
||||||
|
margin-left: $interiorMarginLg;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
.is-missing__indicator {
|
.is-missing__indicator {
|
||||||
|
@ -12,10 +12,6 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__loading {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__no-results {
|
&__no-results {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
@ -26,6 +22,33 @@
|
|||||||
height: 0; // Chrome 73 overflow bug fix
|
height: 0; // Chrome 73 overflow bug fix
|
||||||
padding-right: $interiorMarginSm;
|
padding-right: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-tree {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all;
|
||||||
|
|
||||||
|
.scrollable-children {
|
||||||
|
.c-tree__item-h {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item--empty {
|
||||||
|
// Styling for empty tree items
|
||||||
|
// Indent should allow for c-nav view-control width and icon spacing
|
||||||
|
font-style: italic;
|
||||||
|
padding: $interiorMarginSm * 2 1px;
|
||||||
|
opacity: 0.7;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: $treeNavArrowD + $interiorMarginLg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-tree,
|
.c-tree,
|
||||||
@ -43,7 +66,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
$aPad: $interiorMarginSm;
|
|
||||||
border-radius: $controlCr;
|
border-radius: $controlCr;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -82,22 +104,9 @@
|
|||||||
margin-left: $interiorMarginSm;
|
margin-left: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-navigated-object,
|
@include desktop {
|
||||||
&.is-selected {
|
&:hover {
|
||||||
.c-tree__item__type-icon:before {
|
background: $colorItemTreeHoverBg;
|
||||||
color: $colorItemTreeIconHover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-being-edited {
|
|
||||||
background: $colorItemTreeEditingBg;
|
|
||||||
.c-tree__item__type-icon:before {
|
|
||||||
color: $colorItemTreeEditingIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-tree__item__name {
|
|
||||||
color: $colorItemTreeEditingFg;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,10 +115,6 @@
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
|
||||||
color: $colorItemTreeFg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-alias {
|
&.is-alias {
|
||||||
// Object is an alias to an original.
|
// Object is an alias to an original.
|
||||||
[class*='__type-icon'] {
|
[class*='__type-icon'] {
|
||||||
@ -125,6 +130,55 @@
|
|||||||
width: ceil($mobileTreeItemH * 0.5);
|
width: ceil($mobileTreeItemH * 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-navigated-object,
|
||||||
|
&.is-selected {
|
||||||
|
background: $colorItemTreeSelectedBg;
|
||||||
|
|
||||||
|
[class*="__label"],
|
||||||
|
[class*="__name"] {
|
||||||
|
color: $colorItemTreeSelectedFg;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="__type-icon"]:before {
|
||||||
|
color: $colorItemTreeSelectedIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item__label {
|
||||||
|
@include desktop {
|
||||||
|
&:hover {
|
||||||
|
filter: $filterHov;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-editing .is-navigated-object {
|
||||||
|
a[class*="__item__label"] {
|
||||||
|
opacity: 0.4;
|
||||||
|
|
||||||
|
[class*="__name"] {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-tree {
|
||||||
|
&__item {
|
||||||
|
body.mobile & {
|
||||||
|
@include button($bg: $colorMobilePaneLeftTreeItemBg, $fg: $colorMobilePaneLeftTreeItemFg);
|
||||||
|
height: $mobileTreeItemH;
|
||||||
|
margin-bottom: $interiorMarginSm;
|
||||||
|
[class*="view-control"] {
|
||||||
|
width: ceil($mobileTreeItemH * 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-tree {
|
||||||
|
margin-left: $treeItemIndent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +195,51 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-nav {
|
||||||
|
$dimension: $treeNavArrowD;
|
||||||
|
|
||||||
|
&__up, &__down {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
height: $dimension;
|
||||||
|
width: $dimension;
|
||||||
|
visibility: hidden;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&.is-enabled {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
// Nav arrow
|
||||||
|
$dimension: 9px;
|
||||||
|
$width: 3px;
|
||||||
|
border: solid $colorItemTreeVC;
|
||||||
|
border-width: 0 $width $width 0;
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%; top: 50%;
|
||||||
|
height: $dimension;
|
||||||
|
width: $dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
&:hover:before {
|
||||||
|
border-color: $colorItemTreeHoverFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__up:before {
|
||||||
|
transform: translate(-30%, -50%) rotate(135deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__down:before {
|
||||||
|
transform: translate(-70%, -50%) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.c-selector {
|
.c-selector {
|
||||||
.c-tree-and-search__tree.c-tree {
|
.c-tree-and-search__tree.c-tree {
|
||||||
border: 1px solid $colorInteriorBorder;
|
border: 1px solid $colorInteriorBorder;
|
||||||
@ -148,3 +247,32 @@
|
|||||||
padding: $interiorMargin;
|
padding: $interiorMargin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TRANSITIONS
|
||||||
|
.slide-left,
|
||||||
|
.slide-right {
|
||||||
|
animation-duration: 500ms;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
transition: all;
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left {
|
||||||
|
animation-name: animSlideLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-right {
|
||||||
|
animation-name: animSlideRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animSlideLeft {
|
||||||
|
0% {opactiy: 0; transform: translateX(100%);}
|
||||||
|
10% {opacity: 1;}
|
||||||
|
100% {transform: translateX(0);}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes animSlideRight {
|
||||||
|
0% {opactiy: 0; transform: translateX(-100%);}
|
||||||
|
10% {opacity: 1;}
|
||||||
|
100% {transform: translateX(0);}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-tree-and-search">
|
<div
|
||||||
|
class="c-tree-and-search"
|
||||||
|
>
|
||||||
|
|
||||||
<div class="c-tree-and-search__search">
|
<div class="c-tree-and-search__search">
|
||||||
<search
|
<search
|
||||||
ref="shell-search"
|
ref="shell-search"
|
||||||
@ -10,15 +13,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- loading -->
|
|
||||||
<div
|
<div
|
||||||
v-if="isLoading"
|
v-if="(searchValue && allTreeItems.length === 0 && !isLoading) || (searchValue && searchResultItems.length === 0)"
|
||||||
class="c-tree-and-search__loading loading"
|
|
||||||
></div>
|
|
||||||
<!-- end loading -->
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="(allTreeItems.length === 0) || (searchValue && filteredTreeItems.length === 0)"
|
|
||||||
class="c-tree-and-search__no-results"
|
class="c-tree-and-search__no-results"
|
||||||
>
|
>
|
||||||
No results found
|
No results found
|
||||||
@ -26,30 +22,72 @@
|
|||||||
|
|
||||||
<!-- main tree -->
|
<!-- main tree -->
|
||||||
<ul
|
<ul
|
||||||
v-if="!isLoading"
|
ref="mainTree"
|
||||||
v-show="!searchValue"
|
|
||||||
class="c-tree-and-search__tree c-tree"
|
class="c-tree-and-search__tree c-tree"
|
||||||
>
|
>
|
||||||
|
<!-- ancestors -->
|
||||||
|
<div v-if="!activeSearch">
|
||||||
<tree-item
|
<tree-item
|
||||||
v-for="treeItem in allTreeItems"
|
v-for="(ancestor, index) in ancestors"
|
||||||
|
:key="ancestor.id"
|
||||||
|
:node="ancestor"
|
||||||
|
:show-up="index < ancestors.length - 1"
|
||||||
|
:show-down="false"
|
||||||
|
:left-offset="index * 10 + 'px'"
|
||||||
|
:emit-height="getChildHeight"
|
||||||
|
@emittedHeight="setChildHeight"
|
||||||
|
@resetTree="handleReset"
|
||||||
|
/>
|
||||||
|
<!-- loading -->
|
||||||
|
<li
|
||||||
|
v-if="isLoading"
|
||||||
|
class="c-tree__item c-tree-and-search__loading loading"
|
||||||
|
>
|
||||||
|
<span class="c-tree__item__label">Loading...</span>
|
||||||
|
</li>
|
||||||
|
<!-- end loading -->
|
||||||
|
</div>
|
||||||
|
<!-- currently viewed children -->
|
||||||
|
<transition
|
||||||
|
@enter="childrenIn"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-if="!isLoading"
|
||||||
|
:class="childrenSlideClass"
|
||||||
|
:style="childrenListStyles()"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
ref="scrollable"
|
||||||
|
class="scrollable-children"
|
||||||
|
:style="scrollableStyles()"
|
||||||
|
@scroll="scrollItems"
|
||||||
|
>
|
||||||
|
<div :style="{ height: childrenHeight + 'px'}">
|
||||||
|
<tree-item
|
||||||
|
v-for="(treeItem, index) in visibleItems"
|
||||||
:key="treeItem.id"
|
:key="treeItem.id"
|
||||||
:node="treeItem"
|
:node="treeItem"
|
||||||
|
:left-offset="itemLeftOffset"
|
||||||
|
:item-offset="itemOffset"
|
||||||
|
:item-index="index"
|
||||||
|
:item-height="itemHeight"
|
||||||
|
:virtual-scroll="!noScroll"
|
||||||
|
:show-down="activeSearch ? false : true"
|
||||||
|
@expanded="handleExpanded"
|
||||||
/>
|
/>
|
||||||
|
<li
|
||||||
|
v-if="visibleItems.length === 0"
|
||||||
|
:style="emptyStyles()"
|
||||||
|
class="c-tree__item c-tree__item--empty"
|
||||||
|
>
|
||||||
|
No items
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</transition>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- end main tree -->
|
<!-- end main tree -->
|
||||||
|
|
||||||
<!-- search tree -->
|
|
||||||
<ul
|
|
||||||
v-if="searchValue"
|
|
||||||
class="c-tree-and-search__tree c-tree"
|
|
||||||
>
|
|
||||||
<tree-item
|
|
||||||
v-for="treeItem in filteredTreeItems"
|
|
||||||
:key="treeItem.id"
|
|
||||||
:node="treeItem"
|
|
||||||
/>
|
|
||||||
</ul>
|
|
||||||
<!-- end search tree -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -57,6 +95,15 @@
|
|||||||
import treeItem from './tree-item.vue';
|
import treeItem from './tree-item.vue';
|
||||||
import search from '../components/search.vue';
|
import search from '../components/search.vue';
|
||||||
|
|
||||||
|
const LOCAL_STORAGE_KEY__TREE_EXPANDED__OLD = 'mct-tree-expanded';
|
||||||
|
const LOCAL_STORAGE_KEY__EXPANDED_TREE_NODE = 'mct-expanded-tree-node';
|
||||||
|
const ROOT_PATH = '/browse/';
|
||||||
|
const ITEM_BUFFER = 5;
|
||||||
|
const RECHECK_DELAY = 100;
|
||||||
|
const RESIZE_FIRE_DELAY_MS = 500;
|
||||||
|
let windowResizeId = undefined;
|
||||||
|
let windowResizing = false;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
name: 'MctTree',
|
name: 'MctTree',
|
||||||
@ -64,48 +111,411 @@ export default {
|
|||||||
search,
|
search,
|
||||||
treeItem
|
treeItem
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
syncTreeNavigation: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
|
let isMobile = this.openmct.$injector.get('agentService');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isLoading: false,
|
||||||
searchValue: '',
|
searchValue: '',
|
||||||
allTreeItems: [],
|
allTreeItems: [],
|
||||||
filteredTreeItems: [],
|
searchResultItems: [],
|
||||||
isLoading: false
|
visibleItems: [],
|
||||||
|
ancestors: [],
|
||||||
|
childrenSlideClass: 'slide-left',
|
||||||
|
availableContainerHeight: 0,
|
||||||
|
noScroll: true,
|
||||||
|
updatingView: false,
|
||||||
|
itemHeight: 28,
|
||||||
|
itemOffset: 0,
|
||||||
|
childrenHeight: 0,
|
||||||
|
scrollable: undefined,
|
||||||
|
pageThreshold: 50,
|
||||||
|
activeSearch: false,
|
||||||
|
getChildHeight: false,
|
||||||
|
settingChildrenHeight: false,
|
||||||
|
isMobile: isMobile.mobileName,
|
||||||
|
multipleRootChildren: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
computed: {
|
||||||
|
currentNavigatedPath() {
|
||||||
|
let ancestorsCopy = [...this.ancestors];
|
||||||
|
if (this.multipleRootChildren) {
|
||||||
|
ancestorsCopy.shift(); // remove root
|
||||||
|
}
|
||||||
|
|
||||||
|
return ancestorsCopy
|
||||||
|
.map((ancestor) => ancestor.id)
|
||||||
|
.join('/');
|
||||||
|
},
|
||||||
|
currentObjectPath() {
|
||||||
|
let ancestorsCopy = [...this.ancestors];
|
||||||
|
|
||||||
|
return ancestorsCopy
|
||||||
|
.reverse()
|
||||||
|
.map((ancestor) => ancestor.object);
|
||||||
|
},
|
||||||
|
focusedItems() {
|
||||||
|
return this.activeSearch ? this.searchResultItems : this.allTreeItems;
|
||||||
|
},
|
||||||
|
itemLeftOffset() {
|
||||||
|
return this.activeSearch ? '0px' : this.ancestors.length * 10 + 'px';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
syncTreeNavigation() {
|
||||||
|
const AND_SAVE_PATH = true;
|
||||||
|
let currentLocationPath = this.openmct.router.currentLocation.path;
|
||||||
|
let hasParent = this.currentlyViewedObjectParentPath() || (this.multipleRootChildren && !this.currentlyViewedObjectParentPath());
|
||||||
|
let jumpAndScroll = currentLocationPath
|
||||||
|
&& hasParent
|
||||||
|
&& !this.currentPathIsActivePath();
|
||||||
|
let justScroll = this.currentPathIsActivePath() && !this.noScroll;
|
||||||
|
|
||||||
|
if (this.searchValue) {
|
||||||
|
this.searchValue = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jumpAndScroll) {
|
||||||
|
this.scrollTo = this.currentlyViewedObjectId();
|
||||||
|
this.allTreeItems = [];
|
||||||
|
this.jumpPath = this.currentlyViewedObjectParentPath();
|
||||||
|
if (this.multipleRootChildren) {
|
||||||
|
if (!this.jumpPath) {
|
||||||
|
this.jumpPath = 'ROOT';
|
||||||
|
this.ancestors = [];
|
||||||
|
} else {
|
||||||
|
this.ancestors = [this.ancestors[0]];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.ancestors = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.jumpToPath(AND_SAVE_PATH);
|
||||||
|
} else if (justScroll) {
|
||||||
|
this.scrollTo = this.currentlyViewedObjectId();
|
||||||
|
this.autoScroll();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchValue() {
|
||||||
|
if (this.searchValue !== '' && !this.activeSearch) {
|
||||||
|
this.searchActivated();
|
||||||
|
} else if (this.searchValue === '') {
|
||||||
|
this.searchDeactivated();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchResultItems() {
|
||||||
|
this.setContainerHeight();
|
||||||
|
},
|
||||||
|
allTreeItems() {
|
||||||
|
// catches an edge case race condition and when new items are added (ex. folder)
|
||||||
|
if (!this.isLoading) {
|
||||||
|
this.setContainerHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.backwardsCompatibilityCheck();
|
||||||
|
|
||||||
|
let savedPath = this.getSavedNavigatedPath();
|
||||||
this.searchService = this.openmct.$injector.get('searchService');
|
this.searchService = this.openmct.$injector.get('searchService');
|
||||||
this.getAllChildren();
|
window.addEventListener('resize', this.handleWindowResize);
|
||||||
|
|
||||||
|
let root = await this.openmct.objects.get('ROOT');
|
||||||
|
|
||||||
|
if (root.identifier !== undefined) {
|
||||||
|
let rootNode = this.buildTreeItem(root);
|
||||||
|
// if more than one root item, set multipleRootChildren to true and add root to ancestors
|
||||||
|
if (root.composition && root.composition.length > 1) {
|
||||||
|
this.ancestors.push(rootNode);
|
||||||
|
this.multipleRootChildren = true;
|
||||||
|
} else if (!savedPath && root.composition[0] !== undefined) {
|
||||||
|
// needed if saved path is not set, need to set it to the only root child
|
||||||
|
savedPath = root.composition[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedPath) {
|
||||||
|
let scrollIfApplicable = () => {
|
||||||
|
if (this.currentPathIsActivePath()) {
|
||||||
|
this.scrollTo = this.currentlyViewedObjectId();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.jumpPath = savedPath;
|
||||||
|
this.afterJump = scrollIfApplicable;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getAllChildren(rootNode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
window.removeEventListener('resize', this.handleWindowResize);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getAllChildren() {
|
updatevisibleItems() {
|
||||||
this.isLoading = true;
|
if (this.updatingView) {
|
||||||
this.openmct.objects.get('ROOT')
|
return;
|
||||||
.then(root => {
|
|
||||||
let composition = this.openmct.composition.get(root);
|
|
||||||
if (composition !== undefined) {
|
|
||||||
return composition.load();
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.then(children => {
|
this.updatingView = true;
|
||||||
this.isLoading = false;
|
requestAnimationFrame(() => {
|
||||||
this.allTreeItems = children.map(c => {
|
let start = 0;
|
||||||
return {
|
let end = this.pageThreshold;
|
||||||
id: this.openmct.objects.makeKeyString(c.identifier),
|
let allItemsCount = this.focusedItems.length;
|
||||||
object: c,
|
|
||||||
objectPath: [c],
|
if (allItemsCount < this.pageThreshold) {
|
||||||
navigateToParent: '/browse'
|
end = allItemsCount;
|
||||||
};
|
} else {
|
||||||
});
|
let firstVisible = this.calculateFirstVisibleItem();
|
||||||
|
let lastVisible = this.calculateLastVisibleItem();
|
||||||
|
let totalVisible = lastVisible - firstVisible;
|
||||||
|
let numberOffscreen = this.pageThreshold - totalVisible;
|
||||||
|
|
||||||
|
start = firstVisible - Math.floor(numberOffscreen / 2);
|
||||||
|
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
||||||
|
|
||||||
|
if (start < 0) {
|
||||||
|
start = 0;
|
||||||
|
end = Math.min(this.pageThreshold, allItemsCount);
|
||||||
|
} else if (end >= allItemsCount) {
|
||||||
|
end = allItemsCount;
|
||||||
|
start = end - this.pageThreshold + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.itemOffset = start;
|
||||||
|
this.visibleItems = this.focusedItems.slice(start, end);
|
||||||
|
|
||||||
|
this.updatingView = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getFilteredChildren() {
|
async setContainerHeight() {
|
||||||
this.searchService.query(this.searchValue).then(children => {
|
await this.$nextTick();
|
||||||
this.filteredTreeItems = children.hits.map(child => {
|
let mainTree = this.$refs.mainTree;
|
||||||
|
let mainTreeHeight = mainTree.clientHeight;
|
||||||
|
|
||||||
let context = child.object.getCapability('context');
|
if (mainTreeHeight !== 0) {
|
||||||
let object = child.object.useCapability('adapter');
|
this.calculateChildHeight(() => {
|
||||||
|
let ancestorsHeight = this.calculateAncestorHeight();
|
||||||
|
let allChildrenHeight = this.calculateChildrenHeight();
|
||||||
|
|
||||||
|
if (this.activeSearch) {
|
||||||
|
ancestorsHeight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.availableContainerHeight = mainTreeHeight - ancestorsHeight;
|
||||||
|
|
||||||
|
if (allChildrenHeight > this.availableContainerHeight) {
|
||||||
|
this.setPageThreshold();
|
||||||
|
this.noScroll = false;
|
||||||
|
} else {
|
||||||
|
this.noScroll = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updatevisibleItems();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.setTimeout(this.setContainerHeight, RECHECK_DELAY);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateFirstVisibleItem() {
|
||||||
|
let scrollTop = this.$refs.scrollable.scrollTop;
|
||||||
|
|
||||||
|
return Math.floor(scrollTop / this.itemHeight);
|
||||||
|
},
|
||||||
|
calculateLastVisibleItem() {
|
||||||
|
let scrollBottom = this.$refs.scrollable.scrollTop + this.$refs.scrollable.offsetHeight;
|
||||||
|
|
||||||
|
return Math.ceil(scrollBottom / this.itemHeight);
|
||||||
|
},
|
||||||
|
calculateChildrenHeight() {
|
||||||
|
let mainTreeTopMargin = this.getElementStyleValue(this.$refs.mainTree, 'marginTop');
|
||||||
|
let childrenCount = this.focusedItems.length;
|
||||||
|
|
||||||
|
return (this.itemHeight * childrenCount) - mainTreeTopMargin; // 5px margin
|
||||||
|
},
|
||||||
|
setChildrenHeight() {
|
||||||
|
this.childrenHeight = this.calculateChildrenHeight();
|
||||||
|
},
|
||||||
|
calculateAncestorHeight() {
|
||||||
|
let ancestorCount = this.ancestors.length;
|
||||||
|
|
||||||
|
return this.itemHeight * ancestorCount;
|
||||||
|
},
|
||||||
|
calculateChildHeight(callback) {
|
||||||
|
if (callback) {
|
||||||
|
this.afterChildHeight = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.activeSearch) {
|
||||||
|
this.getChildHeight = true;
|
||||||
|
} else if (this.afterChildHeight) {
|
||||||
|
// keep the height from before
|
||||||
|
this.afterChildHeight();
|
||||||
|
delete this.afterChildHeight;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setChildHeight(item) {
|
||||||
|
if (!this.getChildHeight || this.settingChildrenHeight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settingChildrenHeight = true;
|
||||||
|
if (this.isMobile) {
|
||||||
|
item = item.children[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.$nextTick();
|
||||||
|
let topMargin = this.getElementStyleValue(item, 'marginTop');
|
||||||
|
let bottomMargin = this.getElementStyleValue(item, 'marginBottom');
|
||||||
|
let totalVerticalMargin = topMargin + bottomMargin;
|
||||||
|
|
||||||
|
this.itemHeight = item.clientHeight + totalVerticalMargin;
|
||||||
|
this.setChildrenHeight();
|
||||||
|
if (this.afterChildHeight) {
|
||||||
|
this.afterChildHeight();
|
||||||
|
delete this.afterChildHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getChildHeight = false;
|
||||||
|
this.settingChildrenHeight = false;
|
||||||
|
},
|
||||||
|
setPageThreshold() {
|
||||||
|
let threshold = Math.ceil(this.availableContainerHeight / this.itemHeight) + ITEM_BUFFER;
|
||||||
|
// all items haven't loaded yet (nextTick not working for this)
|
||||||
|
if (threshold === ITEM_BUFFER) {
|
||||||
|
window.setTimeout(this.setPageThreshold, RECHECK_DELAY);
|
||||||
|
} else {
|
||||||
|
this.pageThreshold = threshold;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleWindowResize() {
|
||||||
|
if (!windowResizing) {
|
||||||
|
windowResizing = true;
|
||||||
|
window.clearTimeout(windowResizeId);
|
||||||
|
windowResizeId = window.setTimeout(() => {
|
||||||
|
this.setContainerHeight();
|
||||||
|
windowResizing = false;
|
||||||
|
}, RESIZE_FIRE_DELAY_MS);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getAllChildren(node) {
|
||||||
|
this.isLoading = true;
|
||||||
|
if (this.composition) {
|
||||||
|
this.composition.off('add', this.addChild);
|
||||||
|
this.composition.off('remove', this.removeChild);
|
||||||
|
delete this.composition;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allTreeItems = [];
|
||||||
|
this.composition = this.openmct.composition.get(node.object);
|
||||||
|
this.composition.on('add', this.addChild);
|
||||||
|
this.composition.on('remove', this.removeChild);
|
||||||
|
await this.composition.load();
|
||||||
|
this.finishLoading();
|
||||||
|
},
|
||||||
|
buildTreeItem(domainObject) {
|
||||||
|
let navToParent = ROOT_PATH + this.currentNavigatedPath;
|
||||||
|
if (navToParent === ROOT_PATH) {
|
||||||
|
navToParent = navToParent.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: this.openmct.objects.makeKeyString(domainObject.identifier),
|
||||||
|
object: domainObject,
|
||||||
|
objectPath: [domainObject].concat(this.currentObjectPath),
|
||||||
|
navigateToParent: navToParent
|
||||||
|
};
|
||||||
|
},
|
||||||
|
addChild(child) {
|
||||||
|
let item = this.buildTreeItem(child);
|
||||||
|
this.allTreeItems.push(item);
|
||||||
|
},
|
||||||
|
removeChild(identifier) {
|
||||||
|
let removeId = this.openmct.objects.makeKeyString(identifier);
|
||||||
|
this.allTreeItems = this.allTreeItems
|
||||||
|
.filter(c => c.id !== removeId);
|
||||||
|
this.setContainerHeight();
|
||||||
|
},
|
||||||
|
finishLoading() {
|
||||||
|
if (this.jumpPath) {
|
||||||
|
this.jumpToPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.autoScroll();
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
async jumpToPath(saveExpandedPath = false) {
|
||||||
|
// switching back and forth between multiple root children can cause issues,
|
||||||
|
// this checks for one of those issues
|
||||||
|
if (this.jumpPath.key) {
|
||||||
|
this.jumpPath = this.jumpPath.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nodes = this.jumpPath.split('/');
|
||||||
|
|
||||||
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
|
let currentNode = await this.openmct.objects.get(nodes[i]);
|
||||||
|
let newParent = this.buildTreeItem(currentNode);
|
||||||
|
this.ancestors.push(newParent);
|
||||||
|
|
||||||
|
if (i === nodes.length - 1) {
|
||||||
|
this.jumpPath = '';
|
||||||
|
this.getAllChildren(newParent);
|
||||||
|
if (this.afterJump) {
|
||||||
|
await this.$nextTick();
|
||||||
|
this.afterJump();
|
||||||
|
delete this.afterJump;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveExpandedPath) {
|
||||||
|
this.setCurrentNavigatedPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async autoScroll() {
|
||||||
|
if (!this.scrollTo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$refs.scrollable) {
|
||||||
|
let indexOfScroll = this.indexOfItemById(this.scrollTo);
|
||||||
|
let scrollTopAmount = indexOfScroll * this.itemHeight;
|
||||||
|
|
||||||
|
await this.$nextTick();
|
||||||
|
this.$refs.scrollable.scrollTop = scrollTopAmount;
|
||||||
|
// race condition check
|
||||||
|
if (scrollTopAmount > 0 && this.$refs.scrollable.scrollTop === 0) {
|
||||||
|
window.setTimeout(this.autoScroll, RECHECK_DELAY);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollTo = undefined;
|
||||||
|
} else {
|
||||||
|
window.setTimeout(this.autoScroll, RECHECK_DELAY);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indexOfItemById(id) {
|
||||||
|
for (let i = 0; i < this.allTreeItems.length; i++) {
|
||||||
|
if (this.allTreeItems[i].id === id) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getSearchResults() {
|
||||||
|
let results = await this.searchService.query(this.searchValue);
|
||||||
|
this.searchResultItems = results.hits.map(result => {
|
||||||
|
|
||||||
|
let context = result.object.getCapability('context');
|
||||||
|
let object = result.object.useCapability('adapter');
|
||||||
let objectPath = [];
|
let objectPath = [];
|
||||||
let navigateToParent;
|
let navigateToParent;
|
||||||
|
|
||||||
@ -113,9 +523,9 @@ export default {
|
|||||||
objectPath = context.getPath().slice(1)
|
objectPath = context.getPath().slice(1)
|
||||||
.map(oldObject => oldObject.useCapability('adapter'))
|
.map(oldObject => oldObject.useCapability('adapter'))
|
||||||
.reverse();
|
.reverse();
|
||||||
navigateToParent = '/browse/' + objectPath.slice(1)
|
navigateToParent = objectPath.slice(1)
|
||||||
.map((parent) => this.openmct.objects.makeKeyString(parent.identifier))
|
.map((parent) => this.openmct.objects.makeKeyString(parent.identifier));
|
||||||
.join('/');
|
navigateToParent = ROOT_PATH + navigateToParent.reverse().join('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -125,13 +535,110 @@ export default {
|
|||||||
navigateToParent
|
navigateToParent
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
searchTree(value) {
|
searchTree(value) {
|
||||||
this.searchValue = value;
|
this.searchValue = value;
|
||||||
|
|
||||||
if (this.searchValue !== '') {
|
if (this.searchValue !== '') {
|
||||||
this.getFilteredChildren();
|
this.getSearchResults();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchActivated() {
|
||||||
|
this.activeSearch = true;
|
||||||
|
this.$refs.scrollable.scrollTop = 0;
|
||||||
|
},
|
||||||
|
searchDeactivated() {
|
||||||
|
this.activeSearch = false;
|
||||||
|
this.$refs.scrollable.scrollTop = 0;
|
||||||
|
this.setContainerHeight();
|
||||||
|
},
|
||||||
|
async handleReset(node) {
|
||||||
|
this.visibleItems = [];
|
||||||
|
await this.$nextTick(); // prevents "ghost" image of visibleItems
|
||||||
|
this.childrenSlideClass = 'slide-right';
|
||||||
|
this.ancestors.splice(this.ancestors.indexOf(node) + 1);
|
||||||
|
this.getAllChildren(node);
|
||||||
|
this.setCurrentNavigatedPath();
|
||||||
|
},
|
||||||
|
async handleExpanded(node) {
|
||||||
|
if (this.activeSearch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visibleItems = [];
|
||||||
|
await this.$nextTick(); // prevents "ghost" image of visibleItems
|
||||||
|
this.childrenSlideClass = 'slide-left';
|
||||||
|
let newParent = this.buildTreeItem(node);
|
||||||
|
this.ancestors.push(newParent);
|
||||||
|
this.getAllChildren(newParent);
|
||||||
|
this.setCurrentNavigatedPath();
|
||||||
|
},
|
||||||
|
getSavedNavigatedPath() {
|
||||||
|
return JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY__EXPANDED_TREE_NODE));
|
||||||
|
},
|
||||||
|
setCurrentNavigatedPath() {
|
||||||
|
if (!this.searchValue) {
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_KEY__EXPANDED_TREE_NODE, JSON.stringify(this.currentNavigatedPath));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentPathIsActivePath() {
|
||||||
|
return this.getSavedNavigatedPath() === this.currentlyViewedObjectParentPath();
|
||||||
|
},
|
||||||
|
currentlyViewedObjectId() {
|
||||||
|
let currentPath = this.openmct.router.currentLocation.path;
|
||||||
|
if (currentPath) {
|
||||||
|
currentPath = currentPath.split(ROOT_PATH)[1];
|
||||||
|
|
||||||
|
return currentPath.split('/').pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentlyViewedObjectParentPath() {
|
||||||
|
let currentPath = this.openmct.router.currentLocation.path;
|
||||||
|
if (currentPath) {
|
||||||
|
currentPath = currentPath.split(ROOT_PATH)[1];
|
||||||
|
currentPath = currentPath.split('/');
|
||||||
|
currentPath.pop();
|
||||||
|
|
||||||
|
return currentPath.join('/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollItems(event) {
|
||||||
|
if (!windowResizing) {
|
||||||
|
this.updatevisibleItems();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childrenListStyles() {
|
||||||
|
return { position: 'relative' };
|
||||||
|
},
|
||||||
|
scrollableStyles() {
|
||||||
|
return {
|
||||||
|
height: this.availableContainerHeight + 'px',
|
||||||
|
overflow: this.noScroll ? 'hidden' : 'scroll'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
emptyStyles() {
|
||||||
|
let offset = ((this.ancestors.length + 1) * 10);
|
||||||
|
|
||||||
|
return {
|
||||||
|
paddingLeft: offset + 'px'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
childrenIn(el, done) {
|
||||||
|
// still needing this timeout for some reason
|
||||||
|
window.setTimeout(this.setContainerHeight, RECHECK_DELAY);
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
getElementStyleValue(el, style) {
|
||||||
|
let styleString = window.getComputedStyle(el)[style];
|
||||||
|
let index = styleString.indexOf('px');
|
||||||
|
|
||||||
|
return Number(styleString.slice(0, index));
|
||||||
|
},
|
||||||
|
backwardsCompatibilityCheck() {
|
||||||
|
let oldTreeExpanded = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED__OLD));
|
||||||
|
|
||||||
|
if (oldTreeExpanded) {
|
||||||
|
localStorage.removeItem(LOCAL_STORAGE_KEY__TREE_EXPANDED__OLD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@include desktop() { margin-bottom: $interiorMargin; }
|
@include desktop() { margin-bottom: $interiorMargin; }
|
||||||
|
|
||||||
|
[class*="button"] {
|
||||||
|
color: $colorBtnMajorBg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--reacts {
|
&--reacts {
|
||||||
@ -128,12 +132,23 @@
|
|||||||
@include userSelectNone();
|
@include userSelectNone();
|
||||||
color: $splitterBtnLabelColorFg;
|
color: $splitterBtnLabelColorFg;
|
||||||
display: block;
|
display: block;
|
||||||
pointer-events: none;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
transform-origin: top left;
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[class*="expand-button"] {
|
||||||
|
display: none; // Hidden by default
|
||||||
|
background: $splitterCollapsedBtnColorBg;
|
||||||
|
color: $splitterCollapsedBtnColorFg;
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $splitterCollapsedBtnColorBgHov;
|
||||||
|
color: inherit;
|
||||||
|
transition: $transIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&--resizing {
|
&--resizing {
|
||||||
// User is dragging the handle and resizing a pane
|
// User is dragging the handle and resizing a pane
|
||||||
@include userSelectNone();
|
@include userSelectNone();
|
||||||
@ -160,23 +175,12 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-pane__header {
|
[class*="collapse-button"] {
|
||||||
&:hover {
|
display: none;
|
||||||
color: $splitterCollapsedBtnColorFgHov;
|
|
||||||
.l-pane__label {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
.l-pane__collapse-button {
|
|
||||||
background: $splitterCollapsedBtnColorBgHov;
|
|
||||||
color: inherit;
|
|
||||||
transition: $transIn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-pane__collapse-button {
|
[class*="expand-button"] {
|
||||||
background: $splitterCollapsedBtnColorBg;
|
display: block;
|
||||||
color: $splitterCollapsedBtnColorFg;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,36 +202,26 @@
|
|||||||
|
|
||||||
.l-pane__collapse-button {
|
.l-pane__collapse-button {
|
||||||
&:before {
|
&:before {
|
||||||
content: $glyph-icon-arrow-right-equilateral;
|
content: $glyph-icon-line-horz;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[class*="--collapsed"] {
|
&[class*="--collapsed"] {
|
||||||
/************************ COLLAPSED HORIZONTAL SPLITTER, EITHER DIRECTION */
|
/************************ COLLAPSED HORIZONTAL SPLITTER, EITHER DIRECTION */
|
||||||
[class*="__header"] {
|
[class*="__header"] {
|
||||||
@include abs();
|
display: none;
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="label"] {
|
[class*="expand-button"] {
|
||||||
position: absolute;
|
|
||||||
transform: translate($interiorMarginLg + 1, 18px) rotate(90deg);
|
|
||||||
left: 3px;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-pane__collapse-button {
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-bottom-left-radius: 0; // Only have to do this once, because of scaleX(-1) below.
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; right: 0; bottom: 0; left: 0;
|
top: 0; right: 0; bottom: 0; left: 0;
|
||||||
height: auto; width: 100%;
|
height: auto; width: 100%;
|
||||||
padding: 0;
|
padding: $interiorMargin 2px;
|
||||||
|
|
||||||
&:before {
|
[class*="label"] {
|
||||||
position: absolute;
|
text-orientation: mixed;
|
||||||
top: 5px;
|
text-transform: uppercase;
|
||||||
|
writing-mode: vertical-lr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,10 +237,9 @@
|
|||||||
transform: translateX(floor($splitterHandleD / -2)); // Center over the pane edge
|
transform: translateX(floor($splitterHandleD / -2)); // Center over the pane edge
|
||||||
}
|
}
|
||||||
|
|
||||||
&[class*="--collapsed"] {
|
[class*="expand-button"] {
|
||||||
.l-pane__collapse-button {
|
border-top-left-radius: $controlCr;
|
||||||
transform: scaleX(-1);
|
border-bottom-left-radius: $controlCr;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,10 +254,9 @@
|
|||||||
transform: translateX(floor($splitterHandleD / 2));
|
transform: translateX(floor($splitterHandleD / 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not([class*="--collapsed"]) {
|
[class*="expand-button"] {
|
||||||
.l-pane__collapse-button {
|
border-top-right-radius: $controlCr;
|
||||||
transform: scaleX(-1);
|
border-bottom-right-radius: $controlCr;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,19 @@
|
|||||||
<span v-if="label"
|
<span v-if="label"
|
||||||
class="l-pane__label"
|
class="l-pane__label"
|
||||||
>{{ label }}</span>
|
>{{ label }}</span>
|
||||||
|
<slot name="controls"></slot>
|
||||||
<button
|
<button
|
||||||
v-if="collapsable"
|
v-if="collapsable"
|
||||||
class="l-pane__collapse-button c-button"
|
class="l-pane__collapse-button c-icon-button"
|
||||||
@click="toggleCollapse"
|
@click="toggleCollapse"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
class="l-pane__expand-button"
|
||||||
|
@click="toggleCollapse"
|
||||||
|
>
|
||||||
|
<span class="l-pane__expand-button__label">{{ label }}</span>
|
||||||
|
</button>
|
||||||
<div class="l-pane__contents">
|
<div class="l-pane__contents">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,38 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<li class="c-tree__item-h">
|
<li
|
||||||
|
ref="me"
|
||||||
|
:style="{
|
||||||
|
'top': virtualScroll ? itemTop : 'auto',
|
||||||
|
'position': virtualScroll ? 'absolute' : 'relative'
|
||||||
|
}"
|
||||||
|
class="c-tree__item-h"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="c-tree__item"
|
class="c-tree__item"
|
||||||
:class="{ 'is-alias': isAlias, 'is-navigated-object': navigated }"
|
:class="{
|
||||||
|
'is-alias': isAlias,
|
||||||
|
'is-navigated-object': navigated
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<view-control
|
<view-control
|
||||||
v-model="expanded"
|
v-model="expanded"
|
||||||
class="c-tree__item__view-control"
|
class="c-tree__item__view-control"
|
||||||
:enabled="hasChildren"
|
:control-class="'c-nav__up'"
|
||||||
|
:enabled="showUp"
|
||||||
|
@input="resetTreeHere"
|
||||||
/>
|
/>
|
||||||
<object-label
|
<object-label
|
||||||
:domain-object="node.object"
|
:domain-object="node.object"
|
||||||
:object-path="node.objectPath"
|
:object-path="node.objectPath"
|
||||||
:navigate-to-path="navigateToPath"
|
:navigate-to-path="navigateToPath"
|
||||||
|
:style="{ paddingLeft: leftOffset }"
|
||||||
|
/>
|
||||||
|
<view-control
|
||||||
|
v-model="expanded"
|
||||||
|
class="c-tree__item__view-control"
|
||||||
|
:control-class="'c-nav__down'"
|
||||||
|
:enabled="hasComposition && showDown"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
|
||||||
v-if="expanded"
|
|
||||||
class="c-tree"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
v-if="isLoading && !loaded"
|
|
||||||
class="c-tree__item-h"
|
|
||||||
>
|
|
||||||
<div class="c-tree__item loading">
|
|
||||||
<span class="c-tree__item__label">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<tree-item
|
|
||||||
v-for="child in children"
|
|
||||||
:key="child.id"
|
|
||||||
:node="child"
|
|
||||||
/>
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -40,8 +41,6 @@
|
|||||||
import viewControl from '../components/viewControl.vue';
|
import viewControl from '../components/viewControl.vue';
|
||||||
import ObjectLabel from '../components/ObjectLabel.vue';
|
import ObjectLabel from '../components/ObjectLabel.vue';
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TreeItem',
|
name: 'TreeItem',
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
@ -53,17 +52,49 @@ export default {
|
|||||||
node: {
|
node: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
leftOffset: {
|
||||||
|
type: String,
|
||||||
|
default: '0px'
|
||||||
|
},
|
||||||
|
showUp: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showDown: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
itemIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
itemOffset: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
itemHeight: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
virtualScroll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
emitHeight: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
this.navigateToPath = this.buildPathString(this.node.navigateToParent);
|
this.navigateToPath = this.buildPathString(this.node.navigateToParent);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasChildren: false,
|
hasComposition: false,
|
||||||
isLoading: false,
|
|
||||||
loaded: false,
|
|
||||||
navigated: this.navigateToPath === this.openmct.router.currentLocation.path,
|
navigated: this.navigateToPath === this.openmct.router.currentLocation.path,
|
||||||
children: [],
|
|
||||||
expanded: false
|
expanded: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -77,32 +108,23 @@ export default {
|
|||||||
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
|
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
|
||||||
|
|
||||||
return parentKeyString !== this.node.object.location;
|
return parentKeyString !== this.node.object.location;
|
||||||
|
},
|
||||||
|
itemTop() {
|
||||||
|
return (this.itemOffset + this.itemIndex) * this.itemHeight + 'px';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
expanded() {
|
expanded() {
|
||||||
if (!this.hasChildren) {
|
this.$emit('expanded', this.domainObject);
|
||||||
return;
|
},
|
||||||
}
|
emitHeight() {
|
||||||
|
this.$nextTick(() => {
|
||||||
if (!this.loaded && !this.isLoading) {
|
this.$emit('emittedHeight', this.$refs.me);
|
||||||
this.composition = this.openmct.composition.get(this.domainObject);
|
});
|
||||||
this.composition.on('add', this.addChild);
|
|
||||||
this.composition.on('remove', this.removeChild);
|
|
||||||
this.composition.load().then(this.finishLoading);
|
|
||||||
this.isLoading = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setLocalStorageExpanded(this.navigateToPath);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// TODO: should update on mutation.
|
let objectComposition = this.openmct.composition.get(this.node.object);
|
||||||
// TODO: click navigation should not fubar hash quite so much.
|
|
||||||
// TODO: should highlight if navigated to.
|
|
||||||
// TODO: should have context menu.
|
|
||||||
// TODO: should support drag/drop composition
|
|
||||||
// TODO: set isAlias per tree-item
|
|
||||||
|
|
||||||
this.domainObject = this.node.object;
|
this.domainObject = this.node.object;
|
||||||
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
|
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
|
||||||
@ -110,49 +132,19 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.$once('hook:destroyed', removeListener);
|
this.$once('hook:destroyed', removeListener);
|
||||||
if (this.openmct.composition.get(this.node.object)) {
|
if (objectComposition) {
|
||||||
this.hasChildren = true;
|
this.hasComposition = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.router.on('change:path', this.highlightIfNavigated);
|
this.openmct.router.on('change:path', this.highlightIfNavigated);
|
||||||
|
if (this.emitHeight) {
|
||||||
this.getLocalStorageExpanded();
|
this.$emit('emittedHeight', this.$refs.me);
|
||||||
},
|
}
|
||||||
beforeDestroy() {
|
|
||||||
/****
|
|
||||||
* calling this.setLocalStorageExpanded explicitly here because for whatever reason,
|
|
||||||
* the watcher on this.expanded is not triggering this.setLocalStorageExpanded(),
|
|
||||||
* even though Vue documentation states, "At this stage the instance is still fully functional."
|
|
||||||
*****/
|
|
||||||
this.expanded = false;
|
|
||||||
this.setLocalStorageExpanded();
|
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.openmct.router.off('change:path', this.highlightIfNavigated);
|
this.openmct.router.off('change:path', this.highlightIfNavigated);
|
||||||
if (this.composition) {
|
|
||||||
this.composition.off('add', this.addChild);
|
|
||||||
this.composition.off('remove', this.removeChild);
|
|
||||||
delete this.composition;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addChild(child) {
|
|
||||||
this.children.push({
|
|
||||||
id: this.openmct.objects.makeKeyString(child.identifier),
|
|
||||||
object: child,
|
|
||||||
objectPath: [child].concat(this.node.objectPath),
|
|
||||||
navigateToParent: this.navigateToPath
|
|
||||||
});
|
|
||||||
},
|
|
||||||
removeChild(identifier) {
|
|
||||||
let removeId = this.openmct.objects.makeKeyString(identifier);
|
|
||||||
this.children = this.children
|
|
||||||
.filter(c => c.id !== removeId);
|
|
||||||
},
|
|
||||||
finishLoading() {
|
|
||||||
this.isLoading = false;
|
|
||||||
this.loaded = true;
|
|
||||||
},
|
|
||||||
buildPathString(parentPath) {
|
buildPathString(parentPath) {
|
||||||
return [parentPath, this.openmct.objects.makeKeyString(this.node.object.identifier)].join('/');
|
return [parentPath, this.openmct.objects.makeKeyString(this.node.object.identifier)].join('/');
|
||||||
},
|
},
|
||||||
@ -163,35 +155,8 @@ export default {
|
|||||||
this.navigated = false;
|
this.navigated = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getLocalStorageExpanded() {
|
resetTreeHere() {
|
||||||
let expandedPaths = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED);
|
this.$emit('resetTree', this.node);
|
||||||
|
|
||||||
if (expandedPaths) {
|
|
||||||
expandedPaths = JSON.parse(expandedPaths);
|
|
||||||
this.expanded = expandedPaths.includes(this.navigateToPath);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// expanded nodes/paths are stored in local storage as an array
|
|
||||||
setLocalStorageExpanded() {
|
|
||||||
let expandedPaths = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED);
|
|
||||||
expandedPaths = expandedPaths ? JSON.parse(expandedPaths) : [];
|
|
||||||
|
|
||||||
if (this.expanded) {
|
|
||||||
if (!expandedPaths.includes(this.navigateToPath)) {
|
|
||||||
expandedPaths.push(this.navigateToPath);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// remove this node path and all children paths from stored expanded paths
|
|
||||||
expandedPaths = expandedPaths.filter(path => !path.startsWith(this.navigateToPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_KEY__TREE_EXPANDED, JSON.stringify(expandedPaths));
|
|
||||||
},
|
|
||||||
removeLocalStorageExpanded() {
|
|
||||||
let expandedPaths = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED);
|
|
||||||
expandedPaths = expandedPaths ? JSON.parse(expandedPaths) : [];
|
|
||||||
expandedPaths = expandedPaths.filter(path => !path.startsWith(this.navigateToPath));
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_KEY__TREE_EXPANDED, JSON.stringify(expandedPaths));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -193,11 +193,7 @@ export function getMockObjects(opts = {}) {
|
|||||||
if (opts.overwrite) {
|
if (opts.overwrite) {
|
||||||
for (let mock in requestedMocks) {
|
for (let mock in requestedMocks) {
|
||||||
if (opts.overwrite[mock]) {
|
if (opts.overwrite[mock]) {
|
||||||
for (let key in opts.overwrite[mock]) {
|
requestedMocks[mock] = Object.assign(requestedMocks[mock], opts.overwrite[mock]);
|
||||||
if (Object.prototype.hasOwnProperty.call(opts.overwrite[mock], key)) {
|
|
||||||
requestedMocks[mock][key] = opts.overwrite[mock][key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -254,6 +250,16 @@ function copyObj(obj) {
|
|||||||
function setMockObjects() {
|
function setMockObjects() {
|
||||||
return {
|
return {
|
||||||
default: {
|
default: {
|
||||||
|
folder: {
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "folder-object"
|
||||||
|
},
|
||||||
|
name: "Test Folder Object",
|
||||||
|
type: "folder",
|
||||||
|
composition: [],
|
||||||
|
location: "mine"
|
||||||
|
},
|
||||||
ladTable: {
|
ladTable: {
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: "",
|
namespace: "",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user