Compare commits
14 Commits
v4.0.0-rc1
...
mct6113-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
911bb0b8e9 | ||
|
|
ce567c387c | ||
|
|
55d125d429 | ||
|
|
ab3c52c9b2 | ||
|
|
f354e1d263 | ||
|
|
7ff366c342 | ||
|
|
fe38f17069 | ||
|
|
38c5054e30 | ||
|
|
18bacfd251 | ||
|
|
7ecfebb352 | ||
|
|
c7fb6079ef | ||
|
|
6193f79ce0 | ||
|
|
3422853d3d | ||
|
|
bc550d366b |
@@ -165,11 +165,11 @@ async function createPlanFromJSON(page, { name, json, parent = 'mine' }) {
|
||||
// in the correct location, such as a folder, layout, or plot.
|
||||
await page.goto(`${parentUrl}?hideTree=true`);
|
||||
|
||||
//Click the Create button
|
||||
// Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
// Click 'Plan' menu option
|
||||
await page.click(`li:text("Plan")`);
|
||||
// Click 'Gantt Chart' menu option
|
||||
await page.click(`li:text("Gantt Chart")`);
|
||||
|
||||
// Modify the name input field of the domain object to accept 'name'
|
||||
if (name) {
|
||||
|
||||
@@ -68,7 +68,7 @@ const testPlan = {
|
||||
};
|
||||
|
||||
test.describe("Plan", () => {
|
||||
test("Create a Plan and display all plan events @unstable", async ({ page }) => {
|
||||
test("Create a Plan and display all plan events", async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'networkidle' });
|
||||
|
||||
const plan = await createPlanFromJSON(page, {
|
||||
|
||||
@@ -71,7 +71,7 @@ function (
|
||||
StatusAPI: StatusAPI.default,
|
||||
TelemetryAPI: TelemetryAPI,
|
||||
TimeAPI: TimeAPI.default,
|
||||
TypeRegistry: TypeRegistry,
|
||||
TypeRegistry: TypeRegistry.default,
|
||||
UserAPI: UserAPI.default,
|
||||
AnnotationAPI: AnnotationAPI.default
|
||||
};
|
||||
|
||||
@@ -20,63 +20,25 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(function () {
|
||||
|
||||
/**
|
||||
* A Type describes a kind of domain object that may appear or be
|
||||
* created within Open MCT.
|
||||
*
|
||||
* @param {module:opemct.TypeRegistry~TypeDefinition} definition
|
||||
* @class Type
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
function Type(definition) {
|
||||
/**
|
||||
* A Type describes a kind of domain object that may appear or be
|
||||
* created within Open MCT.
|
||||
*
|
||||
* @param {module:opemct.TypeRegistry~TypeDefinition} definition
|
||||
* @class Type
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
export default class Type {
|
||||
constructor(definition) {
|
||||
this.definition = definition;
|
||||
if (definition.key) {
|
||||
this.key = definition.key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a domain object is an instance of this type.
|
||||
* @param domainObject
|
||||
* @returns {boolean} true if the domain object is of this type
|
||||
* @memberof module:openmct.Type#
|
||||
* @method check
|
||||
*/
|
||||
Type.prototype.check = function (domainObject) {
|
||||
// Depends on assignment from MCT.
|
||||
return domainObject.type === this.key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a definition for this type that can be registered using the
|
||||
* legacy bundle format.
|
||||
* @private
|
||||
*/
|
||||
Type.prototype.toLegacyDefinition = function () {
|
||||
const def = {};
|
||||
def.name = this.definition.name;
|
||||
def.cssClass = this.definition.cssClass;
|
||||
def.description = this.definition.description;
|
||||
def.properties = this.definition.form;
|
||||
|
||||
if (this.definition.initialize) {
|
||||
def.model = {};
|
||||
this.definition.initialize(def.model);
|
||||
}
|
||||
|
||||
if (this.definition.creatable) {
|
||||
def.features = ['creation'];
|
||||
}
|
||||
|
||||
return def;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a type definition from a legacy definition.
|
||||
*/
|
||||
Type.definitionFromLegacyDefinition = function (legacyDefinition) {
|
||||
static definitionFromLegacyDefinition(legacyDefinition) {
|
||||
let definition = {};
|
||||
definition.name = legacyDefinition.name;
|
||||
definition.cssClass = legacyDefinition.cssClass;
|
||||
@@ -121,7 +83,39 @@ define(function () {
|
||||
}
|
||||
|
||||
return definition;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Check if a domain object is an instance of this type.
|
||||
* @param domainObject
|
||||
* @returns {boolean} true if the domain object is of this type
|
||||
* @memberof module:openmct.Type#
|
||||
* @method check
|
||||
*/
|
||||
check(domainObject) {
|
||||
// Depends on assignment from MCT.
|
||||
return domainObject.type === this.key;
|
||||
}
|
||||
/**
|
||||
* Get a definition for this type that can be registered using the
|
||||
* legacy bundle format.
|
||||
* @private
|
||||
*/
|
||||
toLegacyDefinition() {
|
||||
const def = {};
|
||||
def.name = this.definition.name;
|
||||
def.cssClass = this.definition.cssClass;
|
||||
def.description = this.definition.description;
|
||||
def.properties = this.definition.form;
|
||||
|
||||
return Type;
|
||||
});
|
||||
if (this.definition.initialize) {
|
||||
def.model = {};
|
||||
this.definition.initialize(def.model);
|
||||
}
|
||||
|
||||
if (this.definition.creatable) {
|
||||
def.features = ['creation'];
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,35 +19,36 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define(['./Type'], function (Type) {
|
||||
const UNKNOWN_TYPE = new Type({
|
||||
key: "unknown",
|
||||
name: "Unknown Type",
|
||||
cssClass: "icon-object-unknown"
|
||||
});
|
||||
import Type from './Type';
|
||||
|
||||
/**
|
||||
* @typedef TypeDefinition
|
||||
* @memberof module:openmct.TypeRegistry~
|
||||
* @property {string} label the name for this type of object
|
||||
* @property {string} description a longer-form description of this type
|
||||
* @property {function (object)} [initialize] a function which initializes
|
||||
* the model for new domain objects of this type
|
||||
* @property {boolean} [creatable] true if users should be allowed to
|
||||
* create this type (default: false)
|
||||
* @property {string} [cssClass] the CSS class to apply for icons
|
||||
*/
|
||||
const UNKNOWN_TYPE = new Type({
|
||||
key: "unknown",
|
||||
name: "Unknown Type",
|
||||
cssClass: "icon-object-unknown"
|
||||
});
|
||||
|
||||
/**
|
||||
* A TypeRegistry maintains the definitions for different types
|
||||
* that domain objects may have.
|
||||
* @interface TypeRegistry
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
function TypeRegistry() {
|
||||
/**
|
||||
* @typedef TypeDefinition
|
||||
* @memberof module:openmct.TypeRegistry~
|
||||
* @property {string} label the name for this type of object
|
||||
* @property {string} description a longer-form description of this type
|
||||
* @property {function (object)} [initialize] a function which initializes
|
||||
* the model for new domain objects of this type
|
||||
* @property {boolean} [creatable] true if users should be allowed to
|
||||
* create this type (default: false)
|
||||
* @property {string} [cssClass] the CSS class to apply for icons
|
||||
*/
|
||||
|
||||
/**
|
||||
* A TypeRegistry maintains the definitions for different types
|
||||
* that domain objects may have.
|
||||
* @interface TypeRegistry
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
export default class TypeRegistry {
|
||||
constructor() {
|
||||
this.types = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new object type.
|
||||
*
|
||||
@@ -56,17 +57,16 @@ define(['./Type'], function (Type) {
|
||||
* @method addType
|
||||
* @memberof module:openmct.TypeRegistry#
|
||||
*/
|
||||
TypeRegistry.prototype.addType = function (typeKey, typeDef) {
|
||||
addType(typeKey, typeDef) {
|
||||
this.standardizeType(typeDef);
|
||||
this.types[typeKey] = new Type(typeDef);
|
||||
};
|
||||
|
||||
}
|
||||
/**
|
||||
* Takes a typeDef, standardizes it, and logs warnings about unsupported
|
||||
* usage.
|
||||
* @private
|
||||
*/
|
||||
TypeRegistry.prototype.standardizeType = function (typeDef) {
|
||||
standardizeType(typeDef) {
|
||||
if (Object.prototype.hasOwnProperty.call(typeDef, 'label')) {
|
||||
if (!typeDef.name) {
|
||||
typeDef.name = typeDef.label;
|
||||
@@ -74,18 +74,16 @@ define(['./Type'], function (Type) {
|
||||
|
||||
delete typeDef.label;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
/**
|
||||
* List keys for all registered types.
|
||||
* @method listKeys
|
||||
* @memberof module:openmct.TypeRegistry#
|
||||
* @returns {string[]} all registered type keys
|
||||
*/
|
||||
TypeRegistry.prototype.listKeys = function () {
|
||||
listKeys() {
|
||||
return Object.keys(this.types);
|
||||
};
|
||||
|
||||
}
|
||||
/**
|
||||
* Retrieve a registered type by its key.
|
||||
* @method get
|
||||
@@ -93,18 +91,15 @@ define(['./Type'], function (Type) {
|
||||
* @memberof module:openmct.TypeRegistry#
|
||||
* @returns {module:openmct.Type} the registered type
|
||||
*/
|
||||
TypeRegistry.prototype.get = function (typeKey) {
|
||||
get(typeKey) {
|
||||
return this.types[typeKey] || UNKNOWN_TYPE;
|
||||
};
|
||||
|
||||
TypeRegistry.prototype.importLegacyTypes = function (types) {
|
||||
}
|
||||
importLegacyTypes(types) {
|
||||
types.filter((t) => this.get(t.key) === UNKNOWN_TYPE)
|
||||
.forEach((type) => {
|
||||
let def = Type.definitionFromLegacyDefinition(type);
|
||||
this.addType(type.key, def);
|
||||
});
|
||||
};
|
||||
|
||||
return TypeRegistry;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,36 +20,36 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./TypeRegistry', './Type'], function (TypeRegistry, Type) {
|
||||
describe('The Type API', function () {
|
||||
let typeRegistryInstance;
|
||||
import TypeRegistry from './TypeRegistry';
|
||||
|
||||
beforeEach(function () {
|
||||
typeRegistryInstance = new TypeRegistry ();
|
||||
typeRegistryInstance.addType('testType', {
|
||||
name: 'Test Type',
|
||||
description: 'This is a test type.',
|
||||
creatable: true
|
||||
});
|
||||
});
|
||||
describe('The Type API', function () {
|
||||
let typeRegistryInstance;
|
||||
|
||||
it('types can be standardized', function () {
|
||||
typeRegistryInstance.addType('standardizationTestType', {
|
||||
label: 'Test Type',
|
||||
description: 'This is a test type.',
|
||||
creatable: true
|
||||
});
|
||||
typeRegistryInstance.standardizeType(typeRegistryInstance.types.standardizationTestType);
|
||||
expect(typeRegistryInstance.get('standardizationTestType').definition.label).toBeUndefined();
|
||||
expect(typeRegistryInstance.get('standardizationTestType').definition.name).toBe('Test Type');
|
||||
});
|
||||
|
||||
it('new types are registered successfully and can be retrieved', function () {
|
||||
expect(typeRegistryInstance.get('testType').definition.name).toBe('Test Type');
|
||||
});
|
||||
|
||||
it('type registry contains new keys', function () {
|
||||
expect(typeRegistryInstance.listKeys ()).toContain('testType');
|
||||
beforeEach(function () {
|
||||
typeRegistryInstance = new TypeRegistry ();
|
||||
typeRegistryInstance.addType('testType', {
|
||||
name: 'Test Type',
|
||||
description: 'This is a test type.',
|
||||
creatable: true
|
||||
});
|
||||
});
|
||||
|
||||
it('types can be standardized', function () {
|
||||
typeRegistryInstance.addType('standardizationTestType', {
|
||||
label: 'Test Type',
|
||||
description: 'This is a test type.',
|
||||
creatable: true
|
||||
});
|
||||
typeRegistryInstance.standardizeType(typeRegistryInstance.types.standardizationTestType);
|
||||
expect(typeRegistryInstance.get('standardizationTestType').definition.label).toBeUndefined();
|
||||
expect(typeRegistryInstance.get('standardizationTestType').definition.name).toBe('Test Type');
|
||||
});
|
||||
|
||||
it('new types are registered successfully and can be retrieved', function () {
|
||||
expect(typeRegistryInstance.get('testType').definition.name).toBe('Test Type');
|
||||
});
|
||||
|
||||
it('type registry contains new keys', function () {
|
||||
expect(typeRegistryInstance.listKeys ()).toContain('testType');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,9 @@ export default function BarGraphInspectorViewProvider(openmct) {
|
||||
template: '<bar-graph-options></bar-graph-options>'
|
||||
});
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
@@ -40,9 +43,6 @@ export default function BarGraphInspectorViewProvider(openmct) {
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ export default function ScatterPlotInspectorViewProvider(openmct) {
|
||||
template: '<plot-options></plot-options>'
|
||||
});
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
@@ -40,9 +43,6 @@ export default function ScatterPlotInspectorViewProvider(openmct) {
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ export default function FaultManagementInspectorViewProvider(openmct) {
|
||||
template: '<FaultManagementInspector></FaultManagementInspector>'
|
||||
});
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
@@ -63,9 +66,6 @@ export default function FaultManagementInspectorViewProvider(openmct) {
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,6 +61,9 @@ define([
|
||||
|
||||
return hasPersistedFilters || hasGlobalFilters;
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.DEFAULT;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
@@ -68,9 +71,6 @@ define([
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.DEFAULT;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,14 +76,14 @@ export default function StylesInspectorViewProvider(openmct) {
|
||||
template: `<StylesInspectorView />`
|
||||
});
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.DEFAULT;
|
||||
},
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return this.openmct.priority.DEFAULT;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
35
src/plugins/plan/GanttChartCompositionPolicy.js
Normal file
35
src/plugins/plan/GanttChartCompositionPolicy.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2023, 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.
|
||||
*****************************************************************************/
|
||||
const ALLOWED_TYPES = [
|
||||
'plan'
|
||||
];
|
||||
|
||||
export default function ganttChartCompositionPolicy(openmct) {
|
||||
return function (parent, child) {
|
||||
if (parent.type === 'gantt-chart') {
|
||||
return ALLOWED_TYPES.includes(child.type);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import TimelineAxis from "../../ui/components/TimeSystemAxis.vue";
|
||||
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
||||
import { getValidatedData } from "./util";
|
||||
import Vue from "vue";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
const PADDING = 1;
|
||||
const OUTER_TEXT_PADDING = 12;
|
||||
@@ -61,15 +62,16 @@ const RESIZE_POLL_INTERVAL = 200;
|
||||
const ROW_HEIGHT = 25;
|
||||
const LINE_HEIGHT = 12;
|
||||
const MAX_TEXT_WIDTH = 300;
|
||||
const EDGE_ROUNDING = 5;
|
||||
const MIN_ACTIVITY_WIDTH = 2;
|
||||
const DEFAULT_COLOR = '#cc9922';
|
||||
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TimelineAxis,
|
||||
SwimLane
|
||||
},
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
inject: ['openmct', 'domainObject', 'path', 'composition'],
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
@@ -88,8 +90,9 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
viewBounds: undefined,
|
||||
timeSystem: undefined,
|
||||
viewBounds: null,
|
||||
timeSystem: null,
|
||||
clipActivityNames: this.domainObject?.configuration?.clipActivityNames ?? false,
|
||||
height: 0
|
||||
};
|
||||
},
|
||||
@@ -103,9 +106,10 @@ export default {
|
||||
this.setDimensions();
|
||||
this.setTimeContext();
|
||||
this.resizeTimer = setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
|
||||
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
|
||||
this.status = this.openmct.status.get(this.domainObject.identifier);
|
||||
this.stopObservingConfig = this.openmct.objects.observe(this.domainObject, 'configuration', this.handleConfigurationChange);
|
||||
this.loadComposition();
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.resizeTimer);
|
||||
@@ -117,8 +121,18 @@ export default {
|
||||
if (this.removeStatusListener) {
|
||||
this.removeStatusListener();
|
||||
}
|
||||
|
||||
if (this.composition) {
|
||||
this.composition.off('add', this.handleCompositionAdd);
|
||||
this.composition.off('remove', this.handleCompositionRemove);
|
||||
}
|
||||
|
||||
this.stopObservingConfig();
|
||||
},
|
||||
methods: {
|
||||
activityNameFitsRect(activityName, rectWidth) {
|
||||
return (this.getTextWidth(activityName) + TEXT_LEFT_PADDING) < rectWidth;
|
||||
},
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.path);
|
||||
@@ -131,6 +145,14 @@ export default {
|
||||
this.timeContext.on("bounds", this.updateViewBounds);
|
||||
this.timeContext.on("clock", this.updateBounds);
|
||||
},
|
||||
loadComposition() {
|
||||
if (this.composition) {
|
||||
this.composition.on('add', this.handleCompositionAdd);
|
||||
this.composition.on('remove', this.handleCompositionRemove);
|
||||
this.composition.load();
|
||||
}
|
||||
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off("timeSystem", this.setScaleAndPlotActivities);
|
||||
@@ -138,6 +160,63 @@ export default {
|
||||
this.timeContext.off("clock", this.updateBounds);
|
||||
}
|
||||
},
|
||||
showReplacePlanDialog(domainObject) {
|
||||
return new Promise((resolve) => {
|
||||
let dialog = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: 'This action will replace the current Plan. Do you want to continue?',
|
||||
buttons: [
|
||||
{
|
||||
label: 'Ok',
|
||||
emphasis: true,
|
||||
callback: () => {
|
||||
this.removeFromComposition(this.planObject);
|
||||
this.planObject = domainObject;
|
||||
this.getPlanData(domainObject);
|
||||
this.setScaleAndPlotActivities();
|
||||
resolve();
|
||||
dialog.dismiss();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
callback: () => {
|
||||
this.removeFromComposition(domainObject);
|
||||
resolve();
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
async handleCompositionAdd(domainObject) {
|
||||
if (this.planObject) {
|
||||
await this.showReplacePlanDialog(domainObject);
|
||||
} else {
|
||||
this.planObject = domainObject;
|
||||
this.getPlanData(domainObject);
|
||||
this.setScaleAndPlotActivities();
|
||||
}
|
||||
},
|
||||
handleConfigurationChange(newConfiguration) {
|
||||
Object.keys(newConfiguration).forEach((key) => {
|
||||
this[key] = newConfiguration[key];
|
||||
});
|
||||
this.setScaleAndPlotActivities();
|
||||
},
|
||||
handleCompositionRemove(identifier) {
|
||||
if (this.planObject && this.openmct.objects.areIdsEqual(identifier, this.planObject?.identifier)) {
|
||||
this.planObject = null;
|
||||
this.planData = {};
|
||||
}
|
||||
|
||||
this.setScaleAndPlotActivities();
|
||||
},
|
||||
removeFromComposition(domainObject) {
|
||||
this.composition.remove(domainObject);
|
||||
},
|
||||
observeForChanges(mutatedObject) {
|
||||
this.getPlanData(mutatedObject);
|
||||
this.setScaleAndPlotActivities();
|
||||
@@ -193,14 +272,14 @@ export default {
|
||||
this.viewBounds = Object.create(bounds);
|
||||
}
|
||||
|
||||
if (this.timeSystem === undefined) {
|
||||
if (this.timeSystem === null) {
|
||||
this.timeSystem = this.openmct.time.timeSystem();
|
||||
}
|
||||
|
||||
this.setScaleAndPlotActivities();
|
||||
},
|
||||
setScaleAndPlotActivities(timeSystem) {
|
||||
if (timeSystem !== undefined) {
|
||||
if (timeSystem) {
|
||||
this.timeSystem = timeSystem;
|
||||
}
|
||||
|
||||
@@ -224,7 +303,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeSystem === undefined) {
|
||||
if (!timeSystem) {
|
||||
timeSystem = this.openmct.time.timeSystem();
|
||||
}
|
||||
|
||||
@@ -245,10 +324,15 @@ export default {
|
||||
isActivityInBounds(activity) {
|
||||
return (activity.start < this.viewBounds.end) && (activity.end > this.viewBounds.start);
|
||||
},
|
||||
getTextWidth(name) {
|
||||
let metrics = this.canvasContext.measureText(name);
|
||||
/**
|
||||
* Get the width of the given text in pixels.
|
||||
* @param {string} text
|
||||
* @returns {number} width of the text in pixels (as a double)
|
||||
*/
|
||||
getTextWidth(text) {
|
||||
const textMetrics = this.canvasContext.measureText(text);
|
||||
|
||||
return parseInt(metrics.width, 10);
|
||||
return textMetrics.width;
|
||||
},
|
||||
sortFn(a, b) {
|
||||
const numA = parseInt(a, 10);
|
||||
@@ -303,61 +387,62 @@ export default {
|
||||
|
||||
let activities = this.planData[key];
|
||||
activities.forEach((activity) => {
|
||||
if (this.isActivityInBounds(activity)) {
|
||||
const currentStart = Math.max(this.viewBounds.start, activity.start);
|
||||
const currentEnd = Math.min(this.viewBounds.end, activity.end);
|
||||
const rectX = this.xScale(currentStart);
|
||||
const rectY = this.xScale(currentEnd);
|
||||
const rectWidth = rectY - rectX;
|
||||
|
||||
const activityNameWidth = this.getTextWidth(activity.name) + TEXT_LEFT_PADDING;
|
||||
//TODO: Fix bug for SVG where the rectWidth is not proportional to the canvas measuredWidth of the text
|
||||
const activityNameFitsRect = (rectWidth >= activityNameWidth);
|
||||
const textStart = (activityNameFitsRect ? rectX : rectY) + TEXT_LEFT_PADDING;
|
||||
const color = activity.color || DEFAULT_COLOR;
|
||||
let textColor = '';
|
||||
if (activity.textColor) {
|
||||
textColor = activity.textColor;
|
||||
} else if (activityNameFitsRect) {
|
||||
textColor = this.getContrastingColor(color);
|
||||
}
|
||||
|
||||
let textLines = this.getActivityDisplayText(this.canvasContext, activity.name, activityNameFitsRect);
|
||||
const textWidth = textStart + this.getTextWidth(textLines[0]) + TEXT_LEFT_PADDING;
|
||||
|
||||
if (activityNameFitsRect) {
|
||||
currentRow = this.getRowForActivity(rectX, rectWidth, activitiesByRow);
|
||||
} else {
|
||||
currentRow = this.getRowForActivity(rectX, textWidth, activitiesByRow);
|
||||
}
|
||||
|
||||
let textY = parseInt(currentRow, 10) + (activityNameFitsRect ? INNER_TEXT_PADDING : OUTER_TEXT_PADDING);
|
||||
|
||||
if (!activitiesByRow[currentRow]) {
|
||||
activitiesByRow[currentRow] = [];
|
||||
}
|
||||
|
||||
activitiesByRow[currentRow].push({
|
||||
activity: {
|
||||
color: color,
|
||||
textColor: textColor,
|
||||
name: activity.name,
|
||||
exceeds: {
|
||||
start: this.xScale(this.viewBounds.start) > this.xScale(activity.start),
|
||||
end: this.xScale(this.viewBounds.end) < this.xScale(activity.end)
|
||||
},
|
||||
start: activity.start,
|
||||
end: activity.end
|
||||
},
|
||||
textLines: textLines,
|
||||
textStart: textStart,
|
||||
textClass: activityNameFitsRect ? "" : "activity-label--outside-rect",
|
||||
textY: textY,
|
||||
start: rectX,
|
||||
end: activityNameFitsRect ? rectY : textStart + textWidth,
|
||||
rectWidth: rectWidth
|
||||
});
|
||||
if (!this.isActivityInBounds(activity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentStart = Math.max(this.viewBounds.start, activity.start);
|
||||
const currentEnd = Math.min(this.viewBounds.end, activity.end);
|
||||
const rectX1 = this.xScale(currentStart);
|
||||
const rectX2 = this.xScale(currentEnd);
|
||||
const rectWidth = rectX2 - rectX1;
|
||||
|
||||
//TODO: Fix bug for SVG where the rectWidth is not proportional to the canvas measuredWidth of the text
|
||||
const showTextInsideRect = this.clipActivityNames || this.activityNameFitsRect(activity.name, rectWidth);
|
||||
const textStart = (showTextInsideRect ? rectX1 : rectX2) + TEXT_LEFT_PADDING;
|
||||
const color = activity.color || DEFAULT_COLOR;
|
||||
let textColor = '';
|
||||
if (activity.textColor) {
|
||||
textColor = activity.textColor;
|
||||
} else if (showTextInsideRect) {
|
||||
textColor = this.getContrastingColor(color);
|
||||
}
|
||||
|
||||
const textLines = this.getActivityDisplayText(this.canvasContext, activity.name, showTextInsideRect);
|
||||
const textWidth = textStart + this.getTextWidth(textLines[0]) + TEXT_LEFT_PADDING;
|
||||
|
||||
if (showTextInsideRect) {
|
||||
currentRow = this.getRowForActivity(rectX1, rectWidth, activitiesByRow);
|
||||
} else {
|
||||
currentRow = this.getRowForActivity(rectX1, textWidth, activitiesByRow);
|
||||
}
|
||||
|
||||
let textY = parseInt(currentRow, 10) + (showTextInsideRect ? INNER_TEXT_PADDING : OUTER_TEXT_PADDING);
|
||||
|
||||
if (!activitiesByRow[currentRow]) {
|
||||
activitiesByRow[currentRow] = [];
|
||||
}
|
||||
|
||||
activitiesByRow[currentRow].push({
|
||||
activity: {
|
||||
color: color,
|
||||
textColor: textColor,
|
||||
name: activity.name,
|
||||
exceeds: {
|
||||
start: this.xScale(this.viewBounds.start) > this.xScale(activity.start),
|
||||
end: this.xScale(this.viewBounds.end) < this.xScale(activity.end)
|
||||
},
|
||||
start: activity.start,
|
||||
end: activity.end
|
||||
},
|
||||
textLines: textLines,
|
||||
textStart: textStart,
|
||||
textClass: showTextInsideRect ? "" : "activity-label--outside-rect",
|
||||
textY: textY,
|
||||
start: rectX1,
|
||||
end: showTextInsideRect ? rectX2 : textStart + textWidth,
|
||||
rectWidth: rectWidth
|
||||
});
|
||||
});
|
||||
this.groupActivities[key] = {
|
||||
heading: key,
|
||||
@@ -365,28 +450,32 @@ export default {
|
||||
};
|
||||
});
|
||||
},
|
||||
getActivityDisplayText(context, text, activityNameFitsRect) {
|
||||
//TODO: If the activity start is less than viewBounds.start then the text should be cropped on the left/should be off-screen)
|
||||
let words = text.split(' ');
|
||||
/**
|
||||
* Format the activity name to fit within the activity rect with a max of 2 lines
|
||||
* @param {CanvasRenderingContext2D} canvasContext
|
||||
* @param {string} activityName
|
||||
* @param {boolean} activityNameFitsRect
|
||||
*/
|
||||
getActivityDisplayText(canvasContext, activityName, activityNameFitsRect) {
|
||||
// TODO: If the activity start is less than viewBounds.start then the text should be cropped on the left/should be off-screen)
|
||||
let words = activityName.split(' ');
|
||||
let line = '';
|
||||
let activityText = [];
|
||||
let rows = 1;
|
||||
let activityLines = [];
|
||||
|
||||
for (let n = 0; (n < words.length) && (rows <= 2); n++) {
|
||||
let testLine = line + words[n] + ' ';
|
||||
let metrics = context.measureText(testLine);
|
||||
let testWidth = metrics.width;
|
||||
if (!activityNameFitsRect && (testWidth > MAX_TEXT_WIDTH && n > 0)) {
|
||||
activityText.push(line);
|
||||
for (let n = 0; (n < words.length) && (activityLines.length <= 2); n++) {
|
||||
let tempLine = line + words[n] + ' ';
|
||||
let textMetrics = canvasContext.measureText(tempLine);
|
||||
const textWidth = textMetrics.width;
|
||||
if (!activityNameFitsRect && (textWidth > MAX_TEXT_WIDTH && n > 0)) {
|
||||
activityLines.push(line);
|
||||
line = words[n] + ' ';
|
||||
testLine = line + words[n] + ' ';
|
||||
rows = rows + 1;
|
||||
tempLine = line + words[n] + ' ';
|
||||
}
|
||||
|
||||
line = testLine;
|
||||
line = tempLine;
|
||||
}
|
||||
|
||||
return activityText.length ? activityText : [line];
|
||||
return activityLines.length ? activityLines : [line];
|
||||
},
|
||||
getGroupContainer(activityRows, heading) {
|
||||
let svgHeight = 30;
|
||||
@@ -418,7 +507,24 @@ export default {
|
||||
width: svgWidth
|
||||
};
|
||||
},
|
||||
template: `<swim-lane :is-nested="isNested" :status="status"><template slot="label">{{heading}}</template><template slot="object"><svg :height="height" :width="width"></svg></template></swim-lane>`
|
||||
template: `
|
||||
<swim-lane
|
||||
:is-nested="isNested"
|
||||
:status="status"
|
||||
>
|
||||
<template slot="label">
|
||||
{{heading}}
|
||||
</template>
|
||||
<template slot="object">
|
||||
<svg
|
||||
:height="height"
|
||||
:width="width"
|
||||
:viewBox="'0 0 ' + width + ' ' + height"
|
||||
>
|
||||
</svg>
|
||||
</template>
|
||||
</swim-lane>
|
||||
`
|
||||
});
|
||||
|
||||
this.$refs.planHolder.appendChild(component.$mount().$el);
|
||||
@@ -432,7 +538,6 @@ export default {
|
||||
};
|
||||
},
|
||||
drawPlan() {
|
||||
|
||||
Object.keys(this.groupActivities).forEach((group, index) => {
|
||||
const activitiesByRow = this.groupActivities[group].activitiesByRow;
|
||||
const heading = this.groupActivities[group].heading;
|
||||
@@ -454,7 +559,7 @@ export default {
|
||||
});
|
||||
},
|
||||
plotNoItems(svgElement) {
|
||||
let textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
const textElement = document.createElementNS(SVG_NAMESPACE, 'text');
|
||||
this.setNSAttributesForElement(textElement, {
|
||||
x: "10",
|
||||
y: "20",
|
||||
@@ -464,6 +569,10 @@ export default {
|
||||
|
||||
svgElement.appendChild(textElement);
|
||||
},
|
||||
/**
|
||||
* @param {Element} element
|
||||
* @param {Object} attributes
|
||||
*/
|
||||
setNSAttributesForElement(element, attributes) {
|
||||
Object.keys(attributes).forEach((key) => {
|
||||
element.setAttributeNS(null, key, attributes[key]);
|
||||
@@ -474,7 +583,7 @@ export default {
|
||||
},
|
||||
// Experimental for now - unused
|
||||
addForeignElement(svgElement, label, x, y) {
|
||||
let foreign = document.createElementNS('http://www.w3.org/2000/svg', "foreignObject");
|
||||
let foreign = document.createElementNS(SVG_NAMESPACE, "foreignObject");
|
||||
this.setNSAttributesForElement(foreign, {
|
||||
width: String(MAX_TEXT_WIDTH),
|
||||
height: String(LINE_HEIGHT * 2),
|
||||
@@ -490,27 +599,33 @@ export default {
|
||||
|
||||
svgElement.appendChild(foreign);
|
||||
},
|
||||
// TODO: Clean up, extract HTML element creation into utility functions
|
||||
plotActivity(item, row, svgElement) {
|
||||
const activity = item.activity;
|
||||
let width = item.rectWidth;
|
||||
let rectElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
const rectElement = document.createElementNS(SVG_NAMESPACE, 'rect');
|
||||
const width = Math.max(Math.round(item.rectWidth), MIN_ACTIVITY_WIDTH);
|
||||
const clipUuid = uuid();
|
||||
|
||||
if (item.activity.exceeds.start) {
|
||||
width = width + EDGE_ROUNDING;
|
||||
if (this.clipActivityNames) {
|
||||
const clipPathElement = document.createElementNS(SVG_NAMESPACE, 'clipPath');
|
||||
this.setNSAttributesForElement(clipPathElement, {
|
||||
id: `clip-${clipUuid}`
|
||||
});
|
||||
svgElement.appendChild(clipPathElement);
|
||||
let clipRectElement = document.createElementNS(SVG_NAMESPACE, 'rect');
|
||||
this.setNSAttributesForElement(clipRectElement, {
|
||||
x: Math.round(item.start),
|
||||
y: row,
|
||||
width: width,
|
||||
height: String(ROW_HEIGHT)
|
||||
});
|
||||
clipPathElement.appendChild(clipRectElement);
|
||||
}
|
||||
|
||||
if (item.activity.exceeds.end) {
|
||||
width = width + EDGE_ROUNDING;
|
||||
}
|
||||
|
||||
width = Math.max(width, 1); // Set width to a minimum of 1
|
||||
|
||||
// rx: don't round corners if the width of the rect is smaller than the rounding radius
|
||||
this.setNSAttributesForElement(rectElement, {
|
||||
class: 'activity-bounds',
|
||||
x: item.activity.exceeds.start ? item.start - EDGE_ROUNDING : item.start,
|
||||
x: Math.round(item.start),
|
||||
y: row,
|
||||
rx: (width < EDGE_ROUNDING * 2) ? 0 : EDGE_ROUNDING,
|
||||
width: width,
|
||||
height: String(ROW_HEIGHT),
|
||||
fill: activity.color
|
||||
@@ -518,12 +633,13 @@ export default {
|
||||
|
||||
rectElement.addEventListener('click', (event) => {
|
||||
this.setSelectionForActivity(event.currentTarget, activity, event.metaKey);
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
svgElement.appendChild(rectElement);
|
||||
|
||||
item.textLines.forEach((line, index) => {
|
||||
let textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
let textElement = document.createElementNS(SVG_NAMESPACE, 'text');
|
||||
this.setNSAttributesForElement(textElement, {
|
||||
class: `activity-label ${item.textClass}`,
|
||||
x: item.textStart,
|
||||
@@ -531,10 +647,17 @@ export default {
|
||||
fill: activity.textColor
|
||||
});
|
||||
|
||||
if (this.clipActivityNames) {
|
||||
this.setNSAttributesForElement(textElement, {
|
||||
'clip-path': `url(#clip-${clipUuid})`
|
||||
});
|
||||
}
|
||||
|
||||
const textNode = document.createTextNode(line);
|
||||
textElement.appendChild(textNode);
|
||||
textElement.addEventListener('click', (event) => {
|
||||
this.setSelectionForActivity(event.currentTarget, activity, event.metaKey);
|
||||
event.stopPropagation();
|
||||
});
|
||||
svgElement.appendChild(textElement);
|
||||
});
|
||||
@@ -566,6 +689,7 @@ export default {
|
||||
setSelectionForActivity(element, activity, multiSelect) {
|
||||
this.openmct.selection.select([{
|
||||
element: element,
|
||||
// activity: activity,
|
||||
context: {
|
||||
type: 'activity',
|
||||
activity: activity
|
||||
@@ -573,11 +697,11 @@ export default {
|
||||
}, {
|
||||
element: this.openmct.layout.$refs.browseObject.$el,
|
||||
context: {
|
||||
// activity: activity,
|
||||
item: this.domainObject,
|
||||
supportsMultiSelect: true
|
||||
}
|
||||
}], multiSelect);
|
||||
event.stopPropagation();
|
||||
},
|
||||
|
||||
setStatus(status) {
|
||||
|
||||
@@ -35,11 +35,11 @@ export default function PlanViewProvider(openmct) {
|
||||
name: 'Plan',
|
||||
cssClass: 'icon-plan',
|
||||
canView(domainObject) {
|
||||
return domainObject.type === 'plan';
|
||||
return domainObject.type === 'plan' || domainObject.type === 'gantt-chart';
|
||||
},
|
||||
|
||||
canEdit(domainObject) {
|
||||
return false;
|
||||
return domainObject.type === 'gantt-chart';
|
||||
},
|
||||
|
||||
view: function (domainObject, objectPath) {
|
||||
@@ -57,7 +57,8 @@ export default function PlanViewProvider(openmct) {
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
path: objectPath
|
||||
path: objectPath,
|
||||
composition: openmct.composition.get(domainObject)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -54,6 +54,9 @@ export default function PlanInspectorViewProvider(openmct) {
|
||||
template: '<plan-activities-view></plan-activities-view>'
|
||||
});
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
@@ -61,9 +64,6 @@ export default function PlanInspectorViewProvider(openmct) {
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,12 +22,27 @@
|
||||
|
||||
import PlanViewProvider from './PlanViewProvider';
|
||||
import PlanInspectorViewProvider from "./inspector/PlanInspectorViewProvider";
|
||||
import ganttChartCompositionPolicy from './GanttChartCompositionPolicy';
|
||||
|
||||
export default function (configuration) {
|
||||
return function install(openmct) {
|
||||
openmct.types.addType('plan', {
|
||||
name: 'Plan',
|
||||
key: 'plan',
|
||||
description: 'A non-configurable timeline-like view for a compatible mission plan file.',
|
||||
creatable: false,
|
||||
cssClass: 'icon-plan',
|
||||
form: [],
|
||||
initialize: function (domainObject) {
|
||||
domainObject.configuration = {
|
||||
clipActivityNames: false
|
||||
};
|
||||
}
|
||||
});
|
||||
// Name TBD and subject to change
|
||||
openmct.types.addType('gantt-chart', {
|
||||
name: 'Gantt Chart',
|
||||
key: 'gantt-chart',
|
||||
description: 'A configurable timeline-like view for a compatible mission plan file.',
|
||||
creatable: true,
|
||||
cssClass: 'icon-plan',
|
||||
@@ -36,19 +51,34 @@ export default function (configuration) {
|
||||
name: 'Upload Plan (JSON File)',
|
||||
key: 'selectFile',
|
||||
control: 'file-input',
|
||||
required: true,
|
||||
required: false,
|
||||
text: 'Select File...',
|
||||
type: 'application/json',
|
||||
property: [
|
||||
"selectFile"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Clip Activity Names',
|
||||
key: 'clipActivityNames',
|
||||
control: 'toggleSwitch',
|
||||
cssClass: 'l-input',
|
||||
property: [
|
||||
"configuration",
|
||||
"clipActivityNames"
|
||||
]
|
||||
}
|
||||
],
|
||||
initialize: function (domainObject) {
|
||||
initialize(domainObject) {
|
||||
domainObject.configuration = {
|
||||
clipActivityNames: true
|
||||
};
|
||||
domainObject.composition = [];
|
||||
}
|
||||
});
|
||||
openmct.objectViews.addProvider(new PlanViewProvider(openmct));
|
||||
openmct.inspectorViews.addProvider(new PlanInspectorViewProvider(openmct));
|
||||
openmct.composition.addPolicy(ganttChartCompositionPolicy(openmct));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import Properties from "../inspectorViews/properties/Properties.vue";
|
||||
|
||||
describe('the plugin', function () {
|
||||
let planDefinition;
|
||||
let ganttDefinition;
|
||||
let element;
|
||||
let child;
|
||||
let openmct;
|
||||
@@ -50,6 +51,7 @@ describe('the plugin', function () {
|
||||
openmct.install(new PlanPlugin());
|
||||
|
||||
planDefinition = openmct.types.get('plan').definition;
|
||||
ganttDefinition = openmct.types.get('gantt-chart').definition;
|
||||
|
||||
element = document.createElement('div');
|
||||
element.style.width = '640px';
|
||||
@@ -74,15 +76,30 @@ describe('the plugin', function () {
|
||||
let mockPlanObject = {
|
||||
name: 'Plan',
|
||||
key: 'plan',
|
||||
creatable: false
|
||||
};
|
||||
|
||||
let mockGanttObject = {
|
||||
name: 'Gantt',
|
||||
key: 'gantt-chart',
|
||||
creatable: true
|
||||
};
|
||||
|
||||
it('defines a plan object type with the correct key', () => {
|
||||
expect(planDefinition.key).toEqual(mockPlanObject.key);
|
||||
describe('the plan type', () => {
|
||||
it('defines a plan object type with the correct key', () => {
|
||||
expect(planDefinition.key).toEqual(mockPlanObject.key);
|
||||
});
|
||||
it('is not creatable', () => {
|
||||
expect(planDefinition.creatable).toEqual(mockPlanObject.creatable);
|
||||
});
|
||||
});
|
||||
|
||||
it('is creatable', () => {
|
||||
expect(planDefinition.creatable).toEqual(mockPlanObject.creatable);
|
||||
describe('the gantt-chart type', () => {
|
||||
it('defines a gantt-chart object type with the correct key', () => {
|
||||
expect(ganttDefinition.key).toEqual(mockGanttObject.key);
|
||||
});
|
||||
it('is creatable', () => {
|
||||
expect(ganttDefinition.creatable).toEqual(mockGanttObject.creatable);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the plan view', () => {
|
||||
@@ -107,7 +124,7 @@ describe('the plugin', function () {
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testViewObject, [testViewObject]);
|
||||
let planView = applicableViews.find((viewProvider) => viewProvider.key === 'plan.view');
|
||||
expect(planView.canEdit()).toBeFalse();
|
||||
expect(planView.canEdit(testViewObject)).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -179,10 +196,10 @@ describe('the plugin', function () {
|
||||
|
||||
it('displays the group label', () => {
|
||||
const labelEl = element.querySelector('.c-plan__contents .c-object-label .c-object-label__name');
|
||||
expect(labelEl.innerHTML).toEqual('TEST-GROUP');
|
||||
expect(labelEl.innerHTML).toMatch(/TEST-GROUP/);
|
||||
});
|
||||
|
||||
it('displays the activities and their labels', (done) => {
|
||||
it('displays the activities and their labels', async () => {
|
||||
const bounds = {
|
||||
start: 1597160002854,
|
||||
end: 1597181232854
|
||||
@@ -190,27 +207,22 @@ describe('the plugin', function () {
|
||||
|
||||
openmct.time.bounds(bounds);
|
||||
|
||||
Vue.nextTick(() => {
|
||||
const rectEls = element.querySelectorAll('.c-plan__contents rect');
|
||||
expect(rectEls.length).toEqual(2);
|
||||
const textEls = element.querySelectorAll('.c-plan__contents text');
|
||||
expect(textEls.length).toEqual(3);
|
||||
|
||||
done();
|
||||
});
|
||||
await Vue.nextTick();
|
||||
const rectEls = element.querySelectorAll('.c-plan__contents rect');
|
||||
expect(rectEls.length).toEqual(2);
|
||||
const textEls = element.querySelectorAll('.c-plan__contents text');
|
||||
expect(textEls.length).toEqual(3);
|
||||
});
|
||||
|
||||
it ('shows the status indicator when available', (done) => {
|
||||
it ('shows the status indicator when available', async () => {
|
||||
openmct.status.set({
|
||||
key: "test-object",
|
||||
namespace: ''
|
||||
}, 'draft');
|
||||
|
||||
Vue.nextTick(() => {
|
||||
const statusEl = element.querySelector('.c-plan__contents .is-status--draft');
|
||||
expect(statusEl).toBeDefined();
|
||||
done();
|
||||
});
|
||||
await Vue.nextTick();
|
||||
const statusEl = element.querySelector('.c-plan__contents .is-status--draft');
|
||||
expect(statusEl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,10 +236,12 @@ describe('the plugin', function () {
|
||||
key: 'test-plan',
|
||||
namespace: ''
|
||||
},
|
||||
created: 123456789,
|
||||
modified: 123456790,
|
||||
version: 'v1'
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
openmct.selection.select([{
|
||||
element: element,
|
||||
context: {
|
||||
@@ -241,19 +255,18 @@ describe('the plugin', function () {
|
||||
}
|
||||
}], false);
|
||||
|
||||
return Vue.nextTick().then(() => {
|
||||
let viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
Properties
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct
|
||||
},
|
||||
template: '<properties/>'
|
||||
});
|
||||
await Vue.nextTick();
|
||||
let viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
Properties
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct
|
||||
},
|
||||
template: '<properties/>'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -264,7 +277,6 @@ describe('the plugin', function () {
|
||||
it('provides an inspector view with the version information if available', () => {
|
||||
componentObject = component.$root.$children[0];
|
||||
const propertiesEls = componentObject.$el.querySelectorAll('.c-inspect-properties__row');
|
||||
expect(propertiesEls.length).toEqual(7);
|
||||
const found = Array.from(propertiesEls).some((propertyEl) => {
|
||||
return (propertyEl.children[0].innerHTML.trim() === 'Version'
|
||||
&& propertyEl.children[1].innerHTML.trim() === 'v1');
|
||||
|
||||
@@ -44,6 +44,9 @@ export default function PlotsInspectorViewProvider(openmct) {
|
||||
template: '<plot-options></plot-options>'
|
||||
});
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
@@ -51,9 +54,6 @@ export default function PlotsInspectorViewProvider(openmct) {
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ export default function StackedPlotsInspectorViewProvider(openmct) {
|
||||
template: '<plot-options></plot-options>'
|
||||
});
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
@@ -49,9 +52,6 @@ export default function StackedPlotsInspectorViewProvider(openmct) {
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ export default function TimeListInspectorViewProvider(openmct) {
|
||||
template: '<timelist-properties-view></timelist-properties-view>'
|
||||
});
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
},
|
||||
destroy: function () {
|
||||
if (component) {
|
||||
component.$destroy();
|
||||
@@ -62,9 +65,6 @@ export default function TimeListInspectorViewProvider(openmct) {
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return openmct.priority.HIGH + 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user