Compare commits
29 Commits
vue-conduc
...
vue-toolba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b828b68a3 | ||
|
|
76c21ba73a | ||
|
|
d35800a307 | ||
|
|
ebd266cbbb | ||
|
|
de900702a6 | ||
|
|
ae8202b90e | ||
|
|
84350d1776 | ||
|
|
88a23151ec | ||
|
|
1a60a2e13a | ||
|
|
6e81b985a6 | ||
|
|
0ee881f2d2 | ||
|
|
908b843d8b | ||
|
|
16df2567ed | ||
|
|
1aea27f9b6 | ||
|
|
0d1acfd4df | ||
|
|
13c984f3c1 | ||
|
|
f8aea00792 | ||
|
|
15c52fe097 | ||
|
|
1b4aa5cbbf | ||
|
|
11acc1cfe5 | ||
|
|
d4c2a44a96 | ||
|
|
7792396c46 | ||
|
|
984cf0ca1e | ||
|
|
792633c0d1 | ||
|
|
dfd33251f0 | ||
|
|
734d368c0b | ||
|
|
794098740e | ||
|
|
9c178a870d | ||
|
|
8b3a058b03 |
4
app.js
4
app.js
@@ -16,7 +16,7 @@ const request = require('request');
|
||||
|
||||
// Defaults
|
||||
options.port = options.port || options.p || 8080;
|
||||
options.host = options.host || options.h || 'localhost';
|
||||
options.host = options.host || options.h || 'localhost'
|
||||
options.directory = options.directory || options.D || '.';
|
||||
|
||||
// Show command line options
|
||||
@@ -46,7 +46,7 @@ webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||
webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) });
|
||||
|
||||
webpackConfig.entry.openmct = [
|
||||
'webpack-hot-middleware/client',
|
||||
'webpack-hot-middleware/client?reload=true',
|
||||
webpackConfig.entry.openmct
|
||||
];
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
}));
|
||||
openmct.install(openmct.plugins.SummaryWidget());
|
||||
openmct.install(openmct.plugins.Notebook());
|
||||
openmct.install(openmct.plugins.FolderView());
|
||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.start();
|
||||
|
||||
@@ -56,7 +56,7 @@ define([
|
||||
};
|
||||
|
||||
DurationFormat.prototype.validate = function (text) {
|
||||
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||
return moment.utc(text, DATE_FORMATS).isValid();
|
||||
};
|
||||
|
||||
return DurationFormat;
|
||||
|
||||
@@ -29,7 +29,6 @@ define([
|
||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
||||
DATE_FORMATS = [
|
||||
DATE_FORMAT,
|
||||
DATE_FORMAT + "Z",
|
||||
"YYYY-MM-DD HH:mm:ss",
|
||||
"YYYY-MM-DD HH:mm",
|
||||
"YYYY-MM-DD"
|
||||
@@ -53,14 +52,70 @@ define([
|
||||
this.key = "utc";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriate time format based on the provided value and
|
||||
* the threshold required.
|
||||
* @private
|
||||
*/
|
||||
function getScaledFormat(d) {
|
||||
var momentified = moment.utc(d);
|
||||
/**
|
||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
||||
*
|
||||
* Licensed
|
||||
*/
|
||||
var format = [
|
||||
[".SSS", function (m) {
|
||||
return m.milliseconds();
|
||||
}],
|
||||
[":ss", function (m) {
|
||||
return m.seconds();
|
||||
}],
|
||||
["HH:mm", function (m) {
|
||||
return m.minutes();
|
||||
}],
|
||||
["HH", function (m) {
|
||||
return m.hours();
|
||||
}],
|
||||
["ddd DD", function (m) {
|
||||
return m.days() &&
|
||||
m.date() !== 1;
|
||||
}],
|
||||
["MMM DD", function (m) {
|
||||
return m.date() !== 1;
|
||||
}],
|
||||
["MMMM", function (m) {
|
||||
return m.month();
|
||||
}],
|
||||
["YYYY", function () {
|
||||
return true;
|
||||
}]
|
||||
].filter(function (row) {
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
|
||||
if (format !== undefined) {
|
||||
return moment.utc(d).format(format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value The value to format.
|
||||
* @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor
|
||||
* and plot axes. Specifies the smallest number on the scale.
|
||||
* @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor
|
||||
* and plot axes. Specifies the largest number on the scale
|
||||
* @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor
|
||||
* and plot axes. The number of labels on the scale.
|
||||
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||
* in the array.
|
||||
*/
|
||||
UTCTimeFormat.prototype.format = function (value) {
|
||||
if (value !== undefined) {
|
||||
if (arguments.length > 1) {
|
||||
return getScaledFormat(value);
|
||||
} else if (value !== undefined) {
|
||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||
} else {
|
||||
return value;
|
||||
@@ -75,7 +130,7 @@ define([
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.validate = function (text) {
|
||||
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||
return moment.utc(text, DATE_FORMATS).isValid();
|
||||
};
|
||||
|
||||
return UTCTimeFormat;
|
||||
|
||||
62
platform/commonUI/formats/test/UTCTimeFormatSpec.js
Normal file
62
platform/commonUI/formats/test/UTCTimeFormatSpec.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"../src/UTCTimeFormat",
|
||||
"moment"
|
||||
], function (
|
||||
UTCTimeFormat,
|
||||
moment
|
||||
) {
|
||||
describe("The UTCTimeFormat class", function () {
|
||||
var format;
|
||||
var scale;
|
||||
|
||||
beforeEach(function () {
|
||||
format = new UTCTimeFormat();
|
||||
scale = {min: 0, max: 0};
|
||||
});
|
||||
|
||||
it("Provides an appropriately scaled time format based on the input" +
|
||||
" time", function () {
|
||||
var TWO_HUNDRED_MS = 200;
|
||||
var THREE_SECONDS = 3000;
|
||||
var FIVE_MINUTES = 5 * 60 * 1000;
|
||||
var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000);
|
||||
var TEN_HOURS = (10 * 60 * 60 * 1000);
|
||||
|
||||
var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD");
|
||||
var APRIL = moment.utc("2016-04", "YYYY-MM");
|
||||
var TWENTY_SIXTEEN = moment.utc("2016", "YYYY");
|
||||
|
||||
expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200");
|
||||
expect(format.format(THREE_SECONDS, scale)).toBe(":03");
|
||||
expect(format.format(FIVE_MINUTES, scale)).toBe("00:05");
|
||||
expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20");
|
||||
expect(format.format(TEN_HOURS, scale)).toBe("10");
|
||||
|
||||
expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03");
|
||||
expect(format.format(APRIL, scale)).toBe("April");
|
||||
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
|
||||
});
|
||||
});
|
||||
});
|
||||
43
platform/features/conductor/compatibility/bundle.js
Normal file
43
platform/features/conductor/compatibility/bundle.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/ConductorRepresenter",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
ConductorRepresenter,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
legacyRegistry.register("platform/features/conductor/compatibility", {
|
||||
"extensions": {
|
||||
"representers": [
|
||||
{
|
||||
"implementation": ConductorRepresenter,
|
||||
"depends": [
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Representer that provides a compatibility layer between the new
|
||||
* time conductor and existing representations / views. Listens to
|
||||
* the v2 time conductor API and generates v1 style events using the
|
||||
* Angular event bus. This is transitional code code and will be
|
||||
* removed.
|
||||
*
|
||||
* Deprecated immediately as this is temporary code
|
||||
*
|
||||
* @deprecated
|
||||
* @constructor
|
||||
*/
|
||||
function ConductorRepresenter(
|
||||
openmct,
|
||||
scope,
|
||||
element
|
||||
) {
|
||||
this.timeAPI = openmct.time;
|
||||
this.scope = scope;
|
||||
this.element = element;
|
||||
|
||||
this.boundsListener = this.boundsListener.bind(this);
|
||||
this.timeSystemListener = this.timeSystemListener.bind(this);
|
||||
this.followListener = this.followListener.bind(this);
|
||||
}
|
||||
|
||||
ConductorRepresenter.prototype.boundsListener = function (bounds) {
|
||||
var timeSystem = this.timeAPI.timeSystem();
|
||||
this.scope.$broadcast('telemetry:display:bounds', {
|
||||
start: bounds.start,
|
||||
end: bounds.end,
|
||||
domain: timeSystem.key
|
||||
}, this.timeAPI.clock() !== undefined);
|
||||
};
|
||||
|
||||
ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) {
|
||||
var bounds = this.timeAPI.bounds();
|
||||
this.scope.$broadcast('telemetry:display:bounds', {
|
||||
start: bounds.start,
|
||||
end: bounds.end,
|
||||
domain: timeSystem.key
|
||||
}, this.timeAPI.clock() !== undefined);
|
||||
};
|
||||
|
||||
ConductorRepresenter.prototype.followListener = function () {
|
||||
this.boundsListener(this.timeAPI.bounds());
|
||||
};
|
||||
|
||||
// Handle a specific representation of a specific domain object
|
||||
ConductorRepresenter.prototype.represent = function represent(representation) {
|
||||
if (representation.key === 'browse-object') {
|
||||
this.destroy();
|
||||
|
||||
this.timeAPI.on("bounds", this.boundsListener);
|
||||
this.timeAPI.on("timeSystem", this.timeSystemListener);
|
||||
this.timeAPI.on("follow", this.followListener);
|
||||
}
|
||||
};
|
||||
|
||||
ConductorRepresenter.prototype.destroy = function destroy() {
|
||||
this.timeAPI.off("bounds", this.boundsListener);
|
||||
this.timeAPI.off("timeSystem", this.timeSystemListener);
|
||||
this.timeAPI.off("follow", this.followListener);
|
||||
};
|
||||
|
||||
return ConductorRepresenter;
|
||||
}
|
||||
);
|
||||
|
||||
148
platform/features/conductor/core/bundle.js
Normal file
148
platform/features/conductor/core/bundle.js
Normal file
@@ -0,0 +1,148 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/ui/TimeConductorController",
|
||||
"./src/ui/ConductorAxisController",
|
||||
"./src/ui/ConductorTOIController",
|
||||
"./src/ui/ConductorTOIDirective",
|
||||
"./src/ui/TimeOfInterestController",
|
||||
"./src/ui/ConductorAxisDirective",
|
||||
"./src/ui/NumberFormat",
|
||||
"./src/ui/StringFormat",
|
||||
"./res/templates/time-conductor.html",
|
||||
"./res/templates/mode-selector/mode-selector.html",
|
||||
"./res/templates/mode-selector/mode-menu.html",
|
||||
"./res/templates/time-of-interest.html",
|
||||
"legacyRegistry"
|
||||
], function (
|
||||
TimeConductorController,
|
||||
ConductorAxisController,
|
||||
ConductorTOIController,
|
||||
ConductorTOIDirective,
|
||||
TimeOfInterestController,
|
||||
ConductorAxisDirective,
|
||||
NumberFormat,
|
||||
StringFormat,
|
||||
timeConductorTemplate,
|
||||
modeSelectorTemplate,
|
||||
modeMenuTemplate,
|
||||
timeOfInterest,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
legacyRegistry.register("platform/features/conductor/core", {
|
||||
"extensions": {
|
||||
"controllers": [
|
||||
{
|
||||
"key": "TimeConductorController",
|
||||
"implementation": TimeConductorController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$window",
|
||||
"openmct",
|
||||
"formatService",
|
||||
"CONDUCTOR_CONFIG"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ConductorTOIController",
|
||||
"implementation": ConductorTOIController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"formatService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "TimeOfInterestController",
|
||||
"implementation": TimeOfInterestController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"formatService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "conductorAxis",
|
||||
"implementation": ConductorAxisDirective,
|
||||
"depends": [
|
||||
"openmct",
|
||||
"formatService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "conductorToi",
|
||||
"implementation": ConductorTOIDirective
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
"key": "conductor",
|
||||
"template": timeConductorTemplate
|
||||
},
|
||||
{
|
||||
"key": "mode-menu",
|
||||
"template": modeMenuTemplate
|
||||
},
|
||||
{
|
||||
"key": "mode-selector",
|
||||
"template": modeSelectorTemplate
|
||||
},
|
||||
{
|
||||
"key": "time-of-interest",
|
||||
"template": timeOfInterest
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
{
|
||||
"key": "time-conductor",
|
||||
"template": timeConductorTemplate
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "D3: Data-Driven Documents",
|
||||
"version": "4.1.0",
|
||||
"author": "Mike Bostock",
|
||||
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
|
||||
"website": "https://d3js.org/",
|
||||
"copyright": "Copyright 2010-2016 Mike Bostock",
|
||||
"license": "BSD-3-Clause",
|
||||
"link": "https://github.com/d3/d3/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"formats": [
|
||||
{
|
||||
"key": "number",
|
||||
"implementation": NumberFormat
|
||||
},
|
||||
{
|
||||
"key": "string",
|
||||
"implementation": StringFormat
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
<!--
|
||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT Web 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 Web 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.
|
||||
-->
|
||||
<div class="w-menu">
|
||||
<div class="col menu-items">
|
||||
<ul>
|
||||
<li ng-repeat="metadata in ngModel.options"
|
||||
ng-click="ngModel.select(metadata)">
|
||||
<a ng-mouseover="ngModel.activeMetadata = metadata"
|
||||
ng-mouseleave="ngModel.activeMetadata = undefined"
|
||||
class="menu-item-a {{metadata.cssClass}}">
|
||||
{{metadata.name}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col menu-item-description">
|
||||
<div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
|
||||
<div class="w-title-desc">
|
||||
<div class="desc-area title">
|
||||
{{ngModel.activeMetadata.name}}
|
||||
</div>
|
||||
<div class="desc-area description">
|
||||
{{ngModel.activeMetadata.description}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT Web 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 Web 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.
|
||||
-->
|
||||
<span ng-controller="ClickAwayController as modeController">
|
||||
<div class="s-menu-button"
|
||||
ng-click="modeController.toggle()">
|
||||
<span class="title-label">{{ngModel.selected.name}}</span>
|
||||
</div>
|
||||
<div class="menu super-menu mini l-mode-selector-menu"
|
||||
ng-show="modeController.isActive()">
|
||||
<mct-include key="'mode-menu'"
|
||||
ng-model="ngModel">
|
||||
</mct-include>
|
||||
</div>
|
||||
</span>
|
||||
@@ -0,0 +1,117 @@
|
||||
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
|
||||
<div ng-controller="TimeConductorController as tcController"
|
||||
class="holder grows flex-elem l-flex-row l-time-conductor {{tcController.isFixed ? 'fixed-mode' : 'realtime-mode'}} {{timeSystemModel.selected.metadata.key}}-time-system"
|
||||
ng-class="{'status-panning': tcController.panning}">
|
||||
<div class="flex-elem holder time-conductor-icon">
|
||||
<div class="hand-little"></div>
|
||||
<div class="hand-big"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
|
||||
<!-- Holds inputs and ticks -->
|
||||
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
|
||||
<form class="l-time-conductor-inputs-holder"
|
||||
ng-submit="tcController.isFixed ? tcController.setBoundsFromView(boundsModel) : tcController.setOffsetsFromView(boundsModel)">
|
||||
<span class="l-time-range-w start-w">
|
||||
<span class="l-time-conductor-inputs">
|
||||
<span class="l-time-range-input-w start-date">
|
||||
<span class="title"></span>
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: timeSystemModel.format,
|
||||
validate: tcController.validation.validateStart
|
||||
}"
|
||||
ng-model="boundsModel"
|
||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
||||
field="'start'"
|
||||
class="time-range-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
<span class="l-time-range-input-w time-delta start-delta"
|
||||
ng-class="{'hide':tcController.isFixed}">
|
||||
-
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: timeSystemModel.durationFormat,
|
||||
validate: tcController.validation.validateStartOffset
|
||||
}"
|
||||
ng-model="boundsModel"
|
||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
||||
field="'startOffset'"
|
||||
class="s-input-inline hrs-min-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="l-time-range-w end-w">
|
||||
<span class="l-time-conductor-inputs">
|
||||
<span class="l-time-range-input-w end-date"
|
||||
ng-controller="ToggleController as t2">
|
||||
<span class="title"></span>
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: timeSystemModel.format,
|
||||
validate: tcController.validation.validateEnd
|
||||
}"
|
||||
ng-model="boundsModel"
|
||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
||||
ng-disabled="!tcController.isFixed"
|
||||
field="'end'"
|
||||
class="time-range-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
<span class="l-time-range-input-w time-delta end-delta"
|
||||
ng-class="{'hide': tcController.isFixed}">
|
||||
+
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: timeSystemModel.durationFormat,
|
||||
validate: tcController.validation.validateEndOffset
|
||||
}"
|
||||
ng-model="boundsModel"
|
||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
||||
field="'endOffset'"
|
||||
class="s-input-inline hrs-min-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<input type="submit" class="invisible">
|
||||
</form>
|
||||
<conductor-axis class="mobile-hide" view-service="tcController.conductorViewService"></conductor-axis>
|
||||
</div>
|
||||
|
||||
<!-- Holds time system and session selectors, and zoom control -->
|
||||
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
|
||||
<mct-include
|
||||
key="'mode-selector'"
|
||||
ng-model="tcController.menu"
|
||||
class="holder flex-elem menus-up mode-selector">
|
||||
</mct-include>
|
||||
<mct-control
|
||||
key="'menu-button'"
|
||||
class="holder flex-elem menus-up time-system"
|
||||
structure="{
|
||||
text: timeSystemModel.selected.name,
|
||||
click: tcController.setTimeSystemFromView,
|
||||
options: tcController.timeSystemsForClocks[tcController.menu.selected.key]
|
||||
}">
|
||||
</mct-control>
|
||||
<!-- Zoom control -->
|
||||
<div ng-if="tcController.zoom"
|
||||
class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
|
||||
{{currentZoom}}
|
||||
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
|
||||
<input class="time-conductor-zoom flex-elem" type="range"
|
||||
ng-model="tcController.currentZoom"
|
||||
ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)"
|
||||
ng-change="tcController.onZoom(tcController.currentZoom)"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
max="0.99" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="abs angular-controller"
|
||||
ng-controller="TimeOfInterestController as toi">
|
||||
<div class="l-flex-row l-toi">
|
||||
<span class="flex-elem l-flex-row l-toi-buttons">
|
||||
<a class="flex-elem t-button-resync icon-button" title="Re-sync Time of Interest"
|
||||
ng-click="toi.resync()"></a>
|
||||
<a class="flex-elem t-button-unpin icon-button" title="Unset Time of Interest"
|
||||
ng-click="toi.dismiss()"></a>
|
||||
</span>
|
||||
<span class="flex-elem l-toi-val">{{toi.toiText}}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,236 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
"d3-selection",
|
||||
"d3-scale",
|
||||
"d3-axis"
|
||||
],
|
||||
function (d3Selection, d3Scale, d3Axis) {
|
||||
var PADDING = 1;
|
||||
|
||||
/**
|
||||
* Controller that renders a horizontal time scale spanning the current bounds defined in the time conductor.
|
||||
* Used by the mct-conductor-axis directive
|
||||
* @constructor
|
||||
*/
|
||||
function ConductorAxisController(openmct, formatService, scope, element) {
|
||||
// Dependencies
|
||||
this.formatService = formatService;
|
||||
this.timeAPI = openmct.time;
|
||||
|
||||
this.scope = scope;
|
||||
|
||||
this.bounds = this.timeAPI.bounds();
|
||||
|
||||
//Bind all class functions to 'this'
|
||||
Object.keys(ConductorAxisController.prototype).filter(function (key) {
|
||||
return typeof ConductorAxisController.prototype[key] === 'function';
|
||||
}).forEach(function (key) {
|
||||
this[key] = ConductorAxisController.prototype[key].bind(this);
|
||||
}.bind(this));
|
||||
|
||||
this.initialize(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ConductorAxisController.prototype.destroy = function () {
|
||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
||||
this.timeAPI.off('bounds', this.changeBounds);
|
||||
this.viewService.off("zoom", this.onZoom);
|
||||
this.viewService.off("zoom-stop", this.onZoomStop);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ConductorAxisController.prototype.initialize = function (element) {
|
||||
this.target = element[0].firstChild;
|
||||
var height = this.target.offsetHeight;
|
||||
var vis = d3Selection.select(this.target)
|
||||
.append("svg:svg")
|
||||
.attr("width", "100%")
|
||||
.attr("height", height);
|
||||
|
||||
this.xAxis = d3Axis.axisTop();
|
||||
|
||||
// draw x axis with labels. CSS is used to position them.
|
||||
this.axisElement = vis.append("g");
|
||||
|
||||
if (this.timeAPI.timeSystem() !== undefined) {
|
||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
||||
this.setScale();
|
||||
}
|
||||
|
||||
//Respond to changes in conductor
|
||||
this.timeAPI.on("timeSystem", this.changeTimeSystem);
|
||||
this.timeAPI.on("bounds", this.changeBounds);
|
||||
|
||||
this.scope.$on("$destroy", this.destroy);
|
||||
|
||||
this.viewService.on("zoom", this.onZoom);
|
||||
this.viewService.on("zoom-stop", this.onZoomStop);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ConductorAxisController.prototype.changeBounds = function (bounds) {
|
||||
this.bounds = bounds;
|
||||
if (!this.zooming) {
|
||||
this.setScale();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the scale of the axis, based on current conductor bounds.
|
||||
*/
|
||||
ConductorAxisController.prototype.setScale = function () {
|
||||
var width = this.target.offsetWidth;
|
||||
var timeSystem = this.timeAPI.timeSystem();
|
||||
var bounds = this.bounds;
|
||||
|
||||
if (timeSystem.isUTCBased) {
|
||||
this.xScale = this.xScale || d3Scale.scaleUtc();
|
||||
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
||||
} else {
|
||||
this.xScale = this.xScale || d3Scale.scaleLinear();
|
||||
this.xScale.domain([bounds.start, bounds.end]);
|
||||
}
|
||||
|
||||
this.xAxis.scale(this.xScale);
|
||||
|
||||
this.xScale.range([PADDING, width - PADDING * 2]);
|
||||
this.axisElement.call(this.xAxis);
|
||||
|
||||
this.msPerPixel = (bounds.end - bounds.start) / width;
|
||||
};
|
||||
|
||||
/**
|
||||
* When the time system changes, update the scale and formatter used for showing times.
|
||||
* @param timeSystem
|
||||
*/
|
||||
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
|
||||
var key = timeSystem.timeFormat;
|
||||
if (key !== undefined) {
|
||||
var format = this.formatService.getFormat(key);
|
||||
var bounds = this.timeAPI.bounds();
|
||||
|
||||
//The D3 scale used depends on the type of time system as d3
|
||||
// supports UTC out of the box.
|
||||
if (timeSystem.isUTCBased) {
|
||||
this.xScale = d3Scale.scaleUtc();
|
||||
} else {
|
||||
this.xScale = d3Scale.scaleLinear();
|
||||
}
|
||||
|
||||
this.xAxis.scale(this.xScale);
|
||||
|
||||
//Define a custom format function
|
||||
this.xAxis.tickFormat(function (tickValue) {
|
||||
// Normalize date representations to numbers
|
||||
if (tickValue instanceof Date) {
|
||||
tickValue = tickValue.getTime();
|
||||
}
|
||||
return format.format(tickValue, {
|
||||
min: bounds.start,
|
||||
max: bounds.end
|
||||
});
|
||||
});
|
||||
this.axisElement.call(this.xAxis);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The user has stopped panning the time conductor scale element.
|
||||
* @event panStop
|
||||
*/
|
||||
/**
|
||||
* Called on release of mouse button after dragging the scale left or right.
|
||||
* @fires platform.features.conductor.ConductorAxisController~panStop
|
||||
*/
|
||||
ConductorAxisController.prototype.panStop = function () {
|
||||
//resync view bounds with time conductor bounds
|
||||
this.viewService.emit("pan-stop");
|
||||
this.timeAPI.bounds(this.bounds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rescales the axis when the user zooms. Although zoom ultimately results in a bounds change once the user
|
||||
* releases the zoom slider, dragging the slider will not immediately change the conductor bounds. It will
|
||||
* however immediately update the scale and the bounds displayed in the UI.
|
||||
* @private
|
||||
* @param {ZoomLevel}
|
||||
*/
|
||||
ConductorAxisController.prototype.onZoom = function (zoom) {
|
||||
this.zooming = true;
|
||||
|
||||
this.bounds = zoom.bounds;
|
||||
this.setScale();
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ConductorAxisController.prototype.onZoomStop = function (zoom) {
|
||||
this.zooming = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @event platform.features.conductor.ConductorAxisController~pan
|
||||
* Fired when the time conductor is panned
|
||||
*/
|
||||
/**
|
||||
* Initiate panning via a click + drag gesture on the time conductor
|
||||
* scale. Panning triggers a "pan" event
|
||||
* @param {number} delta the offset from the original click event
|
||||
* @see TimeConductorViewService#
|
||||
* @fires platform.features.conductor.ConductorAxisController~pan
|
||||
*/
|
||||
ConductorAxisController.prototype.pan = function (delta) {
|
||||
if (this.timeAPI.clock() === undefined) {
|
||||
var deltaInMs = delta[0] * this.msPerPixel;
|
||||
var bounds = this.timeAPI.bounds();
|
||||
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
|
||||
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
|
||||
this.bounds = {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
this.setScale();
|
||||
this.viewService.emit("pan", this.bounds);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoked on element resize. Will rebuild the scale based on the new dimensions of the element.
|
||||
*/
|
||||
ConductorAxisController.prototype.resize = function () {
|
||||
this.setScale();
|
||||
};
|
||||
|
||||
return ConductorAxisController;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,169 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./ConductorAxisController',
|
||||
'zepto',
|
||||
'd3-selection',
|
||||
'd3-scale'
|
||||
], function (
|
||||
ConductorAxisController,
|
||||
$,
|
||||
d3Selection,
|
||||
d3Scale
|
||||
) {
|
||||
describe("The ConductorAxisController", function () {
|
||||
var controller,
|
||||
mockConductor,
|
||||
mockConductorViewService,
|
||||
mockFormatService,
|
||||
mockScope,
|
||||
mockBounds,
|
||||
element,
|
||||
mockTimeSystem,
|
||||
mockFormat;
|
||||
|
||||
function getCallback(target, name) {
|
||||
return target.calls.all().filter(function (call) {
|
||||
return call.args[0] === name;
|
||||
})[0].args[1];
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj("scope", [
|
||||
"$on"
|
||||
]);
|
||||
|
||||
//Add some HTML elements
|
||||
mockBounds = {
|
||||
start: 100,
|
||||
end: 200
|
||||
};
|
||||
mockConductor = jasmine.createSpyObj("conductor", [
|
||||
"timeSystem",
|
||||
"bounds",
|
||||
"on",
|
||||
"off",
|
||||
"clock"
|
||||
]);
|
||||
mockConductor.bounds.and.returnValue(mockBounds);
|
||||
|
||||
mockFormatService = jasmine.createSpyObj("formatService", [
|
||||
"getFormat"
|
||||
]);
|
||||
|
||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
||||
"on",
|
||||
"off",
|
||||
"emit"
|
||||
]);
|
||||
|
||||
spyOn(d3Scale, 'scaleUtc').and.callThrough();
|
||||
spyOn(d3Scale, 'scaleLinear').and.callThrough();
|
||||
|
||||
element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>');
|
||||
$(document).find('body').append(element);
|
||||
ConductorAxisController.prototype.viewService = mockConductorViewService;
|
||||
controller = new ConductorAxisController({time: mockConductor}, mockFormatService, mockScope, element);
|
||||
|
||||
mockTimeSystem = {};
|
||||
mockFormat = jasmine.createSpyObj("format", [
|
||||
"format"
|
||||
]);
|
||||
|
||||
mockTimeSystem.timeFormat = "mockFormat";
|
||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
||||
mockConductor.timeSystem.and.returnValue(mockTimeSystem);
|
||||
mockTimeSystem.isUTCBased = false;
|
||||
});
|
||||
|
||||
it("listens for changes to time system and bounds", function () {
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
||||
});
|
||||
|
||||
it("on scope destruction, deregisters listeners", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
||||
controller.destroy();
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
||||
});
|
||||
|
||||
describe("when the time system changes", function () {
|
||||
it("uses a UTC scale for UTC time systems", function () {
|
||||
mockTimeSystem.isUTCBased = true;
|
||||
controller.changeTimeSystem(mockTimeSystem);
|
||||
|
||||
expect(d3Scale.scaleUtc).toHaveBeenCalled();
|
||||
expect(d3Scale.scaleLinear).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses a linear scale for non-UTC time systems", function () {
|
||||
mockTimeSystem.isUTCBased = false;
|
||||
controller.changeTimeSystem(mockTimeSystem);
|
||||
expect(d3Scale.scaleLinear).toHaveBeenCalled();
|
||||
expect(d3Scale.scaleUtc).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sets axis domain to time conductor bounds", function () {
|
||||
mockTimeSystem.isUTCBased = false;
|
||||
controller.setScale();
|
||||
expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]);
|
||||
});
|
||||
|
||||
it("uses the format specified by the time system to format tick" +
|
||||
" labels", function () {
|
||||
controller.changeTimeSystem(mockTimeSystem);
|
||||
expect(mockFormat.format).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('responds to zoom events', function () {
|
||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", controller.onZoom);
|
||||
var cb = getCallback(mockConductorViewService.on, "zoom");
|
||||
spyOn(controller, 'setScale').and.callThrough();
|
||||
cb({bounds: {start: 0, end: 100}});
|
||||
expect(controller.setScale).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('adjusts scale on pan', function () {
|
||||
spyOn(controller, 'setScale').and.callThrough();
|
||||
controller.pan(100);
|
||||
expect(controller.setScale).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits event on pan', function () {
|
||||
spyOn(controller, 'setScale').and.callThrough();
|
||||
controller.pan(100);
|
||||
expect(mockConductorViewService.emit).toHaveBeenCalledWith("pan", jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('cleans up listeners on destruction', function () {
|
||||
controller.destroy();
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
||||
|
||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", controller.onZoom);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./ConductorAxisController'], function (ConductorAxisController) {
|
||||
function ConductorAxisDirective() {
|
||||
/**
|
||||
* The mct-conductor-axis renders a horizontal axis with regular
|
||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
||||
* be specified as attributes.
|
||||
*/
|
||||
return {
|
||||
controller: [
|
||||
'openmct',
|
||||
'formatService',
|
||||
'$scope',
|
||||
'$element',
|
||||
ConductorAxisController
|
||||
],
|
||||
controllerAs: 'axis',
|
||||
scope: {
|
||||
viewService: "="
|
||||
},
|
||||
bindToController: true,
|
||||
|
||||
restrict: 'E',
|
||||
priority: 1000,
|
||||
|
||||
template: '<div class="l-axis-holder" ' +
|
||||
' mct-drag-down="axis.panStart()"' +
|
||||
' mct-drag-up="axis.panStop(delta)"' +
|
||||
' mct-drag="axis.pan(delta)"' +
|
||||
' mct-resize="axis.resize()"></div>'
|
||||
};
|
||||
}
|
||||
|
||||
return ConductorAxisDirective;
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["zepto"],
|
||||
function ($) {
|
||||
|
||||
/**
|
||||
* Controller for the Time of Interest indicator in the conductor itself. Sets the horizontal position of the
|
||||
* TOI indicator based on the current value of the TOI, and the width of the TOI conductor.
|
||||
* @memberof platform.features.conductor
|
||||
*/
|
||||
function ConductorTOIController($scope, openmct) {
|
||||
this.timeAPI = openmct.time;
|
||||
|
||||
//Bind all class functions to 'this'
|
||||
Object.keys(ConductorTOIController.prototype).filter(function (key) {
|
||||
return typeof ConductorTOIController.prototype[key] === 'function';
|
||||
}).forEach(function (key) {
|
||||
this[key] = ConductorTOIController.prototype[key].bind(this);
|
||||
}.bind(this));
|
||||
|
||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.viewService.on('zoom', this.setOffsetFromZoom);
|
||||
this.viewService.on('pan', this.setOffsetFromBounds);
|
||||
|
||||
var timeOfInterest = this.timeAPI.timeOfInterest();
|
||||
if (timeOfInterest) {
|
||||
this.changeTimeOfInterest(timeOfInterest);
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', this.destroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ConductorTOIController.prototype.destroy = function () {
|
||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.viewService.off('zoom', this.setOffsetFromZoom);
|
||||
this.viewService.off('pan', this.setOffsetFromBounds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given some bounds, set horizontal position of TOI indicator based
|
||||
* on current conductor TOI value. Bounds are provided so that
|
||||
* ephemeral bounds from zoom and pan events can be used as well
|
||||
* as current conductor bounds, allowing TOI to be updated in
|
||||
* realtime during scroll and zoom.
|
||||
* @param {TimeConductorBounds} bounds
|
||||
*/
|
||||
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
|
||||
var toi = this.timeAPI.timeOfInterest();
|
||||
if (toi !== undefined) {
|
||||
var offset = toi - bounds.start;
|
||||
var duration = bounds.end - bounds.start;
|
||||
this.left = offset / duration * 100;
|
||||
this.pinned = true;
|
||||
} else {
|
||||
this.left = 0;
|
||||
this.pinned = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ConductorTOIController.prototype.setOffsetFromZoom = function (zoom) {
|
||||
return this.setOffsetFromBounds(zoom.bounds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoked when time of interest changes. Will set the horizontal offset of the TOI indicator.
|
||||
* @private
|
||||
*/
|
||||
ConductorTOIController.prototype.changeTimeOfInterest = function () {
|
||||
var bounds = this.timeAPI.bounds();
|
||||
if (bounds) {
|
||||
this.setOffsetFromBounds(bounds);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* On a mouse click event within the TOI element, convert position within element to a time of interest, and
|
||||
* set the time of interest on the conductor.
|
||||
* @param e The angular $event object
|
||||
*/
|
||||
ConductorTOIController.prototype.setTOIFromPosition = function (e) {
|
||||
//TOI is set using the alt key modified + primary click
|
||||
if (e.altKey) {
|
||||
var element = $(e.currentTarget);
|
||||
var width = element.width();
|
||||
var relativeX = e.pageX - element.offset().left;
|
||||
var percX = relativeX / width;
|
||||
var bounds = this.timeAPI.bounds();
|
||||
var timeRange = bounds.end - bounds.start;
|
||||
|
||||
this.timeAPI.timeOfInterest(timeRange * percX + bounds.start);
|
||||
}
|
||||
};
|
||||
|
||||
return ConductorTOIController;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,153 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./ConductorTOIController'
|
||||
], function (
|
||||
ConductorTOIController
|
||||
) {
|
||||
var mockConductor;
|
||||
var mockConductorViewService;
|
||||
var mockScope;
|
||||
var mockAPI;
|
||||
var conductorTOIController;
|
||||
|
||||
function getNamedCallback(thing, name) {
|
||||
return thing.calls.all().filter(function (call) {
|
||||
return call.args[0] === name;
|
||||
}).map(function (call) {
|
||||
return call.args;
|
||||
})[0][1];
|
||||
}
|
||||
|
||||
describe("The ConductorTOIController", function () {
|
||||
beforeEach(function () {
|
||||
mockConductor = jasmine.createSpyObj("conductor", [
|
||||
"bounds",
|
||||
"timeOfInterest",
|
||||
"on",
|
||||
"off"
|
||||
]);
|
||||
mockAPI = {time: mockConductor};
|
||||
|
||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
||||
"on",
|
||||
"off"
|
||||
]);
|
||||
|
||||
mockScope = jasmine.createSpyObj("openMCT", [
|
||||
"$on"
|
||||
]);
|
||||
ConductorTOIController.prototype.viewService = mockConductorViewService;
|
||||
conductorTOIController = new ConductorTOIController(mockScope, mockAPI);
|
||||
});
|
||||
|
||||
it("listens to changes in the time of interest on the conductor", function () {
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", jasmine.any(Function));
|
||||
});
|
||||
|
||||
describe("when responding to changes in the time of interest", function () {
|
||||
var toiCallback;
|
||||
beforeEach(function () {
|
||||
var bounds = {
|
||||
start: 0,
|
||||
end: 200
|
||||
};
|
||||
mockConductor.bounds.and.returnValue(bounds);
|
||||
toiCallback = getNamedCallback(mockConductor.on, "timeOfInterest");
|
||||
});
|
||||
|
||||
it("calculates the correct horizontal offset based on bounds and current TOI", function () {
|
||||
//Expect time of interest position to be 50% of element width
|
||||
mockConductor.timeOfInterest.and.returnValue(100);
|
||||
toiCallback();
|
||||
expect(conductorTOIController.left).toBe(50);
|
||||
|
||||
//Expect time of interest position to be 25% of element width
|
||||
mockConductor.timeOfInterest.and.returnValue(50);
|
||||
toiCallback();
|
||||
expect(conductorTOIController.left).toBe(25);
|
||||
|
||||
//Expect time of interest position to be 0% of element width
|
||||
mockConductor.timeOfInterest.and.returnValue(0);
|
||||
toiCallback();
|
||||
expect(conductorTOIController.left).toBe(0);
|
||||
|
||||
//Expect time of interest position to be 100% of element width
|
||||
mockConductor.timeOfInterest.and.returnValue(200);
|
||||
toiCallback();
|
||||
expect(conductorTOIController.left).toBe(100);
|
||||
});
|
||||
|
||||
it("renders the TOI indicator visible", function () {
|
||||
expect(conductorTOIController.pinned).toBeFalsy();
|
||||
mockConductor.timeOfInterest.and.returnValue(100);
|
||||
toiCallback();
|
||||
expect(conductorTOIController.pinned).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("responds to zoom events", function () {
|
||||
var mockZoom = {
|
||||
bounds: {
|
||||
start: 500,
|
||||
end: 1000
|
||||
}
|
||||
};
|
||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", jasmine.any(Function));
|
||||
|
||||
// Should correspond to horizontal offset of 50%
|
||||
mockConductor.timeOfInterest.and.returnValue(750);
|
||||
var zoomCallback = getNamedCallback(mockConductorViewService.on, "zoom");
|
||||
zoomCallback(mockZoom);
|
||||
expect(conductorTOIController.left).toBe(50);
|
||||
});
|
||||
|
||||
it("responds to pan events", function () {
|
||||
var mockPanBounds = {
|
||||
start: 1000,
|
||||
end: 3000
|
||||
};
|
||||
|
||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("pan", jasmine.any(Function));
|
||||
|
||||
// Should correspond to horizontal offset of 25%
|
||||
mockConductor.timeOfInterest.and.returnValue(1500);
|
||||
var panCallback = getNamedCallback(mockConductorViewService.on, "pan");
|
||||
panCallback(mockPanBounds);
|
||||
expect(conductorTOIController.left).toBe(25);
|
||||
});
|
||||
|
||||
|
||||
it("Cleans up all listeners when controller destroyed", function () {
|
||||
var zoomCB = getNamedCallback(mockConductorViewService.on, "zoom");
|
||||
var panCB = getNamedCallback(mockConductorViewService.on, "pan");
|
||||
var toiCB = getNamedCallback(mockConductor.on, "timeOfInterest");
|
||||
|
||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
|
||||
getNamedCallback(mockScope.$on, "$destroy")();
|
||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", zoomCB);
|
||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("pan", panCB);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("timeOfInterest", toiCB);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./ConductorTOIController'], function (ConductorTOIController) {
|
||||
/**
|
||||
* A directive that encapsulates the TOI specific behavior of the Time Conductor UI.
|
||||
* @constructor
|
||||
*/
|
||||
function ConductorTOIDirective() {
|
||||
/**
|
||||
* The mct-conductor-axis renders a horizontal axis with regular
|
||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
||||
* be specified as attributes.
|
||||
*/
|
||||
return {
|
||||
controller: [
|
||||
'$scope',
|
||||
'openmct',
|
||||
ConductorTOIController
|
||||
],
|
||||
controllerAs: 'toi',
|
||||
scope: {
|
||||
viewService: "="
|
||||
},
|
||||
bindToController: true,
|
||||
|
||||
restrict: 'E',
|
||||
priority: 1000,
|
||||
|
||||
template:
|
||||
'<div class="l-data-visualization-holder l-row-elem flex-elem">' +
|
||||
' <a class="l-page-button s-icon-button icon-pointer-left"></a>' +
|
||||
' <div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">' +
|
||||
' <mct-include key="\'time-of-interest\'" class="l-toi-holder show-val" ' +
|
||||
' ng-class="{ pinned: toi.pinned, \'val-to-left\': toi.left > 80 }" ' +
|
||||
' ng-style="{\'left\': toi.left + \'%\'}"></mct-include>' +
|
||||
' </div>' +
|
||||
' <a class="l-page-button align-right s-icon-button icon-pointer-right"></a>' +
|
||||
'</div>'
|
||||
};
|
||||
}
|
||||
|
||||
return ConductorTOIDirective;
|
||||
});
|
||||
@@ -20,47 +20,35 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import moment from 'moment';
|
||||
define([], function () {
|
||||
|
||||
export default function multiFormat(date) {
|
||||
var momentified = moment.utc(date);
|
||||
/**
|
||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
||||
* Formatter for basic numbers. Provides basic support for non-UTC
|
||||
* numbering systems
|
||||
*
|
||||
* Licensed
|
||||
* @implements {Format}
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
var format = [
|
||||
[".SSS", function (m) {
|
||||
return m.milliseconds();
|
||||
}],
|
||||
[":ss", function (m) {
|
||||
return m.seconds();
|
||||
}],
|
||||
["HH:mm", function (m) {
|
||||
return m.minutes();
|
||||
}],
|
||||
["HH:mm", function (m) {
|
||||
return m.hours();
|
||||
}],
|
||||
["ddd DD", function (m) {
|
||||
return m.days() &&
|
||||
m.date() !== 1;
|
||||
}],
|
||||
["MMM DD", function (m) {
|
||||
return m.date() !== 1;
|
||||
}],
|
||||
["MMMM", function (m) {
|
||||
return m.month();
|
||||
}],
|
||||
["YYYY", function () {
|
||||
return true;
|
||||
}]
|
||||
].filter(function (row) {
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
|
||||
if (format !== undefined) {
|
||||
return moment.utc(date).format(format);
|
||||
function NumberFormat() {
|
||||
this.key = 'number';
|
||||
}
|
||||
}
|
||||
|
||||
NumberFormat.prototype.format = function (value) {
|
||||
if (isNaN(value)) {
|
||||
return '';
|
||||
} else {
|
||||
return '' + value;
|
||||
}
|
||||
};
|
||||
|
||||
NumberFormat.prototype.parse = function (text) {
|
||||
return parseFloat(text);
|
||||
};
|
||||
|
||||
NumberFormat.prototype.validate = function (text) {
|
||||
return !isNaN(text);
|
||||
};
|
||||
|
||||
return NumberFormat;
|
||||
});
|
||||
49
platform/features/conductor/core/src/ui/NumberFormatSpec.js
Normal file
49
platform/features/conductor/core/src/ui/NumberFormatSpec.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./NumberFormat'], function (NumberFormat) {
|
||||
describe("The NumberFormat class", function () {
|
||||
var format;
|
||||
beforeEach(function () {
|
||||
format = new NumberFormat();
|
||||
});
|
||||
|
||||
it("The format function takes a string and produces a number", function () {
|
||||
var text = format.format(1);
|
||||
expect(text).toBe("1");
|
||||
expect(typeof text).toBe("string");
|
||||
});
|
||||
|
||||
it("The parse function takes a string and produces a number", function () {
|
||||
var number = format.parse("1");
|
||||
expect(number).toBe(1);
|
||||
expect(typeof number).toBe("number");
|
||||
});
|
||||
|
||||
it("validates that the input is a number", function () {
|
||||
expect(format.validate("1")).toBe(true);
|
||||
expect(format.validate(1)).toBe(true);
|
||||
expect(format.validate("1.1")).toBe(true);
|
||||
expect(format.validate("abc")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -20,17 +20,34 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./FolderGridView',
|
||||
'./FolderListView'
|
||||
], function (
|
||||
FolderGridView,
|
||||
FolderListView
|
||||
) {
|
||||
return function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new FolderGridView(openmct));
|
||||
openmct.objectViews.addProvider(new FolderListView(openmct));
|
||||
};
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* Formatter for basic strings.
|
||||
*
|
||||
* @implements {Format}
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
function StringFormat() {
|
||||
this.key = 'string';
|
||||
}
|
||||
|
||||
StringFormat.prototype.format = function (string) {
|
||||
if (typeof string === 'string') {
|
||||
return string;
|
||||
} else {
|
||||
return '' + string;
|
||||
}
|
||||
};
|
||||
|
||||
StringFormat.prototype.parse = function (string) {
|
||||
return string;
|
||||
};
|
||||
|
||||
StringFormat.prototype.validate = function (string) {
|
||||
return typeof string === 'string';
|
||||
};
|
||||
|
||||
return StringFormat;
|
||||
});
|
||||
@@ -0,0 +1,554 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
'moment',
|
||||
'./TimeConductorValidation',
|
||||
'./TimeConductorViewService'
|
||||
],
|
||||
function (
|
||||
moment,
|
||||
TimeConductorValidation,
|
||||
TimeConductorViewService
|
||||
) {
|
||||
|
||||
var timeUnitsMegastructure = [
|
||||
["Decades", function (r) {
|
||||
return r.years() > 15;
|
||||
}],
|
||||
["Years", function (r) {
|
||||
return r.years() > 1;
|
||||
}],
|
||||
["Months", function (r) {
|
||||
return r.years() === 1 || r.months() > 1;
|
||||
}],
|
||||
["Days", function (r) {
|
||||
return r.months() === 1 || r.days() > 1;
|
||||
}],
|
||||
["Hours", function (r) {
|
||||
return r.days() === 1 || r.hours() > 1;
|
||||
}],
|
||||
["Minutes", function (r) {
|
||||
return r.hours() === 1 || r.minutes() > 1;
|
||||
}],
|
||||
["Seconds", function (r) {
|
||||
return r.minutes() === 1 || r.seconds() > 1;
|
||||
}],
|
||||
["Milliseconds", function (r) {
|
||||
return true;
|
||||
}]
|
||||
];
|
||||
|
||||
/**
|
||||
* Controller for the Time Conductor UI element. The Time Conductor
|
||||
* includes form fields for specifying time bounds and relative time
|
||||
* offsets for queries, as well as controls for selection mode,
|
||||
* time systems, and zooming.
|
||||
* @memberof platform.features.conductor
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductorController(
|
||||
$scope,
|
||||
$window,
|
||||
openmct,
|
||||
formatService,
|
||||
config
|
||||
) {
|
||||
|
||||
//Bind functions that are used as callbacks to 'this'.
|
||||
[
|
||||
"selectMenuOption",
|
||||
"onPan",
|
||||
"onPanStop",
|
||||
"setViewFromBounds",
|
||||
"setViewFromClock",
|
||||
"setViewFromOffsets",
|
||||
"setViewFromTimeSystem",
|
||||
"setTimeSystemFromView",
|
||||
"destroy"
|
||||
].forEach(function (name) {
|
||||
this[name] = this[name].bind(this);
|
||||
}.bind(this));
|
||||
|
||||
this.$scope = $scope;
|
||||
this.$window = $window;
|
||||
this.timeAPI = openmct.time;
|
||||
this.conductorViewService = new TimeConductorViewService(openmct);
|
||||
this.validation = new TimeConductorValidation(this.timeAPI);
|
||||
this.formatService = formatService;
|
||||
this.config = config;
|
||||
this.timeSystemsForClocks = {};
|
||||
this.$scope.timeSystemModel = {};
|
||||
this.$scope.boundsModel = {};
|
||||
|
||||
this.timeSystems = this.timeAPI.getAllTimeSystems().reduce(function (map, timeSystem) {
|
||||
map[timeSystem.key] = timeSystem;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
this.isFixed = this.timeAPI.clock() === undefined;
|
||||
|
||||
var options = this.optionsFromConfig(config);
|
||||
this.menu = {
|
||||
selected: undefined,
|
||||
options: options,
|
||||
select: this.selectMenuOption
|
||||
};
|
||||
|
||||
//Set the initial state of the UI from the conductor state
|
||||
var timeSystem = this.timeAPI.timeSystem();
|
||||
if (timeSystem) {
|
||||
this.setViewFromTimeSystem(timeSystem);
|
||||
}
|
||||
|
||||
this.setViewFromClock(this.timeAPI.clock());
|
||||
|
||||
var offsets = this.timeAPI.clockOffsets();
|
||||
if (offsets) {
|
||||
this.setViewFromOffsets(offsets);
|
||||
}
|
||||
|
||||
var bounds = this.timeAPI.bounds();
|
||||
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
||||
this.setViewFromBounds(bounds);
|
||||
}
|
||||
|
||||
this.conductorViewService.on('pan', this.onPan);
|
||||
this.conductorViewService.on('pan-stop', this.onPanStop);
|
||||
|
||||
//Respond to any subsequent conductor changes
|
||||
this.timeAPI.on('bounds', this.setViewFromBounds);
|
||||
this.timeAPI.on('timeSystem', this.setViewFromTimeSystem);
|
||||
this.timeAPI.on('clock', this.setViewFromClock);
|
||||
this.timeAPI.on('clockOffsets', this.setViewFromOffsets);
|
||||
this.$scope.$on('$destroy', this.destroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a key for a clock, retrieve the clock object.
|
||||
* @private
|
||||
* @param key
|
||||
* @returns {Clock}
|
||||
*/
|
||||
TimeConductorController.prototype.getClock = function (key) {
|
||||
return this.timeAPI.getAllClocks().filter(function (clock) {
|
||||
return clock.key === key;
|
||||
})[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate the selected menu option. Menu options correspond to clocks.
|
||||
* A distinction is made to avoid confusion between the menu options and
|
||||
* their metadata, and actual {@link Clock} objects.
|
||||
*
|
||||
* @private
|
||||
* @param newOption
|
||||
*/
|
||||
TimeConductorController.prototype.selectMenuOption = function (newOption) {
|
||||
if (this.menu.selected.key === newOption.key) {
|
||||
return;
|
||||
}
|
||||
this.menu.selected = newOption;
|
||||
|
||||
var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock);
|
||||
if (!config) {
|
||||
// Clock does not support this timeSystem, fallback to first
|
||||
// option provided for clock.
|
||||
config = this.config.menuOptions.filter(function (menuOption) {
|
||||
return menuOption.clock === (newOption.clock && newOption.clock.key);
|
||||
})[0];
|
||||
}
|
||||
|
||||
if (config.clock) {
|
||||
this.timeAPI.clock(config.clock, config.clockOffsets);
|
||||
this.timeAPI.timeSystem(config.timeSystem);
|
||||
} else {
|
||||
this.timeAPI.stopClock();
|
||||
this.timeAPI.timeSystem(config.timeSystem, config.bounds);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* From the provided configuration, build the available menu options.
|
||||
* @private
|
||||
* @param config
|
||||
* @returns {*[]}
|
||||
*/
|
||||
TimeConductorController.prototype.optionsFromConfig = function (config) {
|
||||
/*
|
||||
* "Fixed Mode" is always the first available option.
|
||||
*/
|
||||
var options = [{
|
||||
key: 'fixed',
|
||||
name: 'Fixed Timespan Mode',
|
||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
||||
cssClass: 'icon-calendar'
|
||||
}];
|
||||
var clocks = {};
|
||||
var timeSystemsForClocks = this.timeSystemsForClocks;
|
||||
|
||||
(config.menuOptions || []).forEach(function (menuOption) {
|
||||
var clockKey = menuOption.clock || 'fixed';
|
||||
var clock = this.getClock(clockKey);
|
||||
|
||||
if (clock !== undefined) {
|
||||
clocks[clock.key] = clock;
|
||||
}
|
||||
|
||||
var timeSystem = this.timeSystems[menuOption.timeSystem];
|
||||
if (timeSystem !== undefined) {
|
||||
timeSystemsForClocks[clockKey] = timeSystemsForClocks[clockKey] || [];
|
||||
timeSystemsForClocks[clockKey].push(timeSystem);
|
||||
}
|
||||
}, this);
|
||||
|
||||
/*
|
||||
* Populate the clocks menu with metadata from the available clocks
|
||||
*/
|
||||
Object.values(clocks).forEach(function (clock) {
|
||||
options.push({
|
||||
key: clock.key,
|
||||
name: clock.name,
|
||||
description: "Monitor streaming data in real-time. The Time " +
|
||||
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
||||
cssClass: clock.cssClass || 'icon-clock',
|
||||
clock: clock
|
||||
});
|
||||
}.bind(this));
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* When bounds change, set UI values from the new bounds.
|
||||
* @param {TimeBounds} bounds the bounds
|
||||
*/
|
||||
TimeConductorController.prototype.setViewFromBounds = function (bounds) {
|
||||
if (!this.zooming && !this.panning) {
|
||||
this.$scope.boundsModel.start = bounds.start;
|
||||
this.$scope.boundsModel.end = bounds.end;
|
||||
|
||||
if (this.supportsZoom()) {
|
||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
||||
this.currentZoom = this.toSliderValue(bounds.end - bounds.start, config.zoomOutLimit, config.zoomInLimit);
|
||||
this.toTimeUnits(bounds.end - bounds.start);
|
||||
}
|
||||
|
||||
/*
|
||||
Ensure that a digest occurs, capped at the browser's refresh
|
||||
rate.
|
||||
*/
|
||||
if (!this.pendingUpdate) {
|
||||
this.pendingUpdate = true;
|
||||
this.$window.requestAnimationFrame(function () {
|
||||
this.pendingUpdate = false;
|
||||
this.$scope.$digest();
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve any configuration defined for the provided time system and
|
||||
* clock
|
||||
* @private
|
||||
* @param timeSystem
|
||||
* @param clock
|
||||
* @returns {object} The Time Conductor configuration corresponding to
|
||||
* the provided combination of time system and clock
|
||||
*/
|
||||
TimeConductorController.prototype.getConfig = function (timeSystem, clock) {
|
||||
var clockKey = clock && clock.key;
|
||||
var timeSystemKey = timeSystem && timeSystem.key;
|
||||
|
||||
var option = this.config.menuOptions.filter(function (menuOption) {
|
||||
return menuOption.timeSystem === timeSystemKey && menuOption.clock === clockKey;
|
||||
})[0];
|
||||
return option;
|
||||
};
|
||||
|
||||
/**
|
||||
* When the clock offsets change, update the values in the UI
|
||||
* @param {ClockOffsets} offsets
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.setViewFromOffsets = function (offsets) {
|
||||
this.$scope.boundsModel.startOffset = Math.abs(offsets.start);
|
||||
this.$scope.boundsModel.endOffset = offsets.end;
|
||||
};
|
||||
|
||||
/**
|
||||
* When form values for bounds change, update the bounds in the Time API
|
||||
* to trigger an application-wide bounds change.
|
||||
* @param {object} boundsModel
|
||||
*/
|
||||
TimeConductorController.prototype.setBoundsFromView = function (boundsModel) {
|
||||
var bounds = this.timeAPI.bounds();
|
||||
if (boundsModel.start !== bounds.start || boundsModel.end !== bounds.end) {
|
||||
this.timeAPI.bounds({
|
||||
start: boundsModel.start,
|
||||
end: boundsModel.end
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When form values for bounds change, update the bounds in the Time API
|
||||
* to trigger an application-wide bounds change.
|
||||
* @param {object} formModel
|
||||
*/
|
||||
TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) {
|
||||
if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) {
|
||||
var offsets = {
|
||||
start: 0 - boundsModel.startOffset,
|
||||
end: boundsModel.endOffset
|
||||
};
|
||||
var existingOffsets = this.timeAPI.clockOffsets();
|
||||
|
||||
if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) {
|
||||
//Sychronize offsets between form and time API
|
||||
this.timeAPI.clockOffsets(offsets);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
TimeConductorController.prototype.supportsZoom = function () {
|
||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
||||
return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the UI state to reflect a change in clock. Provided conductor
|
||||
* configuration will be checked for compatibility between the new clock
|
||||
* and the currently selected time system. If configuration is not available,
|
||||
* an attempt will be made to default to a time system that is compatible
|
||||
* with the new clock
|
||||
*
|
||||
* @private
|
||||
* @param {Clock} clock
|
||||
*/
|
||||
TimeConductorController.prototype.setViewFromClock = function (clock) {
|
||||
var newClockKey = clock ? clock.key : 'fixed';
|
||||
var timeSystems = this.timeSystemsForClocks[newClockKey];
|
||||
var menuOption = this.menu.options.filter(function (option) {
|
||||
return option.key === (newClockKey);
|
||||
})[0];
|
||||
|
||||
this.menu.selected = menuOption;
|
||||
|
||||
//Try to find currently selected time system in time systems for clock
|
||||
var selectedTimeSystem = timeSystems.filter(function (timeSystem) {
|
||||
return timeSystem.key === this.$scope.timeSystemModel.selected.key;
|
||||
}.bind(this))[0];
|
||||
|
||||
var config = this.getConfig(selectedTimeSystem, clock);
|
||||
|
||||
if (selectedTimeSystem === undefined) {
|
||||
selectedTimeSystem = timeSystems[0];
|
||||
config = this.getConfig(selectedTimeSystem, clock);
|
||||
|
||||
if (clock === undefined) {
|
||||
this.timeAPI.timeSystem(selectedTimeSystem, config.bounds);
|
||||
} else {
|
||||
//When time system changes, some start bounds need to be provided
|
||||
this.timeAPI.timeSystem(selectedTimeSystem, {
|
||||
start: clock.currentValue() + config.clockOffsets.start,
|
||||
end: clock.currentValue() + config.clockOffsets.end
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.isFixed = clock === undefined;
|
||||
|
||||
if (clock === undefined) {
|
||||
this.setViewFromBounds(this.timeAPI.bounds());
|
||||
}
|
||||
|
||||
this.zoom = this.supportsZoom();
|
||||
this.$scope.timeSystemModel.options = timeSystems;
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to time system selection from UI
|
||||
*
|
||||
* Allows time system to be changed by key. This supports selection
|
||||
* from the menu. Resolves a TimeSystem object and then invokes
|
||||
* TimeConductorController#setTimeSystem
|
||||
* @param key
|
||||
* @see TimeConductorController#setTimeSystem
|
||||
*/
|
||||
TimeConductorController.prototype.setTimeSystemFromView = function (key) {
|
||||
var clock = this.menu.selected.clock;
|
||||
var timeSystem = this.timeSystems[key];
|
||||
var config = this.getConfig(timeSystem, clock);
|
||||
|
||||
this.$scope.timeSystemModel.selected = timeSystem;
|
||||
this.$scope.timeSystemModel.format = timeSystem.timeFormat;
|
||||
|
||||
if (clock === undefined) {
|
||||
this.timeAPI.timeSystem(timeSystem, config.bounds);
|
||||
} else {
|
||||
this.timeAPI.clock(clock, config.clockOffsets);
|
||||
this.timeAPI.timeSystem(timeSystem);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles time system change from time conductor
|
||||
*
|
||||
* Sets the selected time system. Will populate form with the default
|
||||
* bounds and offsets defined in the selected time system.
|
||||
*
|
||||
* @param newTimeSystem
|
||||
*/
|
||||
TimeConductorController.prototype.setViewFromTimeSystem = function (timeSystem) {
|
||||
var oldKey = (this.$scope.timeSystemModel.selected || {}).key;
|
||||
var timeSystemModel = this.$scope.timeSystemModel;
|
||||
|
||||
if (timeSystem && (timeSystem.key !== oldKey)) {
|
||||
var config = this.getConfig(timeSystem, this.timeAPI.clock());
|
||||
|
||||
timeSystemModel.selected = timeSystem;
|
||||
timeSystemModel.format = timeSystem.timeFormat;
|
||||
timeSystemModel.durationFormat = timeSystem.durationFormat;
|
||||
|
||||
if (this.supportsZoom()) {
|
||||
timeSystemModel.minZoom = config.zoomOutLimit;
|
||||
timeSystemModel.maxZoom = config.zoomInLimit;
|
||||
}
|
||||
}
|
||||
this.zoom = this.supportsZoom();
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a time span and calculates a slider increment value, used
|
||||
* to set the horizontal offset of the slider.
|
||||
* @private
|
||||
* @param {number} timeSpan a duration of time, in ms
|
||||
* @returns {number} a value between 0.01 and 0.99, in increments of .01
|
||||
*/
|
||||
TimeConductorController.prototype.toSliderValue = function (timeSpan, zoomOutLimit, zoomInLimit) {
|
||||
var perc = timeSpan / (zoomOutLimit - zoomInLimit);
|
||||
return 1 - Math.pow(perc, 1 / 4);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a time span, set a label for the units of time that it,
|
||||
* roughly, represents. Leverages
|
||||
* @param {TimeSpan} timeSpan
|
||||
*/
|
||||
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
|
||||
var timeSystem = this.timeAPI.timeSystem();
|
||||
if (timeSystem && timeSystem.isUTCBased) {
|
||||
var momentified = moment.duration(timeSpan);
|
||||
|
||||
this.$scope.timeUnits = timeUnitsMegastructure.filter(function (row) {
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Zooming occurs when the user manipulates the zoom slider.
|
||||
* Zooming updates the scale and bounds fields immediately, but does
|
||||
* not trigger a bounds change to other views until the mouse button
|
||||
* is released.
|
||||
* @param bounds
|
||||
*/
|
||||
TimeConductorController.prototype.onZoom = function (sliderValue) {
|
||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
||||
var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit);
|
||||
|
||||
var zoom = this.conductorViewService.zoom(timeSpan);
|
||||
this.zooming = true;
|
||||
|
||||
this.$scope.boundsModel.start = zoom.bounds.start;
|
||||
this.$scope.boundsModel.end = zoom.bounds.end;
|
||||
this.toTimeUnits(zoom.bounds.end - zoom.bounds.start);
|
||||
|
||||
if (zoom.offsets) {
|
||||
this.setViewFromOffsets(zoom.offsets);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fired when user has released the zoom slider
|
||||
* @event platform.features.conductor.TimeConductorController~zoomStop
|
||||
*/
|
||||
/**
|
||||
* Invoked when zoom slider is released by user. Will update the time conductor with the new bounds, triggering
|
||||
* a global bounds change event.
|
||||
* @fires platform.features.conductor.TimeConductorController~zoomStop
|
||||
*/
|
||||
TimeConductorController.prototype.onZoomStop = function () {
|
||||
if (this.timeAPI.clock() !== undefined) {
|
||||
this.setOffsetsFromView(this.$scope.boundsModel);
|
||||
}
|
||||
this.setBoundsFromView(this.$scope.boundsModel);
|
||||
|
||||
this.zooming = false;
|
||||
this.conductorViewService.emit('zoom-stop');
|
||||
};
|
||||
|
||||
/**
|
||||
* Panning occurs when the user grabs the conductor scale and drags
|
||||
* it left or right to slide the window of time represented by the
|
||||
* conductor. Panning updates the scale and bounds fields
|
||||
* immediately, but does not trigger a bounds change to other views
|
||||
* until the mouse button is released.
|
||||
* @param {TimeConductorBounds} bounds
|
||||
*/
|
||||
TimeConductorController.prototype.onPan = function (bounds) {
|
||||
this.panning = true;
|
||||
this.$scope.boundsModel.start = bounds.start;
|
||||
this.$scope.boundsModel.end = bounds.end;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the user releases the mouse button after panning.
|
||||
*/
|
||||
TimeConductorController.prototype.onPanStop = function () {
|
||||
this.panning = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.destroy = function () {
|
||||
this.timeAPI.off('bounds', this.setViewFromBounds);
|
||||
this.timeAPI.off('timeSystem', this.setViewFromTimeSystem);
|
||||
this.timeAPI.off('clock', this.setViewFromClock);
|
||||
this.timeAPI.off('follow', this.setFollow);
|
||||
this.timeAPI.off('clockOffsets', this.setViewFromOffsets);
|
||||
|
||||
this.conductorViewService.off('pan', this.onPan);
|
||||
this.conductorViewService.off('pan-stop', this.onPanStop);
|
||||
};
|
||||
|
||||
return TimeConductorController;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,513 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./TimeConductorController'], function (TimeConductorController) {
|
||||
xdescribe("The time conductor controller", function () {
|
||||
var mockScope;
|
||||
var mockWindow;
|
||||
var mockTimeConductor;
|
||||
var mockConductorViewService;
|
||||
var mockTimeSystems;
|
||||
var controller;
|
||||
var mockFormatService;
|
||||
var mockFormat;
|
||||
var mockLocation;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj("$scope", [
|
||||
"$watch",
|
||||
"$on"
|
||||
]);
|
||||
|
||||
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
|
||||
mockTimeConductor = jasmine.createSpyObj(
|
||||
"TimeConductor",
|
||||
[
|
||||
"bounds",
|
||||
"timeSystem",
|
||||
"on",
|
||||
"off"
|
||||
]
|
||||
);
|
||||
mockTimeConductor.bounds.and.returnValue({start: undefined, end: undefined});
|
||||
|
||||
mockConductorViewService = jasmine.createSpyObj(
|
||||
"ConductorViewService",
|
||||
[
|
||||
"availableModes",
|
||||
"mode",
|
||||
"availableTimeSystems",
|
||||
"deltas",
|
||||
"deltas",
|
||||
"on",
|
||||
"off"
|
||||
]
|
||||
);
|
||||
mockConductorViewService.availableModes.and.returnValue([]);
|
||||
mockConductorViewService.availableTimeSystems.and.returnValue([]);
|
||||
|
||||
mockFormatService = jasmine.createSpyObj('formatService', [
|
||||
'getFormat'
|
||||
]);
|
||||
mockFormat = jasmine.createSpyObj('format', [
|
||||
'format'
|
||||
]);
|
||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
||||
mockLocation = jasmine.createSpyObj('location', [
|
||||
'search'
|
||||
]);
|
||||
mockLocation.search.and.returnValue({});
|
||||
|
||||
mockTimeSystems = [];
|
||||
});
|
||||
|
||||
function getListener(target, event) {
|
||||
return target.calls.all().filter(function (call) {
|
||||
return call.args[0] === event;
|
||||
})[0].args[1];
|
||||
}
|
||||
|
||||
describe("when time conductor state changes", function () {
|
||||
var mockDeltaFormat;
|
||||
var defaultBounds;
|
||||
var defaultDeltas;
|
||||
var mockDefaults;
|
||||
var timeSystem;
|
||||
var tsListener;
|
||||
|
||||
beforeEach(function () {
|
||||
mockFormat = {};
|
||||
mockDeltaFormat = {};
|
||||
defaultBounds = {
|
||||
start: 2,
|
||||
end: 3
|
||||
};
|
||||
defaultDeltas = {
|
||||
start: 10,
|
||||
end: 20
|
||||
};
|
||||
mockDefaults = {
|
||||
deltas: defaultDeltas,
|
||||
bounds: defaultBounds
|
||||
};
|
||||
timeSystem = {
|
||||
metadata: {
|
||||
key: 'mock'
|
||||
},
|
||||
formats: function () {
|
||||
return [mockFormat];
|
||||
},
|
||||
deltaFormat: function () {
|
||||
return mockDeltaFormat;
|
||||
},
|
||||
defaults: function () {
|
||||
return mockDefaults;
|
||||
}
|
||||
};
|
||||
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
mockFormatService,
|
||||
'fixed',
|
||||
true
|
||||
|
||||
);
|
||||
|
||||
tsListener = getListener(mockTimeConductor.on, "timeSystem");
|
||||
});
|
||||
|
||||
it("listens for changes to conductor state", function () {
|
||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
||||
});
|
||||
|
||||
it("deregisters conductor listens when scope is destroyed", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
||||
|
||||
controller.destroy();
|
||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
||||
});
|
||||
|
||||
it("when time system changes, sets time system on scope", function () {
|
||||
expect(tsListener).toBeDefined();
|
||||
tsListener(timeSystem);
|
||||
|
||||
expect(mockScope.timeSystemModel).toBeDefined();
|
||||
expect(mockScope.timeSystemModel.selected).toBe(timeSystem);
|
||||
expect(mockScope.timeSystemModel.format).toBe(mockFormat);
|
||||
expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat);
|
||||
});
|
||||
|
||||
it("when time system changes, sets defaults on scope", function () {
|
||||
mockDefaults.zoom = {
|
||||
min: 100,
|
||||
max: 10
|
||||
};
|
||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
||||
tsListener(timeSystem);
|
||||
|
||||
expect(mockScope.boundsModel.start).toEqual(defaultBounds.start);
|
||||
expect(mockScope.boundsModel.end).toEqual(defaultBounds.end);
|
||||
|
||||
expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start);
|
||||
expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end);
|
||||
|
||||
expect(mockScope.timeSystemModel.minZoom).toBe(mockDefaults.zoom.min);
|
||||
expect(mockScope.timeSystemModel.maxZoom).toBe(mockDefaults.zoom.max);
|
||||
});
|
||||
|
||||
it("supports zoom if time system defines zoom defaults", function () {
|
||||
|
||||
mockDefaults.zoom = undefined;
|
||||
|
||||
tsListener(timeSystem);
|
||||
expect(controller.supportsZoom).toBe(false);
|
||||
|
||||
mockDefaults.zoom = {
|
||||
min: 100,
|
||||
max: 10
|
||||
};
|
||||
|
||||
var anotherTimeSystem = Object.create(timeSystem);
|
||||
timeSystem.defaults = function () {
|
||||
return mockDefaults;
|
||||
};
|
||||
|
||||
tsListener(anotherTimeSystem);
|
||||
expect(controller.supportsZoom).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
it("when bounds change, sets the correct zoom slider value", function () {
|
||||
var bounds = {
|
||||
start: 0,
|
||||
end: 50
|
||||
};
|
||||
mockDefaults.zoom = {
|
||||
min: 100,
|
||||
max: 0
|
||||
};
|
||||
|
||||
function exponentializer(rawValue) {
|
||||
return 1 - Math.pow(rawValue, 1 / 4);
|
||||
}
|
||||
|
||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
||||
//Set zoom defaults
|
||||
tsListener(timeSystem);
|
||||
|
||||
controller.changeBounds(bounds);
|
||||
expect(controller.currentZoom).toEqual(exponentializer(0.5));
|
||||
|
||||
});
|
||||
|
||||
it("when bounds change, sets them on scope", function () {
|
||||
var bounds = {
|
||||
start: 1,
|
||||
end: 2
|
||||
};
|
||||
|
||||
var boundsListener = getListener(mockTimeConductor.on, "bounds");
|
||||
expect(boundsListener).toBeDefined();
|
||||
boundsListener(bounds);
|
||||
|
||||
expect(mockScope.boundsModel).toBeDefined();
|
||||
expect(mockScope.boundsModel.start).toEqual(bounds.start);
|
||||
expect(mockScope.boundsModel.end).toEqual(bounds.end);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user makes changes from UI", function () {
|
||||
var mode = "realtime";
|
||||
var ts1Metadata;
|
||||
var ts2Metadata;
|
||||
var ts3Metadata;
|
||||
|
||||
beforeEach(function () {
|
||||
mode = "realtime";
|
||||
ts1Metadata = {
|
||||
'key': 'ts1',
|
||||
'name': 'Time System One',
|
||||
'cssClass': 'cssClassOne'
|
||||
};
|
||||
ts2Metadata = {
|
||||
'key': 'ts2',
|
||||
'name': 'Time System Two',
|
||||
'cssClass': 'cssClassTwo'
|
||||
};
|
||||
ts3Metadata = {
|
||||
'key': 'ts3',
|
||||
'name': 'Time System Three',
|
||||
'cssClass': 'cssClassThree'
|
||||
};
|
||||
mockTimeSystems = [
|
||||
{
|
||||
metadata: ts1Metadata
|
||||
},
|
||||
{
|
||||
metadata: ts2Metadata
|
||||
},
|
||||
{
|
||||
metadata: ts3Metadata
|
||||
}
|
||||
];
|
||||
|
||||
//Wrap in mock constructors
|
||||
mockConductorViewService.systems = mockTimeSystems;
|
||||
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
mockFormatService,
|
||||
"fixed",
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("sets the mode on scope", function () {
|
||||
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
|
||||
controller.setMode(mode);
|
||||
|
||||
expect(mockScope.modeModel.selectedKey).toEqual(mode);
|
||||
});
|
||||
|
||||
it("sets available time systems on scope when mode changes", function () {
|
||||
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
|
||||
controller.setMode(mode);
|
||||
|
||||
expect(mockScope.timeSystemModel.options.length).toEqual(3);
|
||||
expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata);
|
||||
expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata);
|
||||
expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata);
|
||||
});
|
||||
|
||||
it("sets bounds on the time conductor", function () {
|
||||
var formModel = {
|
||||
start: 1,
|
||||
end: 10
|
||||
};
|
||||
|
||||
controller.setBounds(formModel);
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
|
||||
});
|
||||
|
||||
it("applies deltas when they change in form", function () {
|
||||
var deltas = {
|
||||
start: 1000,
|
||||
end: 2000
|
||||
};
|
||||
var formModel = {
|
||||
startDelta: deltas.start,
|
||||
endDelta: deltas.end
|
||||
};
|
||||
|
||||
controller.setDeltas(formModel);
|
||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
|
||||
});
|
||||
|
||||
it("sets the time system on the time conductor", function () {
|
||||
var defaultBounds = {
|
||||
start: 5,
|
||||
end: 6
|
||||
};
|
||||
var timeSystem = {
|
||||
metadata: {
|
||||
key: 'testTimeSystem'
|
||||
},
|
||||
defaults: function () {
|
||||
return {
|
||||
bounds: defaultBounds
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
controller.timeSystems = [timeSystem];
|
||||
|
||||
controller.selectTimeSystemByKey('testTimeSystem');
|
||||
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
|
||||
});
|
||||
|
||||
it("updates form bounds during pan events", function () {
|
||||
var testBounds = {
|
||||
start: 10,
|
||||
end: 20
|
||||
};
|
||||
|
||||
expect(controller.$scope.boundsModel.start).not.toBe(testBounds.start);
|
||||
expect(controller.$scope.boundsModel.end).not.toBe(testBounds.end);
|
||||
|
||||
expect(controller.conductorViewService.on).toHaveBeenCalledWith("pan",
|
||||
controller.onPan);
|
||||
|
||||
getListener(controller.conductorViewService.on, "pan")(testBounds);
|
||||
|
||||
expect(controller.$scope.boundsModel.start).toBe(testBounds.start);
|
||||
expect(controller.$scope.boundsModel.end).toBe(testBounds.end);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the URL defines conductor state", function () {
|
||||
var urlBounds;
|
||||
var urlTimeSystem;
|
||||
var urlDeltas;
|
||||
|
||||
var mockDeltaFormat;
|
||||
var defaultBounds;
|
||||
var defaultDeltas;
|
||||
var mockDefaults;
|
||||
var timeSystem;
|
||||
var otherTimeSystem;
|
||||
var mockSearchObject;
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
mockFormat = {};
|
||||
mockDeltaFormat = {};
|
||||
defaultBounds = {
|
||||
start: 2,
|
||||
end: 3
|
||||
};
|
||||
defaultDeltas = {
|
||||
start: 10,
|
||||
end: 20
|
||||
};
|
||||
mockDefaults = {
|
||||
deltas: defaultDeltas,
|
||||
bounds: defaultBounds
|
||||
};
|
||||
timeSystem = {
|
||||
metadata: {
|
||||
key: 'mockTimeSystem'
|
||||
},
|
||||
formats: function () {
|
||||
return [mockFormat];
|
||||
},
|
||||
deltaFormat: function () {
|
||||
return mockDeltaFormat;
|
||||
},
|
||||
defaults: function () {
|
||||
return mockDefaults;
|
||||
}
|
||||
};
|
||||
otherTimeSystem = {
|
||||
metadata: {
|
||||
key: 'otherTimeSystem'
|
||||
},
|
||||
formats: function () {
|
||||
return [mockFormat];
|
||||
},
|
||||
deltaFormat: function () {
|
||||
return mockDeltaFormat;
|
||||
},
|
||||
defaults: function () {
|
||||
return mockDefaults;
|
||||
}
|
||||
};
|
||||
|
||||
mockConductorViewService.systems = [timeSystem, otherTimeSystem];
|
||||
|
||||
urlBounds = {
|
||||
start: 100,
|
||||
end: 200
|
||||
};
|
||||
urlTimeSystem = "otherTimeSystem";
|
||||
urlDeltas = {
|
||||
start: 300,
|
||||
end: 400
|
||||
};
|
||||
mockSearchObject = {
|
||||
"tc.startBound": urlBounds.start,
|
||||
"tc.endBound": urlBounds.end,
|
||||
"tc.startDelta": urlDeltas.start,
|
||||
"tc.endDelta": urlDeltas.end,
|
||||
"tc.timeSystem": urlTimeSystem
|
||||
};
|
||||
mockLocation.search.and.returnValue(mockSearchObject);
|
||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
||||
|
||||
controller = new TimeConductorController(
|
||||
mockScope,
|
||||
mockWindow,
|
||||
mockLocation,
|
||||
{conductor: mockTimeConductor},
|
||||
mockConductorViewService,
|
||||
mockFormatService,
|
||||
"fixed",
|
||||
true
|
||||
);
|
||||
|
||||
spyOn(controller, "setMode");
|
||||
spyOn(controller, "selectTimeSystemByKey");
|
||||
});
|
||||
|
||||
it("sets conductor state from URL", function () {
|
||||
mockSearchObject["tc.mode"] = "fixed";
|
||||
controller.setStateFromSearchParams(mockSearchObject);
|
||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(urlBounds);
|
||||
});
|
||||
|
||||
it("sets mode from URL", function () {
|
||||
mockTimeConductor.bounds.reset();
|
||||
mockSearchObject["tc.mode"] = "realtime";
|
||||
controller.setStateFromSearchParams(mockSearchObject);
|
||||
expect(controller.setMode).toHaveBeenCalledWith("realtime");
|
||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(urlDeltas);
|
||||
expect(mockTimeConductor.bounds).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when conductor state changes", function () {
|
||||
it("updates the URL with the mode", function () {
|
||||
controller.setMode("realtime", "fixed");
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.mode", "fixed");
|
||||
});
|
||||
|
||||
it("updates the URL with the bounds", function () {
|
||||
mockConductorViewService.mode.and.returnValue("fixed");
|
||||
controller.changeBounds({start: 500, end: 600});
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startBound", 500);
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endBound", 600);
|
||||
});
|
||||
|
||||
it("updates the URL with the deltas", function () {
|
||||
controller.setDeltas({startDelta: 700, endDelta: 800});
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startDelta", 700);
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endDelta", 800);
|
||||
});
|
||||
|
||||
it("updates the URL with the time system", function () {
|
||||
controller.changeTimeSystem(otherTimeSystem);
|
||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.timeSystem", "otherTimeSystem");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Form validation for the TimeConductorController.
|
||||
* @param conductor
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductorValidation(timeAPI) {
|
||||
var self = this;
|
||||
this.timeAPI = timeAPI;
|
||||
|
||||
/*
|
||||
* Bind all class functions to 'this'
|
||||
*/
|
||||
Object.keys(TimeConductorValidation.prototype).filter(function (key) {
|
||||
return typeof TimeConductorValidation.prototype[key] === 'function';
|
||||
}).forEach(function (key) {
|
||||
self[key] = self[key].bind(self);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation methods below are invoked directly from controls in the TimeConductor form
|
||||
*/
|
||||
TimeConductorValidation.prototype.validateStart = function (start) {
|
||||
var bounds = this.timeAPI.bounds();
|
||||
return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true;
|
||||
};
|
||||
|
||||
TimeConductorValidation.prototype.validateEnd = function (end) {
|
||||
var bounds = this.timeAPI.bounds();
|
||||
return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true;
|
||||
};
|
||||
|
||||
TimeConductorValidation.prototype.validateStartOffset = function (startOffset) {
|
||||
return !isNaN(startOffset) && startOffset > 0;
|
||||
};
|
||||
|
||||
TimeConductorValidation.prototype.validateEndOffset = function (endOffset) {
|
||||
return !isNaN(endOffset) && endOffset >= 0;
|
||||
};
|
||||
|
||||
return TimeConductorValidation;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,73 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./TimeConductorValidation'], function (TimeConductorValidation) {
|
||||
describe("The Time Conductor Validation class", function () {
|
||||
var timeConductorValidation,
|
||||
mockTimeConductor;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
|
||||
"validateBounds",
|
||||
"bounds"
|
||||
]);
|
||||
timeConductorValidation = new TimeConductorValidation(mockTimeConductor);
|
||||
});
|
||||
|
||||
describe("Validates start and end values using Time Conductor", function () {
|
||||
beforeEach(function () {
|
||||
var mockBounds = {
|
||||
start: 10,
|
||||
end: 20
|
||||
};
|
||||
|
||||
mockTimeConductor.bounds.and.returnValue(mockBounds);
|
||||
|
||||
});
|
||||
it("Validates start values using Time Conductor", function () {
|
||||
var startValue = 30;
|
||||
timeConductorValidation.validateStart(startValue);
|
||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
||||
});
|
||||
it("Validates end values using Time Conductor", function () {
|
||||
var endValue = 40;
|
||||
timeConductorValidation.validateEnd(endValue);
|
||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("Validates that start Offset is valid number > 0", function () {
|
||||
expect(timeConductorValidation.validateStartOffset(-1)).toBe(false);
|
||||
expect(timeConductorValidation.validateStartOffset("abc")).toBe(false);
|
||||
expect(timeConductorValidation.validateStartOffset("1")).toBe(true);
|
||||
expect(timeConductorValidation.validateStartOffset(1)).toBe(true);
|
||||
});
|
||||
|
||||
it("Validates that end Offset is valid number >= 0", function () {
|
||||
expect(timeConductorValidation.validateEndOffset(-1)).toBe(false);
|
||||
expect(timeConductorValidation.validateEndOffset("abc")).toBe(false);
|
||||
expect(timeConductorValidation.validateEndOffset("1")).toBe(true);
|
||||
expect(timeConductorValidation.validateEndOffset(0)).toBe(true);
|
||||
expect(timeConductorValidation.validateEndOffset(1)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
'EventEmitter'
|
||||
],
|
||||
function (EventEmitter) {
|
||||
|
||||
/**
|
||||
* The TimeConductorViewService acts as an event bus between different
|
||||
* elements of the Time Conductor UI. Zooming and panning occur via this
|
||||
* service, as they are specific behaviour of the UI, and not general
|
||||
* functions of the time API.
|
||||
*
|
||||
* Synchronization of conductor state between the Time API and the URL
|
||||
* also occurs from the conductor view service, whose lifecycle persists
|
||||
* between view changes.
|
||||
*
|
||||
* @memberof platform.features.conductor
|
||||
* @param conductor
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductorViewService(openmct) {
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.timeAPI = openmct.time;
|
||||
}
|
||||
|
||||
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
/**
|
||||
* An event to indicate that zooming is taking place
|
||||
* @event platform.features.conductor.TimeConductorViewService~zoom
|
||||
* @property {ZoomLevel} zoom the new zoom level.
|
||||
*/
|
||||
/**
|
||||
* Zoom to given time span. Will fire a zoom event with new zoom
|
||||
* bounds. Zoom bounds emitted in this way are considered ephemeral
|
||||
* and should be overridden by any time conductor bounds events. Does
|
||||
* not set bounds globally.
|
||||
* @param {number} zoom A time duration in ms
|
||||
* @fires platform.features.conductor.TimeConductorViewService~zoom
|
||||
* @see module:openmct.TimeConductor#bounds
|
||||
*/
|
||||
TimeConductorViewService.prototype.zoom = function (timeSpan) {
|
||||
var zoom = {};
|
||||
|
||||
// If a tick source is defined, then the concept of 'now' is
|
||||
// important. Calculate zoom based on 'now'.
|
||||
if (this.timeAPI.clock() !== undefined) {
|
||||
zoom.offsets = {
|
||||
start: -timeSpan,
|
||||
end: this.timeAPI.clockOffsets().end
|
||||
};
|
||||
|
||||
var currentVal = this.timeAPI.clock().currentValue();
|
||||
|
||||
zoom.bounds = {
|
||||
start: currentVal + zoom.offsets.start,
|
||||
end: currentVal + zoom.offsets.end
|
||||
};
|
||||
} else {
|
||||
var bounds = this.timeAPI.bounds();
|
||||
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
|
||||
bounds.start = center - timeSpan / 2;
|
||||
bounds.end = center + timeSpan / 2;
|
||||
zoom.bounds = bounds;
|
||||
}
|
||||
|
||||
this.emit("zoom", zoom);
|
||||
return zoom;
|
||||
};
|
||||
|
||||
return TimeConductorViewService;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,109 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Controller for the Time of Interest element used in various views to display the TOI. Responsible for setting
|
||||
* the text label for the current TOI, and for toggling the (un)pinned state which determines whether the TOI
|
||||
* indicator is visible.
|
||||
* @constructor
|
||||
*/
|
||||
function TimeOfInterestController($scope, openmct, formatService) {
|
||||
this.timeAPI = openmct.time;
|
||||
this.formatService = formatService;
|
||||
this.format = undefined;
|
||||
this.toiText = undefined;
|
||||
this.$scope = $scope;
|
||||
|
||||
//Bind all class functions to 'this'
|
||||
Object.keys(TimeOfInterestController.prototype).filter(function (key) {
|
||||
return typeof TimeOfInterestController.prototype[key] === 'function';
|
||||
}).forEach(function (key) {
|
||||
this[key] = TimeOfInterestController.prototype[key].bind(this);
|
||||
}.bind(this));
|
||||
|
||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.timeAPI.on('timeSystem', this.changeTimeSystem);
|
||||
if (this.timeAPI.timeSystem() !== undefined) {
|
||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
||||
var toi = this.timeAPI.timeOfInterest();
|
||||
if (toi) {
|
||||
this.changeTimeOfInterest(toi);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', this.destroy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the time of interest changes on the conductor. Will pin (display) the TOI indicator, and set the
|
||||
* text using the default formatter of the currently active Time System.
|
||||
* @private
|
||||
* @param {integer} toi Current time of interest in ms
|
||||
*/
|
||||
TimeOfInterestController.prototype.changeTimeOfInterest = function (toi) {
|
||||
if (toi !== undefined) {
|
||||
this.$scope.pinned = true;
|
||||
this.toiText = this.format.format(toi);
|
||||
} else {
|
||||
this.$scope.pinned = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When time system is changed, update the formatter used to
|
||||
* display the current TOI label
|
||||
*/
|
||||
TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) {
|
||||
this.format = this.formatService.getFormat(timeSystem.timeFormat);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeOfInterestController.prototype.destroy = function () {
|
||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
||||
};
|
||||
|
||||
/**
|
||||
* Will unpin (hide) the TOI indicator. Has the effect of setting the time of interest to `undefined` on the
|
||||
* Time Conductor
|
||||
*/
|
||||
TimeOfInterestController.prototype.dismiss = function () {
|
||||
this.timeAPI.timeOfInterest(undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends out a time of interest event with the effect of resetting
|
||||
* the TOI displayed in views.
|
||||
*/
|
||||
TimeOfInterestController.prototype.resync = function () {
|
||||
this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest());
|
||||
};
|
||||
|
||||
return TimeOfInterestController;
|
||||
}
|
||||
);
|
||||
@@ -226,6 +226,7 @@ define([
|
||||
this.legacyRegistry = defaultRegistry;
|
||||
this.install(this.plugins.Plot());
|
||||
this.install(this.plugins.TelemetryTable());
|
||||
this.install(this.plugins.DisplayLayout());
|
||||
|
||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||
this.install(buildInfoPlugin(BUILD_CONSTANTS));
|
||||
@@ -316,6 +317,7 @@ define([
|
||||
}
|
||||
});
|
||||
domElement.appendChild(appLayout.$mount().$el);
|
||||
|
||||
this.layout = appLayout;
|
||||
Browse(this);
|
||||
this.router.start();
|
||||
|
||||
@@ -3,16 +3,6 @@ define([
|
||||
], function (
|
||||
|
||||
) {
|
||||
const DEFAULT_VIEW_PRIORITY = 100;
|
||||
|
||||
const PRIORITY_LEVELS = {
|
||||
"fallback": Number.NEGATIVE_INFINITY,
|
||||
"default": -100,
|
||||
"none": 0,
|
||||
"optional": DEFAULT_VIEW_PRIORITY,
|
||||
"preferred": 1000,
|
||||
"mandatory": Number.POSITIVE_INFINITY
|
||||
};
|
||||
|
||||
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
||||
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
||||
@@ -94,13 +84,6 @@ define([
|
||||
scope.$destroy();
|
||||
}
|
||||
}
|
||||
},
|
||||
priority: function () {
|
||||
let priority = legacyView.priority || DEFAULT_VIEW_PRIORITY;
|
||||
if (typeof priority === 'string') {
|
||||
priority = PRIORITY_LEVELS[priority];
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -55,6 +55,8 @@ define([
|
||||
'../platform/exporters/bundle',
|
||||
'../platform/features/clock/bundle',
|
||||
'../platform/features/fixed/bundle',
|
||||
'../platform/features/conductor/core/bundle',
|
||||
'../platform/features/conductor/compatibility/bundle',
|
||||
'../platform/features/imagery/bundle',
|
||||
'../platform/features/layout/bundle',
|
||||
'../platform/features/listview/bundle',
|
||||
|
||||
206
src/plugins/displayLayout/DisplayLayout.vue
Normal file
206
src/plugins/displayLayout/DisplayLayout.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="l-layout">
|
||||
<div class="l-layout__object">
|
||||
<!-- Background grid -->
|
||||
<div class="l-layout__grid-holder c-grid" v-if="!drilledIn">
|
||||
<div class="c-grid__x l-grid l-grid-x"
|
||||
v-if="gridSize[0] >= 3"
|
||||
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
|
||||
</div>
|
||||
<div class="c-grid__y l-grid l-grid-y"
|
||||
v-if="gridSize[1] >= 3"
|
||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
||||
</div>
|
||||
|
||||
<layout-frame v-for="item in frameItems"
|
||||
class="l-layout__frame"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
@drilledIn="updateDrilledInState"
|
||||
@selected="updateSelectedState">
|
||||
</layout-frame>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.l-layout,
|
||||
.c-grid,
|
||||
.c-grid__x,
|
||||
.c-grid__y {
|
||||
@include abs();
|
||||
}
|
||||
|
||||
.l-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__grid-holder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__object {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__frame {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.c-grid {
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
|
||||
&__x { @include bgTicks($colorGridLines, 'x'); }
|
||||
&__y { @include bgTicks($colorGridLines, 'y'); }
|
||||
}
|
||||
|
||||
.is-editing {
|
||||
.l-layout {
|
||||
background: rgba($colorKey, 0.1);
|
||||
|
||||
&.s-selected,
|
||||
&.s-selected-parent {
|
||||
[class*="__grid-holder"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue';
|
||||
|
||||
const DEFAULT_GRID_SIZE = [32, 32],
|
||||
DEFAULT_DIMENSIONS = [12, 8],
|
||||
MINIMUM_FRAME_SIZE = [320, 180];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
gridSize: DEFAULT_GRID_SIZE,
|
||||
frameItems: [],
|
||||
frames: [],
|
||||
composition: Object,
|
||||
frameStyles: [],
|
||||
rawPositions: {},
|
||||
isEditing: true,
|
||||
initSelect: true,
|
||||
drilledIn: undefined
|
||||
}
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: ['domainObject'],
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
created: function () {
|
||||
console.log("domainObject", JSON.parse(JSON.stringify(this.domainObject)));
|
||||
|
||||
this.populatePositions(this.domainObject.configuration.layout.panels);
|
||||
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.composition.on('add', this.onAddComposition);
|
||||
this.composition.on('remove', this.onRemoveComposition);
|
||||
this.composition.load();
|
||||
},
|
||||
methods: {
|
||||
onAddComposition(domainObject) {
|
||||
console.log('composition object', domainObject);
|
||||
const id = this.openmct.objects.makeKeyString(domainObject.identifier)
|
||||
this.frameItems.push({
|
||||
id: id,
|
||||
hasFrame: this.hasFrame(id),
|
||||
domainObject,
|
||||
style: this.frameStyles[id],
|
||||
drilledIn: this.isDrilledIn(id),
|
||||
selected: false
|
||||
});
|
||||
},
|
||||
onRemoveComposition(identifier) {
|
||||
// TODO: remove the object from frameItems
|
||||
},
|
||||
populatePositions(panels) {
|
||||
Object.keys(panels).forEach(function (key, index) {
|
||||
this.rawPositions[key] = {
|
||||
position: panels[key].position || this.defaultPosition(index),
|
||||
dimensions: panels[key].dimensions || this.defaultDimensions()
|
||||
};
|
||||
this.frameStyles[key] = this.convertPosition(this.rawPositions[key]);
|
||||
this.frames[key] = panels[key].hasFrame;
|
||||
}.bind(this));
|
||||
},
|
||||
defaultPosition(index) {
|
||||
return [index, index];
|
||||
},
|
||||
defaultDimensions() {
|
||||
let gridSize = this.gridSize;
|
||||
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
||||
return Math.max(
|
||||
Math.ceil(min / gridSize[i]),
|
||||
DEFAULT_DIMENSIONS[i]
|
||||
);
|
||||
});
|
||||
},
|
||||
convertPosition(raw) {
|
||||
return {
|
||||
left: (this.gridSize[0] * raw.position[0]) + 'px',
|
||||
top: (this.gridSize[1] * raw.position[1]) + 'px',
|
||||
width: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
height: (this.gridSize[1] * raw.dimensions[1]) + 'px',
|
||||
minWidth: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
|
||||
};
|
||||
},
|
||||
hasFrame(id) {
|
||||
return this.frames[id]
|
||||
},
|
||||
setSelection(selection) {
|
||||
if (selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateDrilledInState();
|
||||
this.updateSelectedState();
|
||||
},
|
||||
updateDrilledInState(id) {
|
||||
this.drilledIn = id;
|
||||
this.frameItems.forEach(function (item) {
|
||||
item.drilledIn = item.id === id;
|
||||
});
|
||||
},
|
||||
updateSelectedState(id) {
|
||||
this.frameItems.forEach(function (item) {
|
||||
item.selected = item.id === id;
|
||||
});
|
||||
},
|
||||
isDrilledIn(id) {
|
||||
return this.drilledIn === id;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el,
|
||||
{
|
||||
item: this.domainObject
|
||||
},
|
||||
this.initSelect
|
||||
);
|
||||
|
||||
this.openmct.selection.on('change', this.setSelection);
|
||||
},
|
||||
destroyed: function () {
|
||||
this.composition.off('add', this.onAddComposition);
|
||||
this.composition.off('remove', this.onRemoveComposition);
|
||||
this.openmct.off('change', this.selection);
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
251
src/plugins/displayLayout/LayoutFrame.vue
Normal file
251
src/plugins/displayLayout/LayoutFrame.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<!-- - TODO: styles for selectable, moveable, etc. -->
|
||||
<div class="c-frame has-local-controls"
|
||||
:style="item.style"
|
||||
:class="classObject"
|
||||
@dblclick="drill(item.id, $event)">
|
||||
<div class="c-frame__header">
|
||||
<div class="c-frame__header__start">
|
||||
<div class="c-frame__name icon-object">Header</div>
|
||||
<div class="c-frame__context-actions c-disclosure-button"></div>
|
||||
</div>
|
||||
<div class="c-frame__header__end">
|
||||
<div class="c-button icon-expand local-controls--hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<object-view
|
||||
class="c-frame__object-view"
|
||||
:object="item.domainObject"></object-view>
|
||||
|
||||
<!-- Drag handles -->
|
||||
<div class="c-frame-edit">
|
||||
<div class="c-frame-edit__move"></div>
|
||||
<div class="c-frame-edit__handle --nw"></div>
|
||||
<div class="c-frame-edit__handle --ne"></div>
|
||||
<div class="c-frame-edit__handle --se"></div>
|
||||
<div class="c-frame-edit__handle --sw"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* FRAME */
|
||||
.c-frame {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
/*************************** HEADER */
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: $interiorMargin;
|
||||
|
||||
> [class*="__"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[class*="__start"] {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
[class*="__end"] {
|
||||
justify-content: flex-end;
|
||||
flex: 1 1 auto;
|
||||
|
||||
[class*="button"] {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
flex: 0 1 auto;
|
||||
font-size: 1.2em;
|
||||
|
||||
&:before {
|
||||
// Object type icon
|
||||
flex: 0 0 auto;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** OBJECT VIEW */
|
||||
&__object-view {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/*************************** NO-FRAME */
|
||||
&.no-frame {
|
||||
[class*="__header"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.no-frame) {
|
||||
background: $colorBodyBg;
|
||||
border: 1px solid $colorInteriorBorder;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
|
||||
/*************************** SELECTION */
|
||||
&.s-selected {
|
||||
//Legacy name for now
|
||||
border-color: $colorKey;
|
||||
}
|
||||
|
||||
&.is-drilled-in {
|
||||
border: 1px dashed deeppink;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** EDITING */
|
||||
.is-editing .c-frame:not(.is-drilled-in) {
|
||||
border: 1px dotted rgba($colorKey, 0.5);
|
||||
|
||||
&.s-selected {
|
||||
> .c-frame-edit {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-frame-edit {
|
||||
$z: 10;
|
||||
|
||||
@include abs();
|
||||
box-shadow: rgba($colorKey, 1) 0 0 10px;
|
||||
display: none;
|
||||
|
||||
&__move {
|
||||
@include abs();
|
||||
cursor: move;
|
||||
z-index: $z;
|
||||
}
|
||||
|
||||
&__handle {
|
||||
$d: 8px;
|
||||
$o: floor($d * -0.5);
|
||||
background: rgba($colorKey, 0.3);
|
||||
border: 1px solid $colorKey;
|
||||
position: absolute;
|
||||
width: $d; height: $d;
|
||||
top: auto; right: auto; bottom: auto; left: auto;
|
||||
z-index: $z + 1;
|
||||
|
||||
&:before {
|
||||
// Extended hit area
|
||||
$m: -5px;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: $m; right: $m; bottom: $m; left: $m;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $colorKey;
|
||||
}
|
||||
|
||||
&.--nw {
|
||||
cursor: nw-resize;
|
||||
left: $o; top: $o;
|
||||
}
|
||||
|
||||
&.--ne {
|
||||
cursor: ne-resize;
|
||||
right: $o; top: $o;
|
||||
}
|
||||
|
||||
&.--se {
|
||||
cursor: se-resize;
|
||||
right: $o; bottom: $o;
|
||||
}
|
||||
|
||||
&.--sw {
|
||||
cursor: sw-resize;
|
||||
left: $o; bottom: $o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import ObjectView from '../../ui/components/layout/ObjectView.vue'
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object
|
||||
},
|
||||
components: {
|
||||
ObjectView
|
||||
},
|
||||
computed: {
|
||||
classObject: function () {
|
||||
return {
|
||||
'is-drilled-in': this.item.drilledIn,
|
||||
'no-frame': !this.item.hasFrame
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setSelection(selection) {
|
||||
if (selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this.openmct.objects.makeKeyString(selection[0].context.item.identifier);
|
||||
if (this.item.id === id) {
|
||||
this.$emit('selected', id);
|
||||
}
|
||||
},
|
||||
drill(id, $event) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
if (!this.isBeingEdited(this.item.domainObject)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.openmct.composition.get(this.item.domainObject) === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable for fixed position.
|
||||
if (this.item.domainObject.type === 'telemetry.fixed') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('drilledIn', id);
|
||||
},
|
||||
isBeingEdited(object) {
|
||||
// TODO: add logic when inEditContext() is implemented in Vue.
|
||||
return true;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el,
|
||||
{
|
||||
item: this.item.domainObject
|
||||
},
|
||||
this.item.selected
|
||||
);
|
||||
|
||||
this.openmct.selection.on('change', this.setSelection);
|
||||
},
|
||||
destroyed() {
|
||||
this.openmct.off('change', this.selection);
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
45
src/plugins/displayLayout/plugin.js
Normal file
45
src/plugins/displayLayout/plugin.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import Layout from './DisplayLayout.vue'
|
||||
import Vue from 'vue'
|
||||
import objectUtils from '../../api/objects/object-utils.js'
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
console.log("Installing Layout component...");
|
||||
|
||||
openmct.objectViews.addProvider({
|
||||
key: 'layout.view',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'layout';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
return {
|
||||
show(container) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
Layout
|
||||
},
|
||||
template: '<layout :domain-object="domainObject"></layout>',
|
||||
provide: {
|
||||
openmct,
|
||||
objectUtils
|
||||
},
|
||||
el: container,
|
||||
data () {
|
||||
return {
|
||||
domainObject: domainObject
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
destroy() {
|
||||
component.$destroy();
|
||||
}
|
||||
};
|
||||
},
|
||||
priority() {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./components/GridView.vue',
|
||||
'vue'
|
||||
], function (
|
||||
GridViewComponent,
|
||||
Vue
|
||||
) {
|
||||
function FolderGridView(openmct) {
|
||||
return {
|
||||
key: 'grid',
|
||||
name: 'Grid Vue',
|
||||
cssClass: 'icon-thumbs-strip',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'folder';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
gridViewComponent: GridViewComponent.default
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
el: element,
|
||||
template: '<grid-view-component></grid-view-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
return FolderGridView;
|
||||
});
|
||||
@@ -1,70 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./components/ListView.vue',
|
||||
'vue',
|
||||
'moment'
|
||||
], function (
|
||||
ListViewComponent,
|
||||
Vue,
|
||||
Moment
|
||||
) {
|
||||
function FolderListView(openmct) {
|
||||
return {
|
||||
key: 'list-view',
|
||||
name: 'List Vue',
|
||||
cssClass: 'icon-list-view',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'folder';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
listViewComponent: ListViewComponent.default
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
Moment
|
||||
},
|
||||
el: element,
|
||||
template: '<list-view-component></list-view-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
return FolderListView;
|
||||
});
|
||||
@@ -1,207 +0,0 @@
|
||||
<template>
|
||||
<div class="l-grid-view">
|
||||
<div v-for="(item, index) in items"
|
||||
v-bind:key="index"
|
||||
class="l-grid-view__item c-grid-item"
|
||||
:class="{ 'is-alias': item.isAlias === true }"
|
||||
@click="navigate(item.model.identifier.key)">
|
||||
<div class="c-grid-item__type-icon"
|
||||
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'">
|
||||
</div>
|
||||
<div class="c-grid-item__details">
|
||||
<!-- Name and metadata -->
|
||||
<div class="c-grid-item__name"
|
||||
:title="item.model.name">{{item.model.name}}</div>
|
||||
<div class="c-grid-item__metadata"
|
||||
:title="item.type.name">
|
||||
<span>{{item.type.name}}</span>
|
||||
<span v-if="item.model.composition !== undefined">
|
||||
- {{item.model.composition.length}} item<span v-if="item.model.composition.length !== 1">s</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-grid-item__controls">
|
||||
<div class="icon-people" title='Shared'></div>
|
||||
<div class="c-click-icon icon-info c-info-button" title='More Info'></div>
|
||||
<div class="icon-pointer-right c-pointer-icon"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* GRID VIEW */
|
||||
.l-grid-view {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
&__item {
|
||||
flex: 0 0 auto;
|
||||
+ .l-grid-view__item { margin-top: $interiorMargin; }
|
||||
}
|
||||
|
||||
body.desktop & {
|
||||
flex-flow: row wrap;
|
||||
&__item {
|
||||
height: $ueBrowseGridItemLg;
|
||||
width: $ueBrowseGridItemLg;
|
||||
margin: 0 $interiorMargin $interiorMargin 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************* GRID ITEMS */
|
||||
.c-grid-item {
|
||||
// Mobile-first
|
||||
@include button($bg: $colorItemBg, $fg: $colorItemFg);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: $interiorMarginLg;
|
||||
|
||||
&__type-icon {
|
||||
filter: $colorKeyFilter;
|
||||
flex: 0 0 32px;
|
||||
margin-right: $interiorMarginLg;
|
||||
}
|
||||
|
||||
&.is-alias {
|
||||
// Object is an alias to an original.
|
||||
[class*='__type-icon'] {
|
||||
&:before {
|
||||
color: $colorIconAliasForKeyFilter;
|
||||
content: $glyph-icon-link;
|
||||
display: block;
|
||||
font-family: symbolsfont;
|
||||
font-size: 2.5em;
|
||||
position: absolute;
|
||||
text-shadow: rgba(black, 0.5) 0 1px 4px;
|
||||
top: auto; left: 0; bottom: 10px; right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__details {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
color: $colorItemFg;
|
||||
font-size: 1.3em;
|
||||
font-weight: 400;
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&__metadata {
|
||||
color: $colorItemFgDetails;
|
||||
}
|
||||
|
||||
&__controls {
|
||||
color: $colorItemFgDetails;
|
||||
flex: 0 0 64px;
|
||||
font-size: 1.2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop & {
|
||||
$transOutMs: 300ms;
|
||||
flex-flow: column nowrap;
|
||||
transition: background $transOutMs ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background: $colorItemBgHov;
|
||||
transition: $transIn;
|
||||
|
||||
.c-grid-item__type-icon {
|
||||
filter: $colorKeyFilterHov;
|
||||
transform: scale(1);
|
||||
transition: $transInBounce;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
margin: 0; // Reset from mobile
|
||||
}
|
||||
|
||||
&__controls {
|
||||
align-items: start;
|
||||
flex: 0 0 auto;
|
||||
order: 1;
|
||||
.c-info-button,
|
||||
.c-pointer-icon { display: none; }
|
||||
}
|
||||
|
||||
&__type-icon {
|
||||
flex: 1 1 auto;
|
||||
margin: $interiorMargin 22.5%;
|
||||
order: 2;
|
||||
transform: scale(0.9);
|
||||
transform-origin: center;
|
||||
transition: all $transOutMs ease-in-out;
|
||||
}
|
||||
|
||||
&__details {
|
||||
flex: 0 0 auto;
|
||||
justify-content: flex-end;
|
||||
order: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
var items = [],
|
||||
unknownObjectType = {
|
||||
definition: {
|
||||
cssClass: 'icon-object-unknown',
|
||||
name: 'Unknown Type'
|
||||
}
|
||||
};
|
||||
|
||||
var composition = this.openmct.composition.get(this.domainObject);
|
||||
|
||||
if (composition) {
|
||||
|
||||
composition.load().then((array) => {
|
||||
if (Array.isArray(array)) {
|
||||
array.forEach((model) => {
|
||||
var type = this.openmct.types.get(model.type) || unknownObjectType;
|
||||
|
||||
items.push({
|
||||
model: model,
|
||||
type: type.definition,
|
||||
isAlias: this.domainObject.identifier.key !== model.location
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
items: items
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigate(identifier) {
|
||||
let currentLocation = this.openmct.router.currentLocation.path,
|
||||
navigateToPath = `${currentLocation}/${identifier}`;
|
||||
|
||||
this.openmct.router.setPath(navigateToPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,215 +0,0 @@
|
||||
<template>
|
||||
<div class="c-table c-table--sortable c-list-view">
|
||||
<table class="c-table__body">
|
||||
<thead class="c-table__header">
|
||||
<tr>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'name' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('name', 'asc')">
|
||||
Name
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'type' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('type', 'asc')">
|
||||
Type
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'createdDate' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('createdDate', 'desc')">
|
||||
Created Date
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'updatedDate' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('updatedDate', 'desc')">
|
||||
Updated Date
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'items' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('items', 'asc')">
|
||||
Items
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="c-list-item"
|
||||
v-for="(item,index) in sortedItems"
|
||||
v-bind:key="index"
|
||||
:class="{ 'is-alias': item.isAlias === true }"
|
||||
@click="navigate(item.identifier)">
|
||||
<td class="c-list-item__name">
|
||||
<div class="c-list-item__type-icon" :class="(item.cssClass != undefined) ? item.cssClass : 'icon-object-unknown'"></div>
|
||||
{{item.name}}
|
||||
</td>
|
||||
<td class="c-list-item__type">{{ item.type }}</td>
|
||||
<td class="c-list-item__date-created">{{ formatTime(item.createdDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||
<td class="c-list-item__date-updated">{{ formatTime(item.updatedDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||
<td class="c-list-item__items">{{ item.items }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* LIST VIEW */
|
||||
.c-list-view {
|
||||
overflow-x: auto !important;
|
||||
overflow-y: auto;
|
||||
|
||||
tbody tr {
|
||||
background: $colorListItemBg;
|
||||
transition: $transOut;
|
||||
}
|
||||
|
||||
body.desktop & {
|
||||
tbody tr {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: $colorListItemBgHov;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
$p: floor($interiorMargin * 1.5);
|
||||
font-size: 1.1em;
|
||||
padding-top: $p;
|
||||
padding-bottom: $p;
|
||||
|
||||
&:not(.c-list-item__name) {
|
||||
color: $colorItemFgDetails;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-list-item {
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
}
|
||||
|
||||
&__type-icon {
|
||||
color: $colorKey;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin-right:$interiorMarginSm;
|
||||
}
|
||||
|
||||
&.is-alias {
|
||||
// Object is an alias to an original.
|
||||
[class*='__type-icon'] {
|
||||
&:after {
|
||||
color: $colorIconAlias;
|
||||
content: $glyph-icon-link;
|
||||
font-family: symbolsfont;
|
||||
display: block;
|
||||
position: absolute;
|
||||
text-shadow: rgba(black, 0.5) 0 1px 2px;
|
||||
top: auto; left: -1px; bottom: 1px; right: auto;
|
||||
transform-origin: bottom left;
|
||||
transform: scale(0.65);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************* LIST ITEM */
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'Moment'],
|
||||
data() {
|
||||
var items = [],
|
||||
unknownObjectType = {
|
||||
definition: {
|
||||
cssClass: 'icon-object-unknown',
|
||||
name: 'Unknown Type'
|
||||
}
|
||||
},
|
||||
composition = this.openmct.composition.get(this.domainObject);
|
||||
|
||||
if (composition) {
|
||||
|
||||
composition.load().then((array) => {
|
||||
if (Array.isArray(array)) {
|
||||
array.forEach(model => {
|
||||
var type = this.openmct.types.get(model.type) || unknownObjectType;
|
||||
|
||||
items.push({
|
||||
name: model.name,
|
||||
identifier: model.identifier.key,
|
||||
type: type.definition.name,
|
||||
isAlias: false,
|
||||
cssClass: type.definition.cssClass,
|
||||
createdDate: model.persisted,
|
||||
updatedDate: model.modified,
|
||||
items: model.composition ? model.composition.length : 0,
|
||||
isAlias: this.domainObject.identifier.key !== model.location
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
items: items,
|
||||
orderByField: 'name',
|
||||
sortClass: 'asc',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sortedItems () {
|
||||
if (this.sortClass === 'asc') {
|
||||
return this.items.sort(this.ascending.bind(this));
|
||||
} else if (this.sortClass === 'desc') {
|
||||
return this.items.sort(this.descending.bind(this));
|
||||
}
|
||||
},
|
||||
formatTime () {
|
||||
return function (timestamp, format) {
|
||||
return this.Moment(timestamp).format(format);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigate(identifier) {
|
||||
let currentLocation = this.openmct.router.currentLocation.path,
|
||||
navigateToPath = `${currentLocation}/${identifier}`;
|
||||
|
||||
this.openmct.router.setPath(navigateToPath);
|
||||
},
|
||||
sortTrigger(field, sortOrder) {
|
||||
if (this.orderByField === field) {
|
||||
this.sortClass = (this.sortClass === 'asc') ? 'desc' : 'asc';
|
||||
} else {
|
||||
this.sortClass = sortOrder;
|
||||
}
|
||||
this.orderByField = field;
|
||||
},
|
||||
ascending(first, second) {
|
||||
if (first[this.orderByField] < second[this.orderByField]) {
|
||||
return -1;
|
||||
} else if (first[this.orderByField] > second[this.orderByField]) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
descending(first, second) {
|
||||
if (first[this.orderByField] > second[this.orderByField]) {
|
||||
return -1;
|
||||
} else if (first[this.orderByField] < second[this.orderByField]) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -51,12 +51,64 @@ define([
|
||||
function LocalTimeFormat() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriate time format based on the provided value and
|
||||
* the threshold required.
|
||||
* @private
|
||||
*/
|
||||
function getScaledFormat(d) {
|
||||
var momentified = moment.utc(d);
|
||||
/**
|
||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
||||
*
|
||||
* Licensed
|
||||
*/
|
||||
return [
|
||||
[".SSS", function (m) {
|
||||
return m.milliseconds();
|
||||
}],
|
||||
[":ss", function (m) {
|
||||
return m.seconds();
|
||||
}],
|
||||
["hh:mma", function (m) {
|
||||
return m.minutes();
|
||||
}],
|
||||
["hha", function (m) {
|
||||
return m.hours();
|
||||
}],
|
||||
["ddd DD", function (m) {
|
||||
return m.days() &&
|
||||
m.date() !== 1;
|
||||
}],
|
||||
["MMM DD", function (m) {
|
||||
return m.date() !== 1;
|
||||
}],
|
||||
["MMMM", function (m) {
|
||||
return m.month();
|
||||
}],
|
||||
["YYYY", function () {
|
||||
return true;
|
||||
}]
|
||||
].filter(function (row) {
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param {Scale} [scale] Optionally provides context to the
|
||||
* format request, allowing for scale-appropriate formatting.
|
||||
* @returns {string} the formatted date
|
||||
*/
|
||||
LocalTimeFormat.prototype.format = function (value, scale) {
|
||||
if (scale !== undefined) {
|
||||
var scaledFormat = getScaledFormat(value, scale);
|
||||
if (scaledFormat) {
|
||||
return moment.utc(value).format(scaledFormat);
|
||||
}
|
||||
}
|
||||
return moment(value).format(DATE_FORMAT);
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ define([], function () {
|
||||
this.timeFormat = 'local-format';
|
||||
this.durationFormat = 'duration';
|
||||
|
||||
this.isUTCBased = false;
|
||||
this.isUTCBased = true;
|
||||
}
|
||||
|
||||
return LocalTimeSystem;
|
||||
|
||||
@@ -35,7 +35,7 @@ define([
|
||||
'./telemetryTable/plugin',
|
||||
'./staticRootPlugin/plugin',
|
||||
'./notebook/plugin',
|
||||
'./folderView/plugin'
|
||||
'./displayLayout/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@@ -51,7 +51,7 @@ define([
|
||||
TelemetryTablePlugin,
|
||||
StaticRootPlugin,
|
||||
Notebook,
|
||||
FolderView
|
||||
DisplayLayoutPlugin
|
||||
) {
|
||||
var bundleMap = {
|
||||
LocalStorage: 'platform/persistence/local',
|
||||
@@ -103,7 +103,7 @@ define([
|
||||
*/
|
||||
plugins.AutoflowView = AutoflowPlugin;
|
||||
|
||||
plugins.Conductor = TimeConductorPlugin.default;
|
||||
plugins.Conductor = TimeConductorPlugin;
|
||||
|
||||
plugins.CouchDB = function (url) {
|
||||
return function (openmct) {
|
||||
@@ -161,7 +161,7 @@ define([
|
||||
plugins.TelemetryMean = TelemetryMean;
|
||||
plugins.URLIndicator = URLIndicatorPlugin;
|
||||
plugins.Notebook = Notebook;
|
||||
plugins.FolderView = FolderView;
|
||||
plugins.DisplayLayout = DisplayLayoutPlugin.default;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ define([
|
||||
objectUtils
|
||||
) {
|
||||
|
||||
const DEFAULT_VIEW_PRIORITY = 100;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -34,11 +34,7 @@ define([
|
||||
},
|
||||
editable: true,
|
||||
priority: function (domainObject) {
|
||||
if (domainObject.type === 'summary-widget') {
|
||||
return Number.MAX_VALUE;
|
||||
} else {
|
||||
return DEFAULT_VIEW_PRIORITY;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,12 +48,7 @@ define(function () {
|
||||
}
|
||||
|
||||
getFormattedValue(telemetryDatum) {
|
||||
let formattedValue = this.formatter.format(telemetryDatum);
|
||||
if (typeof formattedValue !== 'string') {
|
||||
return formattedValue.toString();
|
||||
} else {
|
||||
return formattedValue;
|
||||
}
|
||||
return this.formatter.format(telemetryDatum);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ define([], function () {
|
||||
|
||||
getFormattedValue(key) {
|
||||
let column = this.columns[key];
|
||||
return column && column.getFormattedValue(this.datum[key]);
|
||||
return column.getFormattedValue(this.datum[key]);
|
||||
}
|
||||
|
||||
getRowLimitClass() {
|
||||
|
||||
@@ -32,9 +32,7 @@ export default {
|
||||
columnWidths: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
default: [],
|
||||
},
|
||||
rowIndex: {
|
||||
type: Number,
|
||||
@@ -50,6 +48,10 @@ export default {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
configuration: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<!-- Headers table -->
|
||||
<div class="c-telemetry-table__headers-w js-table__headers-w">
|
||||
<div class="c-table__headers-w js-table__headers-w">
|
||||
<table class="c-table__headers c-telemetry-table__headers"
|
||||
:style="{ 'max-width': totalWidth + 'px'}">
|
||||
<thead>
|
||||
@@ -68,22 +68,48 @@
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
@import "~styles/table";
|
||||
|
||||
.c-telemetry-table {
|
||||
// Table that displays telemetry in a scrolling body area
|
||||
.c-table {
|
||||
// Can be used by any type of table, scrolling, LAD, etc.
|
||||
$min-w: 50px;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: 0; left: 0;
|
||||
|
||||
&__control-bar,
|
||||
&__headers-w {
|
||||
// Don't allow top level elements to grow or shrink
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/******************************* ELEMENTS */
|
||||
th, td {
|
||||
display: block;
|
||||
flex: 1 0 auto;
|
||||
font-size: 0.7rem; // TEMP LEGACY TODO: refactor this when __main-container font-size is dealt with
|
||||
white-space: nowrap;
|
||||
min-width: $min-w;
|
||||
padding: $tabularTdPadTB $tabularTdPadLR;
|
||||
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default
|
||||
}
|
||||
|
||||
td {
|
||||
color: $colorTelemFresh;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&__control-bar {
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
/******************************* WRAPPERS */
|
||||
&__headers-w {
|
||||
// Wraps __headers table
|
||||
flex: 0 0 auto;
|
||||
background: $colorTabHeaderBg;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -109,6 +135,65 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
// A table
|
||||
tr {
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid $colorTabBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************* MODIFIERS */
|
||||
&--filterable {
|
||||
// TODO: discuss using the search.vue custom control here
|
||||
|
||||
.l-filter {
|
||||
input[type="text"],
|
||||
input[type="search"] {
|
||||
$p: 20px;
|
||||
transition: padding 200ms ease-in-out;
|
||||
box-sizing: border-box;
|
||||
padding-right: $p; // Fend off from icon
|
||||
padding-left: $p; // Fend off from icon
|
||||
width: 100%;
|
||||
}
|
||||
&.active {
|
||||
// When user has typed something, hide the icon and collapse left padding
|
||||
&:before {
|
||||
opacity: 0;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="search"] {
|
||||
padding-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--sortable {
|
||||
.is-sorting {
|
||||
&:after {
|
||||
color: $colorIconLink;
|
||||
content: $glyph-icon-arrow-tall-up;
|
||||
font-family: symbolsfont;
|
||||
font-size: 8px;
|
||||
display: inline-block;
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
&.desc:after {
|
||||
content: $glyph-icon-arrow-tall-down;
|
||||
}
|
||||
}
|
||||
.is-sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-telemetry-table {
|
||||
// Table that displays telemetry in a scrolling body area
|
||||
|
||||
/******************************* ELEMENTS */
|
||||
&__scroll-forcer {
|
||||
// Force horz scroll when needed; width set via JS
|
||||
@@ -166,6 +251,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.c-table__control-bar {
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
/******************************* LEGACY */
|
||||
.s-status-taking-snapshot,
|
||||
.overlay.snapshot {
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="c-conductor"
|
||||
:class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode', panning ? 'status-panning' : '']">
|
||||
<form class="u-contents" ref="conductorForm"
|
||||
@submit="isFixed ? setBoundsFromView($event) : setOffsetsFromView($event)">
|
||||
|
||||
<ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon>
|
||||
|
||||
<div class="c-conductor__start-input">
|
||||
<!-- Start input and controls -->
|
||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start__fixed"
|
||||
v-if="isFixed">
|
||||
<!-- Fixed input -->
|
||||
<div class="c-conductor__start__fixed__label">Start</div>
|
||||
<input class="c-input--datetime"
|
||||
type="text" autocorrect="off" spellcheck="false"
|
||||
ref="startDate"
|
||||
v-model="formattedBounds.start"
|
||||
@change="validateBounds('start', $event.target); setBoundsFromView()" />
|
||||
<date-picker
|
||||
:default-date-time="formattedBounds.start"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="startDateSelected"></date-picker>
|
||||
</div>
|
||||
|
||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start__delta"
|
||||
v-if="!isFixed">
|
||||
<!-- RT input -->
|
||||
<div class="c-direction-indicator icon-minus"></div>
|
||||
<input class="c-input--hrs-min-sec"
|
||||
type="text" autocorrect="off"
|
||||
spellcheck="false"
|
||||
v-model="offsets.start"
|
||||
@change="validateOffsets($event); setOffsetsFromView()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-conductor__end-input">
|
||||
<!-- End input and controls -->
|
||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end__fixed"
|
||||
v-if="isFixed">
|
||||
<!-- Fixed input -->
|
||||
<div class="c-conductor__end__fixed__label">End</div>
|
||||
<input class="c-input--datetime"
|
||||
type="text" autocorrect="off" spellcheck="false"
|
||||
v-model="formattedBounds.end"
|
||||
:disabled="!isFixed"
|
||||
ref="endDate"
|
||||
@change="validateBounds('end', $event.target); setBoundsFromView()">
|
||||
<date-picker
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
:default-date-time="formattedBounds.end"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="endDateSelected"></date-picker>
|
||||
</div>
|
||||
|
||||
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end__delta"
|
||||
v-if="!isFixed">
|
||||
<!-- RT input -->
|
||||
<div class="c-direction-indicator icon-plus"></div>
|
||||
<input class="c-input--hrs-min-sec"
|
||||
type="text"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
v-model="offsets.end"
|
||||
@change="validateOffsets($event); setOffsetsFromView()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<conductor-axis
|
||||
class="c-conductor__ticks"
|
||||
:bounds="rawBounds"
|
||||
@panAxis="setViewFromBounds"></conductor-axis>
|
||||
<div class="c-conductor__controls">
|
||||
<!-- Mode, time system menu buttons and duration slider -->
|
||||
<ConductorMode></ConductorMode>
|
||||
<ConductorTimeSystem></ConductorTimeSystem>
|
||||
</div>
|
||||
<input type="submit" class="invisible">
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/*********************************************** CONDUCTOR LAYOUT */
|
||||
.c-conductor {
|
||||
display: grid;
|
||||
grid-column-gap: $interiorMargin;
|
||||
grid-row-gap: $interiorMargin;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-template-columns: 20px auto 1fr auto;
|
||||
grid-template-areas:
|
||||
"tc-mode-icon tc-start tc-ticks tc-end"
|
||||
"tc-controls tc-controls tc-controls tc-controls";
|
||||
align-items: center;
|
||||
|
||||
/* grid-template-columns: 20px 160px 1fr 180px;
|
||||
grid-template-areas:
|
||||
"tc-mode-icon tc-controls tc-controls tc-controls"
|
||||
"tc-start tc-start tc-ticks tc-end";*/
|
||||
|
||||
&__mode-icon {
|
||||
grid-area: tc-mode-icon;
|
||||
}
|
||||
|
||||
&__start-input,
|
||||
&__end-input {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__start-input {
|
||||
grid-area: tc-start;
|
||||
}
|
||||
|
||||
&__end-input {
|
||||
grid-area: tc-end;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__ticks {
|
||||
grid-area: tc-ticks;
|
||||
}
|
||||
|
||||
&__controls {
|
||||
grid-area: tc-controls;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
[class*='__delta'] {
|
||||
&:before {
|
||||
content: $glyph-icon-clock;
|
||||
font-family: symbolsfont;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-conductor-input {
|
||||
color: $colorInputFg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&:before {
|
||||
// Realtime-mode clock icon symbol
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
|
||||
.c-direction-indicator {
|
||||
// Holds realtime-mode + and - symbols
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
input:invalid {
|
||||
background: rgba($colorFormInvalid, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.is-realtime-mode {
|
||||
.c-conductor-input {
|
||||
&:before {
|
||||
color: $colorTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import ConductorMode from './ConductorMode.vue';
|
||||
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
||||
import DatePicker from './DatePicker.vue';
|
||||
import ConductorAxis from './ConductorAxis.vue';
|
||||
import ConductorModeIcon from './ConductorModeIcon.vue';
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
const SECONDS = 1000;
|
||||
const DAYS = 24 * 60 * 60 * SECONDS;
|
||||
const YEARS = 365 * DAYS;
|
||||
|
||||
const RESIZE_POLL_INTERVAL = 200;
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'configuration'],
|
||||
components: {
|
||||
ConductorMode,
|
||||
ConductorTimeSystem,
|
||||
DatePicker,
|
||||
ConductorAxis,
|
||||
ConductorModeIcon
|
||||
},
|
||||
data() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let offsets = this.openmct.time.clockOffsets();
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
|
||||
return {
|
||||
timeFormatter: timeFormatter,
|
||||
durationFormatter: durationFormatter,
|
||||
offsets: {
|
||||
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
|
||||
end: offsets && durationFormatter.format(Math.abs(offsets.end)),
|
||||
},
|
||||
formattedBounds: {
|
||||
start: timeFormatter.format(bounds.start),
|
||||
end: timeFormatter.format(bounds.end)
|
||||
},
|
||||
rawBounds: {
|
||||
start: bounds.start,
|
||||
end: bounds.end
|
||||
},
|
||||
isFixed: this.openmct.time.clock() === undefined,
|
||||
isUTCBased: timeSystem.isUTCBased,
|
||||
showDatePicker: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setTimeSystem(timeSystem) {
|
||||
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
|
||||
this.isUTCBased = timeSystem.isUTCBased;
|
||||
},
|
||||
setOffsetsFromView($event) {
|
||||
if (this.$refs.conductorForm.checkValidity()){
|
||||
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
||||
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
||||
|
||||
this.openmct.time.clockOffsets({
|
||||
start: startOffset,
|
||||
end: endOffset
|
||||
});
|
||||
}
|
||||
if ($event) {
|
||||
$event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
setBoundsFromView($event) {
|
||||
if (this.$refs.conductorForm.checkValidity()){
|
||||
let start = this.timeFormatter.parse(this.formattedBounds.start);
|
||||
let end = this.timeFormatter.parse(this.formattedBounds.end);
|
||||
|
||||
this.openmct.time.bounds({
|
||||
start: start,
|
||||
end: end
|
||||
});
|
||||
}
|
||||
if ($event) {
|
||||
$event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
setViewFromClock(clock) {
|
||||
this.isFixed = clock === undefined;
|
||||
},
|
||||
setViewFromBounds(bounds) {
|
||||
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||
this.rawBounds.start = bounds.start;
|
||||
this.rawBounds.end = bounds.end;
|
||||
},
|
||||
setViewFromOffsets(offsets) {
|
||||
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
|
||||
this.offsets.end = this.durationFormatter.format(Math.abs(offsets.end));
|
||||
},
|
||||
validateBounds(startOrEnd, input) {
|
||||
let validationResult = true;
|
||||
|
||||
if (!this.timeFormatter.validate(input.value)){
|
||||
validationResult = 'Invalid date value';
|
||||
} else {
|
||||
let boundsValues = {
|
||||
start: this.timeFormatter.parse(this.formattedBounds.start),
|
||||
end: this.timeFormatter.parse(this.formattedBounds.end)
|
||||
};
|
||||
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||
}
|
||||
|
||||
if (validationResult !== true){
|
||||
input.setCustomValidity(validationResult);
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
}
|
||||
},
|
||||
validateOffsets(event) {
|
||||
let input = event.target;
|
||||
let validationResult = true;
|
||||
|
||||
if (!this.durationFormatter.validate(input.value)) {
|
||||
validationResult = 'Invalid offset value';
|
||||
} else {
|
||||
let offsetValues = {
|
||||
start: 0 - this.durationFormatter.parse(this.offsets.start),
|
||||
end: this.durationFormatter.parse(this.offsets.end)
|
||||
};
|
||||
validationResult = this.openmct.time.validateOffsets(offsetValues);
|
||||
}
|
||||
|
||||
if (validationResult !== true){
|
||||
input.setCustomValidity(validationResult);
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
}
|
||||
|
||||
},
|
||||
getFormatter(key) {
|
||||
return this.openmct.telemetry.getValueFormatter({
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
startDateSelected(date){
|
||||
this.formattedBounds.start = this.timeFormatter.format(date);
|
||||
this.validateBounds('start', this.$refs.startDate);
|
||||
this.setBoundsFromView();
|
||||
},
|
||||
endDateSelected(date){
|
||||
this.formattedBounds.end = this.timeFormatter.format(date);
|
||||
this.validateBounds('end', this.$refs.endDate);
|
||||
this.setBoundsFromView();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
||||
|
||||
this.openmct.time.on('bounds', this.setViewFromBounds);
|
||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
||||
this.openmct.time.on('clock', this.setViewFromClock);
|
||||
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="c-conductor-axis"
|
||||
ref="axisHolder"
|
||||
@mousedown="dragStart($event)">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.c-conductor-axis {
|
||||
$h: 18px;
|
||||
$tickYPos: ($h / 2) + 12px;
|
||||
|
||||
@include userSelectNone();
|
||||
@include bgTicks($c: rgba($colorBodyFg, 0.4));
|
||||
background-position: 0 50%;
|
||||
height: $h;
|
||||
|
||||
svg {
|
||||
text-rendering: geometricPrecision;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
> g {
|
||||
// Overall Tick holder
|
||||
transform: translateY($tickYPos);
|
||||
path {
|
||||
// Domain line
|
||||
display: none;
|
||||
}
|
||||
|
||||
g {
|
||||
// Each tick. These move on drag.
|
||||
line {
|
||||
// Line beneath ticks
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text {
|
||||
// Tick labels
|
||||
font-size: 1em;
|
||||
paint-order: stroke;
|
||||
font-weight: bold;
|
||||
stroke-linecap: butt;
|
||||
stroke-linejoin: bevel;
|
||||
stroke-width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.is-fixed-mode & {
|
||||
@include cursorGrab();
|
||||
background-size: 3px 30%;
|
||||
border-radius: $controlCr;
|
||||
background-color: $colorBodyBgSubtle;
|
||||
box-shadow: inset rgba(black, 0.2) 0 1px 1px;
|
||||
|
||||
svg text {
|
||||
fill: $colorBodyFg;
|
||||
stroke: $colorBodyBgSubtle;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
$c: $colorKeySubtle;
|
||||
background-color: $c;
|
||||
svg text {
|
||||
stroke: $c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-realtime-mode & {
|
||||
background-size: 5px 2px;
|
||||
|
||||
svg text {
|
||||
fill: $colorTime;
|
||||
stroke: $colorBodyBg;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
import * as d3Selection from 'd3-selection';
|
||||
import * as d3Axis from 'd3-axis';
|
||||
import * as d3Scale from 'd3-scale';
|
||||
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
||||
|
||||
const PADDING = 1;
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
const RESIZE_POLL_INTERVAL = 200;
|
||||
const PIXELS_PER_TICK = 100;
|
||||
const PIXELS_PER_TICK_WIDE = 200;
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
bounds: Object
|
||||
},
|
||||
methods: {
|
||||
setScale() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let bounds = this.bounds;
|
||||
|
||||
if (timeSystem.isUTCBased) {
|
||||
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
||||
} else {
|
||||
this.xScale.domain([bounds.start, bounds.end]);
|
||||
}
|
||||
|
||||
this.xAxis.scale(this.xScale);
|
||||
|
||||
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
||||
this.axisElement.call(this.xAxis);
|
||||
|
||||
if (this.width > 1800) {
|
||||
this.xAxis.ticks(this.width / PIXELS_PER_TICK_WIDE);
|
||||
} else {
|
||||
this.xAxis.ticks(this.width / PIXELS_PER_TICK);
|
||||
}
|
||||
|
||||
this.msPerPixel = (bounds.end - bounds.start) / this.width;
|
||||
},
|
||||
setViewFromTimeSystem(timeSystem) {
|
||||
let format = this.getActiveFormatter();
|
||||
let bounds = this.openmct.time.bounds();
|
||||
|
||||
//The D3 scale used depends on the type of time system as d3
|
||||
// supports UTC out of the box.
|
||||
if (timeSystem.isUTCBased) {
|
||||
this.xScale = d3Scale.scaleUtc();
|
||||
} else {
|
||||
this.xScale = d3Scale.scaleLinear();
|
||||
}
|
||||
|
||||
this.xAxis.scale(this.xScale);
|
||||
this.xAxis.tickFormat(utcMultiTimeFormat);
|
||||
this.axisElement.call(this.xAxis);
|
||||
},
|
||||
getActiveFormatter() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let isFixed = this.openmct.time.clock() === undefined;
|
||||
|
||||
if (isFixed) {
|
||||
return this.getFormatter(timeSystem.timeFormat);
|
||||
} else {
|
||||
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
}
|
||||
},
|
||||
getFormatter(key) {
|
||||
return this.openmct.telemetry.getValueFormatter({
|
||||
format: key
|
||||
}).formatter;
|
||||
},
|
||||
dragStart($event){
|
||||
let isFixed = this.openmct.time.clock() === undefined;
|
||||
if (isFixed){
|
||||
this.dragStartX = $event.clientX;
|
||||
|
||||
document.addEventListener('mousemove', this.drag);
|
||||
document.addEventListener('mouseup', this.dragEnd, {
|
||||
once: true
|
||||
});
|
||||
}
|
||||
},
|
||||
drag($event) {
|
||||
if (!this.dragging){
|
||||
this.dragging = true;
|
||||
requestAnimationFrame(()=>{
|
||||
let deltaX = $event.clientX - this.dragStartX;
|
||||
let percX = deltaX / this.width;
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let deltaTime = bounds.end - bounds.start;
|
||||
let newStart = bounds.start - percX * deltaTime;
|
||||
this.bounds = {
|
||||
start: newStart,
|
||||
end: newStart + deltaTime
|
||||
};
|
||||
this.$emit('panAxis', this.bounds);
|
||||
this.dragging = false;
|
||||
})
|
||||
} else {
|
||||
console.log('Rejected drag due to RAF cap');
|
||||
}
|
||||
},
|
||||
dragEnd() {
|
||||
document.removeEventListener('mousemove', this.drag);
|
||||
this.openmct.time.bounds({
|
||||
start: this.bounds.start,
|
||||
end: this.bounds.end
|
||||
});
|
||||
},
|
||||
resize() {
|
||||
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
||||
this.width = this.$refs.axisHolder.clientWidth;
|
||||
this.setScale();
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
bounds: {
|
||||
handler(bounds) {
|
||||
this.setScale();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let axisHolder = this.$refs.axisHolder;
|
||||
let height = axisHolder.offsetHeight;
|
||||
let vis = d3Selection.select(axisHolder)
|
||||
.append("svg:svg")
|
||||
.attr("width", "100%")
|
||||
.attr("height", height);
|
||||
|
||||
this.width = this.$refs.axisHolder.clientWidth;
|
||||
this.xAxis = d3Axis.axisTop();
|
||||
this.dragging = false;
|
||||
|
||||
// draw x axis with labels. CSS is used to position them.
|
||||
this.axisElement = vis.append("g");
|
||||
|
||||
this.setViewFromTimeSystem(this.openmct.time.timeSystem());
|
||||
this.setScale();
|
||||
|
||||
//Respond to changes in conductor
|
||||
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
|
||||
setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||
},
|
||||
destroyed() {
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -1,213 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
|
||||
<div class="c-button--menu c-mode-button"
|
||||
@click="toggleMenu($event)">
|
||||
<span class="c-button__label">{{selectedMode.name}}</span>
|
||||
</div>
|
||||
<div class="c-menu c-super-menu c-conductor__mode-menu"
|
||||
v-if="showMenu">
|
||||
<div class="c-super-menu__menu">
|
||||
<ul>
|
||||
<li v-for="mode in modes"
|
||||
:key="mode.key"
|
||||
@click="setOption(mode)"
|
||||
@mouseover="hoveredMode = mode"
|
||||
@mouseleave="hoveredMode = {}"
|
||||
class="menu-item-a"
|
||||
:class="mode.cssClass">
|
||||
{{mode.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="c-super-menu__item-description">
|
||||
<div :class="['l-item-description__icon', 'bg-' + hoveredMode.cssClass]"></div>
|
||||
<div class="l-item-description__name">{{hoveredMode.name}}</div>
|
||||
<div class="l-item-description__description">{{hoveredMode.description}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.c-conductor__mode-menu {
|
||||
max-height: 80vh;
|
||||
max-width: 500px;
|
||||
min-height: 250px;
|
||||
z-index: 70;
|
||||
|
||||
[class*="__icon"] {
|
||||
filter: $colorKeyFilter;
|
||||
}
|
||||
|
||||
[class*="__item-description"] {
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.is-realtime-mode {
|
||||
.c-mode-button {
|
||||
background: $colorTimeBg;
|
||||
|
||||
&:hover {
|
||||
background: $colorTimeHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct', 'configuration'],
|
||||
data: function () {
|
||||
let activeClock = this.openmct.time.clock();
|
||||
if (activeClock !== undefined) {
|
||||
//Create copy of active clock so the time API does not get reactified.
|
||||
activeClock = Object.create(activeClock);
|
||||
}
|
||||
return {
|
||||
selectedMode: this.getModeOptionForClock(activeClock),
|
||||
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
|
||||
modes: [],
|
||||
hoveredMode: {},
|
||||
showMenu: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
loadClocksFromConfiguration() {
|
||||
let clocks = this.configuration.menuOptions
|
||||
.map(menuOption => menuOption.clock)
|
||||
.filter(isDefinedAndUnique)
|
||||
.map(this.getClock);
|
||||
|
||||
/*
|
||||
* Populate the modes menu with metadata from the available clocks
|
||||
* "Fixed Mode" is always first, and has no defined clock
|
||||
*/
|
||||
this.modes = [undefined]
|
||||
.concat(clocks)
|
||||
.map(this.getModeOptionForClock);
|
||||
|
||||
function isDefinedAndUnique(key, index, array) {
|
||||
return key!== undefined && array.indexOf(key) === index;
|
||||
}
|
||||
},
|
||||
|
||||
getModeOptionForClock(clock) {
|
||||
if (clock === undefined) {
|
||||
return {
|
||||
key: 'fixed',
|
||||
name: 'Fixed Timespan Mode',
|
||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
||||
cssClass: 'icon-calendar'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
key: clock.key,
|
||||
name: clock.name,
|
||||
description: "Monitor streaming data in real-time. The Time " +
|
||||
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
||||
cssClass: clock.cssClass || 'icon-clock'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getClock(key) {
|
||||
return this.openmct.time.getAllClocks().filter(function (clock) {
|
||||
return clock.key === key;
|
||||
})[0];
|
||||
},
|
||||
|
||||
setOption(option) {
|
||||
let clockKey = option.key;
|
||||
if (clockKey === 'fixed') {
|
||||
clockKey = undefined;
|
||||
}
|
||||
|
||||
let configuration = this.getMatchingConfig({
|
||||
clock: clockKey,
|
||||
timeSystem: this.openmct.time.timeSystem().key
|
||||
});
|
||||
|
||||
if (configuration === undefined) {
|
||||
configuration = this.getMatchingConfig({
|
||||
clock: clockKey
|
||||
});
|
||||
|
||||
this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds);
|
||||
}
|
||||
|
||||
if (clockKey === undefined) {
|
||||
this.openmct.time.stopClock();
|
||||
} else {
|
||||
this.openmct.time.clock(clockKey, configuration.clockOffsets);
|
||||
}
|
||||
},
|
||||
|
||||
getMatchingConfig(options) {
|
||||
const matchers = {
|
||||
clock(config) {
|
||||
return options.clock === config.clock
|
||||
},
|
||||
timeSystem(config) {
|
||||
return options.timeSystem === config.timeSystem
|
||||
}
|
||||
};
|
||||
|
||||
function configMatches(config) {
|
||||
return Object.keys(options).reduce((match, option) => {
|
||||
return match && matchers[option](config);
|
||||
}, true);
|
||||
}
|
||||
|
||||
return this.configuration.menuOptions.filter(configMatches)[0];
|
||||
},
|
||||
|
||||
setViewFromClock(clock) {
|
||||
this.selectedMode = this.getModeOptionForClock(clock);
|
||||
},
|
||||
|
||||
toggleMenu(event) {
|
||||
this.showMenu = !this.showMenu;
|
||||
|
||||
if (this.showMenu) {
|
||||
document.addEventListener('click', this.toggleMenu, true);
|
||||
} else {
|
||||
document.removeEventListener('click', this.toggleMenu, true);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
this.loadClocksFromConfiguration();
|
||||
|
||||
this.openmct.time.on('clock', this.setViewFromClock);
|
||||
},
|
||||
destroyed: function () {
|
||||
this.openmct.time.off('clock', this.setViewFromClock);
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -1,118 +0,0 @@
|
||||
<template>
|
||||
<div class="c-clock-symbol">
|
||||
<div class="hand-little"></div>
|
||||
<div class="hand-big"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
@keyframes clock-hands {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes clock-hands-sticky {
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
7% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
8% { transform: translate(-50%, -50%) rotate(30deg); }
|
||||
15% { transform: translate(-50%, -50%) rotate(30deg); }
|
||||
16% { transform: translate(-50%, -50%) rotate(60deg); }
|
||||
24% { transform: translate(-50%, -50%) rotate(60deg); }
|
||||
25% { transform: translate(-50%, -50%) rotate(90deg); }
|
||||
32% { transform: translate(-50%, -50%) rotate(90deg); }
|
||||
33% { transform: translate(-50%, -50%) rotate(120deg); }
|
||||
40% { transform: translate(-50%, -50%) rotate(120deg); }
|
||||
41% { transform: translate(-50%, -50%) rotate(150deg); }
|
||||
49% { transform: translate(-50%, -50%) rotate(150deg); }
|
||||
50% { transform: translate(-50%, -50%) rotate(180deg); }
|
||||
57% { transform: translate(-50%, -50%) rotate(180deg); }
|
||||
58% { transform: translate(-50%, -50%) rotate(210deg); }
|
||||
65% { transform: translate(-50%, -50%) rotate(210deg); }
|
||||
66% { transform: translate(-50%, -50%) rotate(240deg); }
|
||||
74% { transform: translate(-50%, -50%) rotate(240deg); }
|
||||
75% { transform: translate(-50%, -50%) rotate(270deg); }
|
||||
82% { transform: translate(-50%, -50%) rotate(270deg); }
|
||||
83% { transform: translate(-50%, -50%) rotate(300deg); }
|
||||
90% { transform: translate(-50%, -50%) rotate(300deg); }
|
||||
91% { transform: translate(-50%, -50%) rotate(330deg); }
|
||||
99% { transform: translate(-50%, -50%) rotate(330deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
|
||||
.c-clock-symbol {
|
||||
$c: $colorBtnBg; //$colorObjHdrIc;
|
||||
$d: 18px;
|
||||
height: $d;
|
||||
width: $d;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
font-family: symbolsfont;
|
||||
color: $c;
|
||||
content: $glyph-icon-brackets;
|
||||
font-size: $d;
|
||||
line-height: normal;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// Clock hands
|
||||
div[class*="hand"] {
|
||||
$handW: 2px;
|
||||
$handH: $d * 0.4;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
transform-origin: bottom;
|
||||
position: absolute;
|
||||
height: $handW;
|
||||
width: $handW;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 2;
|
||||
&:before {
|
||||
background: $c;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: -1px;
|
||||
}
|
||||
&.hand-little {
|
||||
z-index: 2;
|
||||
animation-duration: 12s;
|
||||
transform: translate(-50%, -50%) rotate(120deg);
|
||||
&:before {
|
||||
height: ceil($handH * 0.6);
|
||||
}
|
||||
}
|
||||
&.hand-big {
|
||||
z-index: 1;
|
||||
animation-duration: 1s;
|
||||
transform: translate(-50%, -50%);
|
||||
&:before {
|
||||
height: $handH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modes
|
||||
.is-realtime-mode &,
|
||||
.is-lad-mode & {
|
||||
&:before {
|
||||
// Brackets icon
|
||||
color: $colorTime;
|
||||
}
|
||||
div[class*="hand"] {
|
||||
animation-name: clock-hands;
|
||||
&:before {
|
||||
background: $colorTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,140 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div class="holder flex-elem time-system c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||
v-if="selectedTimeSystem.name">
|
||||
<div class="c-button--menu c-time-system-button"
|
||||
:class="selectedTimeSystem.cssClass"
|
||||
@click="toggleMenu($event)">
|
||||
<span class="c-button__label">{{selectedTimeSystem.name}}</span>
|
||||
</div>
|
||||
<div class="c-menu" v-if="showMenu">
|
||||
<ul>
|
||||
<li @click="setTimeSystemFromView(timeSystem)"
|
||||
v-for="timeSystem in timeSystems"
|
||||
:key="timeSystem.key"
|
||||
:class="timeSystem.cssClass">
|
||||
{{timeSystem.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.is-realtime-mode {
|
||||
.c-time-system-button {
|
||||
background: $colorTimeBg;
|
||||
|
||||
&:hover {
|
||||
background: $colorTimeHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct', 'configuration'],
|
||||
data: function () {
|
||||
let activeClock = this.openmct.time.clock();
|
||||
|
||||
return {
|
||||
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
|
||||
timeSystems: this.getValidTimesystemsForClock(activeClock),
|
||||
showMenu: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getValidTimesystemsForClock(clock) {
|
||||
return this.configuration.menuOptions
|
||||
.filter(menuOption => menuOption.clock === (clock && clock.key))
|
||||
.map(menuOption => JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem))));
|
||||
},
|
||||
|
||||
setTimeSystemFromView(timeSystem) {
|
||||
if (timeSystem.key !== this.selectedTimeSystem.key) {
|
||||
let activeClock = this.openmct.time.clock();
|
||||
let configuration = this.getMatchingConfig({
|
||||
clock: activeClock && activeClock.key,
|
||||
timeSystem: timeSystem.key
|
||||
});
|
||||
if (activeClock === undefined) {
|
||||
this.openmct.time.timeSystem(timeSystem.key, configuration.bounds);
|
||||
} else {
|
||||
this.openmct.time.timeSystem(timeSystem.key);
|
||||
this.openmct.time.clockOffsets(configuration.clockOffsets);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getMatchingConfig(options) {
|
||||
const matchers = {
|
||||
clock(config) {
|
||||
return options.clock === config.clock
|
||||
},
|
||||
timeSystem(config) {
|
||||
return options.timeSystem === config.timeSystem
|
||||
}
|
||||
};
|
||||
|
||||
function configMatches(config) {
|
||||
return Object.keys(options).reduce((match, option) => {
|
||||
return match && matchers[option](config);
|
||||
}, true);
|
||||
}
|
||||
|
||||
return this.configuration.menuOptions.filter(configMatches)[0];
|
||||
},
|
||||
|
||||
toggleMenu(event) {
|
||||
this.showMenu = !this.showMenu;
|
||||
|
||||
if (this.showMenu) {
|
||||
document.addEventListener('click', this.toggleMenu, true);
|
||||
} else {
|
||||
document.removeEventListener('click', this.toggleMenu, true);
|
||||
}
|
||||
},
|
||||
|
||||
setViewFromTimeSystem(timeSystem) {
|
||||
this.selectedTimeSystem = timeSystem;
|
||||
},
|
||||
|
||||
setViewFromClock(clock) {
|
||||
let activeClock = this.openmct.time.clock();
|
||||
this.timeSystems = this.getValidTimesystemsForClock(activeClock);
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.openmct.time.on('timeSystem', this.setViewFromTimeSystem);
|
||||
this.openmct.time.on('clock', this.setViewFromClock);
|
||||
},
|
||||
destroyed: function () {
|
||||
this.openmct.time.off('timeSystem', this.setViewFromTimeSystem);
|
||||
this.openmct.time.on('clock', this.setViewFromClock);
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -1,312 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<!-- TODOS: changeMonth doesn't appear to work, was ng-click -->
|
||||
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up" ref="calendarHolder">
|
||||
<a class="c-click-icon icon-calendar"
|
||||
@click="togglePicker()"></a>
|
||||
<div class="c-menu c-datetime-picker"
|
||||
v-if="showPicker">
|
||||
<div class="c-datetime-picker__month-year-pager c-pager l-month-year-pager">
|
||||
<div class="c-pager__prev c-click-icon icon-arrow-left"
|
||||
@click="changeMonth(-1)"></div>
|
||||
<div class="c-pager__month-year">{{model.month}} {{model.year}}</div>
|
||||
<div class="c-pager__next c-click-icon icon-arrow-right"
|
||||
@click="changeMonth(1)"></div>
|
||||
</div>
|
||||
<div class="c-datetime-picker__calendar c-calendar">
|
||||
<ul class="c-calendar__row--header l-cal-row">
|
||||
<li v-for="day in ['Su','Mo','Tu','We','Th','Fr','Sa']"
|
||||
:key="day">{{day}}</li>
|
||||
</ul>
|
||||
<ul class="c-calendar__row--body"
|
||||
v-for="(row, index) in table"
|
||||
:key="index">
|
||||
<li v-for="(cell, index) in row"
|
||||
:key="index"
|
||||
@click="select(cell)"
|
||||
:class="{ 'is-in-month': isInCurrentMonth(cell), selected: isSelected(cell) }">
|
||||
<div class="c-calendar__day--prime">{{cell.day}}</div>
|
||||
<div class="c-calendar__day--sub">{{cell.dayOfYear}}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************************************** PICKER */
|
||||
.c-datetime-picker {
|
||||
@include userSelectNone();
|
||||
padding: $interiorMarginLg !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> * + * {
|
||||
border-top: 1px solid $colorInteriorBorder;
|
||||
margin-top: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
.c-pager {
|
||||
display: grid;
|
||||
grid-column-gap: $interiorMargin;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
|
||||
.c-click-icon {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
&__month-year {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** CALENDAR */
|
||||
.c-calendar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, min-content);
|
||||
grid-template-rows: auto;
|
||||
grid-gap: 1px;
|
||||
|
||||
$mutedOpacity: 0.7;
|
||||
|
||||
ul {
|
||||
display: contents;
|
||||
&[class*='--header'] {
|
||||
pointer-events: none;
|
||||
li {
|
||||
opacity: $mutedOpacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: $interiorMargin;
|
||||
|
||||
&.is-in-month {
|
||||
background: rgba($colorBodyFg, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&__day {
|
||||
&--sub {
|
||||
opacity: $mutedOpacity;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
|
||||
const TIME_NAMES = {
|
||||
'hours': "Hour",
|
||||
'minutes': "Minute",
|
||||
'seconds': "Second"
|
||||
};
|
||||
const MONTHS = moment.months();
|
||||
const TIME_OPTIONS = (function makeRanges() {
|
||||
let arr = [];
|
||||
while (arr.length < 60) {
|
||||
arr.push(arr.length);
|
||||
}
|
||||
return {
|
||||
hours: arr.slice(0, 24),
|
||||
minutes: arr,
|
||||
seconds: arr
|
||||
};
|
||||
}());
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
defaultDateTime: String,
|
||||
formatter: Object
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
showPicker: false,
|
||||
picker: {
|
||||
year: undefined,
|
||||
month: undefined,
|
||||
interacted: false
|
||||
},
|
||||
model: {
|
||||
year: undefined,
|
||||
month: undefined,
|
||||
},
|
||||
table: undefined,
|
||||
date: undefined,
|
||||
time: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateTable() {
|
||||
let m = moment.utc({ year: this.picker.year, month: this.picker.month }).day(0),
|
||||
table = [],
|
||||
row,
|
||||
col;
|
||||
|
||||
for (row = 0; row < 6; row += 1) {
|
||||
table.push([]);
|
||||
for (col = 0; col < 7; col += 1) {
|
||||
table[row].push({
|
||||
year: m.year(),
|
||||
month: m.month(),
|
||||
day: m.date(),
|
||||
dayOfYear: m.dayOfYear()
|
||||
});
|
||||
m.add(1, 'days'); // Next day!
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
},
|
||||
|
||||
updateViewForMonth() {
|
||||
this.model.month = MONTHS[this.picker.month];
|
||||
this.model.year = this.picker.year;
|
||||
this.table = this.generateTable();
|
||||
},
|
||||
|
||||
updateFromModel(defaultDateTime) {
|
||||
let m;
|
||||
|
||||
m = moment.utc(defaultDateTime);
|
||||
|
||||
this.date = {
|
||||
year: m.year(),
|
||||
month: m.month(),
|
||||
day: m.date()
|
||||
};
|
||||
this.time = {
|
||||
hours: m.hour(),
|
||||
minutes: m.minute(),
|
||||
seconds: m.second()
|
||||
};
|
||||
|
||||
// Zoom to that date in the picker, but
|
||||
// only if the user hasn't interacted with it yet.
|
||||
if (!this.picker.interacted) {
|
||||
this.picker.year = m.year();
|
||||
this.picker.month = m.month();
|
||||
this.updateViewForMonth();
|
||||
}
|
||||
},
|
||||
|
||||
updateFromView() {
|
||||
let m = moment.utc({
|
||||
year: this.date.year,
|
||||
month: this.date.month,
|
||||
day: this.date.day,
|
||||
hour: this.time.hours,
|
||||
minute: this.time.minutes,
|
||||
second: this.time.seconds
|
||||
});
|
||||
this.$emit('date-selected', m.valueOf());
|
||||
},
|
||||
|
||||
isInCurrentMonth(cell) {
|
||||
return cell.month === this.picker.month;
|
||||
},
|
||||
|
||||
isSelected(cell) {
|
||||
let date = this.date || {};
|
||||
return cell.day === date.day &&
|
||||
cell.month === date.month &&
|
||||
cell.year === date.year;
|
||||
},
|
||||
|
||||
select(cell) {
|
||||
this.date = this.date || {};
|
||||
this.date.month = cell.month;
|
||||
this.date.year = cell.year;
|
||||
this.date.day = cell.day;
|
||||
this.updateFromView();
|
||||
this.showPicker = false;
|
||||
},
|
||||
|
||||
dateEquals(d1, d2) {
|
||||
return d1.year === d2.year &&
|
||||
d1.month === d2.month &&
|
||||
d1.day === d2.day;
|
||||
},
|
||||
|
||||
changeMonth(delta) {
|
||||
this.picker.month += delta;
|
||||
if (this.picker.month > 11) {
|
||||
this.picker.month = 0;
|
||||
this.picker.year += 1;
|
||||
}
|
||||
if (this.picker.month < 0) {
|
||||
this.picker.month = 11;
|
||||
this.picker.year -= 1;
|
||||
}
|
||||
this.picker.interacted = true;
|
||||
this.updateViewForMonth();
|
||||
},
|
||||
|
||||
nameFor(key) {
|
||||
return TIME_NAMES[key];
|
||||
},
|
||||
|
||||
optionsFor(key) {
|
||||
return TIME_OPTIONS[key];
|
||||
},
|
||||
|
||||
hidePicker(event) {
|
||||
let path = event.composedPath();
|
||||
if (path.indexOf(this.$refs.calendarHolder) === -1) {
|
||||
this.showPicker = false;
|
||||
}
|
||||
},
|
||||
|
||||
togglePicker() {
|
||||
this.showPicker = !this.showPicker;
|
||||
|
||||
if (this.showPicker) {
|
||||
document.addEventListener('click', this.hidePicker, {
|
||||
capture: true
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.updateFromModel(this.defaultDateTime);
|
||||
this.updateViewForMonth();
|
||||
},
|
||||
destroyed: function () {
|
||||
document.addEventListener('click', this.hidePicker, {
|
||||
capture: true
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -20,108 +20,110 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import Conductor from './Conductor.vue';
|
||||
import Vue from 'vue';
|
||||
define([], function () {
|
||||
|
||||
function isTruthy(a) {
|
||||
return !!a;
|
||||
}
|
||||
function isTruthy(a) {
|
||||
return !!a;
|
||||
}
|
||||
|
||||
function validateMenuOption(menuOption, index) {
|
||||
if (menuOption.clock && !menuOption.clockOffsets) {
|
||||
return "clock-based menuOption at index " + index + " is " +
|
||||
"missing required property 'clockOffsets'.";
|
||||
function validateMenuOption(menuOption, index) {
|
||||
if (menuOption.clock && !menuOption.clockOffsets) {
|
||||
return "clock-based menuOption at index " + index + " is " +
|
||||
"missing required property 'clockOffsets'.";
|
||||
}
|
||||
if (!menuOption.timeSystem) {
|
||||
return "menuOption at index " + index + " is missing " +
|
||||
"required property 'timeSystem'.";
|
||||
}
|
||||
if (!menuOption.bounds && !menuOption.clock) {
|
||||
return "fixed-bounds menuOption at index " + index + " is " +
|
||||
"missing required property 'bounds'";
|
||||
}
|
||||
}
|
||||
if (!menuOption.timeSystem) {
|
||||
return "menuOption at index " + index + " is missing " +
|
||||
"required property 'timeSystem'.";
|
||||
}
|
||||
if (!menuOption.bounds && !menuOption.clock) {
|
||||
return "fixed-bounds menuOption at index " + index + " is " +
|
||||
"missing required property 'bounds'";
|
||||
}
|
||||
}
|
||||
|
||||
function validateConfiguration(config) {
|
||||
if (config === undefined ||
|
||||
config.menuOptions === undefined ||
|
||||
config.menuOptions.length === 0) {
|
||||
return "You must specify one or more 'menuOptions'.";
|
||||
function validateConfiguration(config) {
|
||||
if (config === undefined ||
|
||||
config.menuOptions === undefined ||
|
||||
config.menuOptions.length === 0) {
|
||||
return "You must specify one or more 'menuOptions'.";
|
||||
}
|
||||
if (config.menuOptions.some(validateMenuOption)) {
|
||||
return config.menuOptions.map(validateMenuOption)
|
||||
.filter(isTruthy)
|
||||
.join('\n');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (config.menuOptions.some(validateMenuOption)) {
|
||||
return config.menuOptions.map(validateMenuOption)
|
||||
|
||||
function validateRuntimeConfiguration(config, openmct) {
|
||||
var systems = openmct.time.getAllTimeSystems()
|
||||
.reduce(function (m, ts) {
|
||||
m[ts.key] = ts;
|
||||
return m;
|
||||
}, {});
|
||||
var clocks = openmct.time.getAllClocks()
|
||||
.reduce(function (m, c) {
|
||||
m[c.key] = c;
|
||||
return m;
|
||||
}, {});
|
||||
|
||||
return config.menuOptions.map(function (menuOption, index) {
|
||||
if (menuOption.timeSystem && !systems[menuOption.timeSystem]) {
|
||||
return "menuOption at index " + index + " specifies a " +
|
||||
"timeSystem that does not exist: " + menuOption.timeSystem;
|
||||
}
|
||||
if (menuOption.clock && !clocks[menuOption.clock]) {
|
||||
return "menuOption at index " + index + " specifies a " +
|
||||
"clock that does not exist: " + menuOption.clock;
|
||||
}
|
||||
})
|
||||
.filter(isTruthy)
|
||||
.join('\n');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function validateRuntimeConfiguration(config, openmct) {
|
||||
var systems = openmct.time.getAllTimeSystems()
|
||||
.reduce(function (m, ts) {
|
||||
m[ts.key] = ts;
|
||||
return m;
|
||||
}, {});
|
||||
var clocks = openmct.time.getAllClocks()
|
||||
.reduce(function (m, c) {
|
||||
m[c.key] = c;
|
||||
return m;
|
||||
}, {});
|
||||
|
||||
return config.menuOptions.map(function (menuOption, index) {
|
||||
if (menuOption.timeSystem && !systems[menuOption.timeSystem]) {
|
||||
return "menuOption at index " + index + " specifies a " +
|
||||
"timeSystem that does not exist: " + menuOption.timeSystem;
|
||||
function throwConfigErrorIfExists(error) {
|
||||
if (error) {
|
||||
throw new Error("Invalid Time Conductor Configuration: \n" +
|
||||
error + '\n' +
|
||||
"https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor");
|
||||
}
|
||||
if (menuOption.clock && !clocks[menuOption.clock]) {
|
||||
return "menuOption at index " + index + " specifies a " +
|
||||
"clock that does not exist: " + menuOption.clock;
|
||||
}
|
||||
}).filter(isTruthy).join('\n');
|
||||
}
|
||||
|
||||
function throwIfError(configResult) {
|
||||
if (configResult) {
|
||||
throw new Error("Invalid Time Conductor Configuration: \n" +
|
||||
configResult + '\n' +
|
||||
"https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor");
|
||||
}
|
||||
}
|
||||
|
||||
function mountComponent(openmct, configuration) {
|
||||
openmct.layout.conductorComponent = Object.create({
|
||||
components: {
|
||||
Conductor
|
||||
},
|
||||
template: "<conductor></conductor>",
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
configuration: configuration
|
||||
}
|
||||
});
|
||||
}
|
||||
return function (config) {
|
||||
|
||||
export default function (config){
|
||||
throwConfigErrorIfExists(validateConfiguration(config));
|
||||
|
||||
let configResult = validateConfiguration(config);
|
||||
throwIfError(configResult);
|
||||
return function (openmct) {
|
||||
|
||||
return function (openmct) {
|
||||
openmct.legacyExtension('constants', {
|
||||
key: 'CONDUCTOR_CONFIG',
|
||||
value: config,
|
||||
priority: 'mandatory'
|
||||
});
|
||||
|
||||
openmct.on('start', function () {
|
||||
configResult = validateRuntimeConfiguration(config, openmct);
|
||||
throwIfError(configResult);
|
||||
openmct.legacyRegistry.enable('platform/features/conductor/core');
|
||||
openmct.legacyRegistry.enable('platform/features/conductor/compatibility');
|
||||
|
||||
var defaults = config.menuOptions[0];
|
||||
if (defaults.clock) {
|
||||
openmct.time.clock(defaults.clock, defaults.clockOffsets);
|
||||
openmct.time.timeSystem(defaults.timeSystem, openmct.time.bounds());
|
||||
} else {
|
||||
openmct.time.timeSystem(defaults.timeSystem, defaults.bounds);
|
||||
}
|
||||
openmct.on('start', function () {
|
||||
|
||||
mountComponent(openmct, config);
|
||||
throwConfigErrorIfExists(validateRuntimeConfiguration(config, openmct));
|
||||
|
||||
});
|
||||
/*
|
||||
On app startup, default the conductor if not already set.
|
||||
*/
|
||||
if (openmct.time.timeSystem() !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var defaults = config.menuOptions[0];
|
||||
if (defaults.clock) {
|
||||
openmct.time.clock(defaults.clock, defaults.clockOffsets);
|
||||
openmct.time.timeSystem(defaults.timeSystem, openmct.time.bounds());
|
||||
} else {
|
||||
openmct.time.timeSystem(defaults.timeSystem, defaults.bounds);
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -21,12 +21,11 @@ $colorStatusBarFg: #999;
|
||||
$colorStatusBarFgHov: #aaa;
|
||||
$colorKey: #0099cc;
|
||||
$colorKeyFilter: brightness(0.9) sepia(1) hue-rotate(145deg) saturate(6);
|
||||
$colorKeyFilterHov: brightness(1) sepia(1) hue-rotate(145deg) saturate(7);
|
||||
$colorKeySelectedBg: $colorKey;
|
||||
$colorKeyFg: #fff;
|
||||
$colorKeyHov: #00c0f6;
|
||||
$colorEditAreaBg: #eafaff; // Deprecated, use $editColor instead
|
||||
$colorEditAreaFg: #4bb1c7; // Deprecated, use $editColor instead
|
||||
$colorEditAreaBg: #eafaff;
|
||||
$colorEditAreaFg: #4bb1c7;
|
||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||
$colorA: #999;
|
||||
$colorAHov: $colorKey;
|
||||
@@ -38,16 +37,6 @@ $smallCr: 2px;
|
||||
$overlayCr: 11px;
|
||||
$shdwTextSubtle: rgba(black, 0.2) 0 1px 2px;
|
||||
|
||||
// Variations
|
||||
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
|
||||
$colorBodyBgSubtleHov: pushBack($colorKey, 50%);
|
||||
$colorKeySubtle: pushBack($colorKey, 50%);
|
||||
$colorTime: #618cff;
|
||||
$colorTimeBg: $colorTime;
|
||||
$colorTimeFg: $colorBodyBg;
|
||||
$colorTimeHov: pushBack($colorTime, 5%);
|
||||
$colorTimeSubtle: pushBack($colorTime, 20%);
|
||||
|
||||
// Buttons and Controls
|
||||
$btnPad: $interiorMargin, $interiorMargin * 1.25;
|
||||
$colorBtnBg: #aaaaaa;
|
||||
@@ -83,7 +72,7 @@ $sliderColorRangeValHovFg: $colorBodyFg;
|
||||
$sliderKnobW: 15px;
|
||||
$sliderKnobR: 2px;
|
||||
$timeControllerToiLineColor: $colorBodyFg;
|
||||
$timeControllerToiLineColorHov: $colorTime;
|
||||
$timeControllerToiLineColorHov: #0052b5;
|
||||
$colorTransLucBg: #666; // Used as a visual blocking element over variable backgrounds, like imagery
|
||||
$createBtnTextTransform: uppercase;
|
||||
|
||||
@@ -96,25 +85,17 @@ $colorDiagnostic: #a4b442;
|
||||
$colorCommand: #3693bd;
|
||||
$colorInfo: #2294a2;
|
||||
$colorOk: #33cc33;
|
||||
$colorIconAlias: #4af6f3;
|
||||
$colorIconAliasForKeyFilter: #aaa;
|
||||
$colorIconLink: #49dedb;
|
||||
$colorPausedBg: #ff9900;
|
||||
$colorPausedFg: #fff;
|
||||
$colorCreateBtn: $colorKey;
|
||||
$colorGridLines: rgba(#000, 0.05);
|
||||
$colorInvokeMenu: #fff;
|
||||
$colorObjHdrTxt: $colorBodyFg;
|
||||
$colorObjHdrIc: lighten($colorObjHdrTxt, 30%);
|
||||
$colorTick: rgba(black, 0.2);
|
||||
|
||||
$editColor: #00c7c3;
|
||||
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
|
||||
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
||||
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
|
||||
$editBorderSelectable: 1px dotted rgba($editColor, 1);
|
||||
$editBorderSelectableHov: 1px dashed rgba($editColor, 1);
|
||||
$editBorderSelected: 1px solid $editColor;
|
||||
$editBorderDrilledIn: 1px dashed #ff4d9a;
|
||||
$colorGridLines: rgba($editColor, 0.2);
|
||||
$colorSelectableSelectedPrimary: $colorKey;
|
||||
$colorSelectableHov: rgba($colorBodyFg, 0.4);
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
||||
@@ -150,9 +131,6 @@ $colorInputPlaceholder: pushBack($colorBodyFg, 20%);
|
||||
$colorFormText: pushBack($colorBodyFg, 10%);
|
||||
$colorInputIcon: pushBack($colorBodyFg, 25%);
|
||||
$colorFieldHint: pullForward($colorBodyFg, 40%);
|
||||
$shdwInput: inset rgba(black, 0.4) 0 0 1px;
|
||||
$shdwInputHov: inset rgba(black, 0.7) 0 0 1px;
|
||||
$shdwInputFoc: inset rgba(black, 0.7) 0 0 3px;
|
||||
|
||||
// Inspector
|
||||
$colorInspectorBg: pullForward($colorBodyBg, 5%);
|
||||
@@ -212,9 +190,7 @@ $durLargeViewExpand: 250ms;
|
||||
|
||||
// Items
|
||||
$colorItemBg: #ddd;
|
||||
$colorItemBgHov: rgba($colorKey, 0.1); //pushBack($colorItemBg, $hoverRatioPercent * 0.4);
|
||||
$colorListItemBg: transparent;
|
||||
$colorListItemBgHov: rgba($colorKey, 0.1);
|
||||
$colorItemBgHov: pullForward($colorItemBg, $hoverRatioPercent * 0.7);
|
||||
$colorItemFg: $colorBodyFg;
|
||||
$colorItemFgDetails: pushBack($colorItemFg, 15%);
|
||||
$colorItemIc: $colorKey;
|
||||
@@ -318,10 +294,8 @@ $colorLoadingFg: $colorAlt1;
|
||||
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
|
||||
|
||||
// Transitions
|
||||
$transIn: all 50ms ease-in-out;
|
||||
$transOut: all 250ms ease-in-out;
|
||||
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
|
||||
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
$transIn: all 50ms ease-in;
|
||||
$transOut: all 250ms ease-out;
|
||||
|
||||
// Discrete items, like Notebook entries, Widget rules
|
||||
@mixin discreteItem() {
|
||||
|
||||
@@ -96,7 +96,7 @@ $glyph-icon-pointer-right: '\e1028';
|
||||
$glyph-icon-refresh: '\e1029';
|
||||
$glyph-icon-save: '\e1030';
|
||||
$glyph-icon-sine: '\e1031';
|
||||
$glyph-icon-T: '\e1032';
|
||||
$glyph-icon-font: '\e1032';
|
||||
$glyph-icon-thumbs-strip: '\e1033';
|
||||
$glyph-icon-two-parts-both: '\e1034';
|
||||
$glyph-icon-two-parts-one-only: '\e1035';
|
||||
@@ -113,7 +113,7 @@ $glyph-icon-frame-show: '\e1045';
|
||||
$glyph-icon-frame-hide: '\e1046';
|
||||
$glyph-icon-import: '\e1047';
|
||||
$glyph-icon-export: '\e1048';
|
||||
$glyph-icon-minimize: '\e1049'; // 12px only
|
||||
$glyph-icon-font-size: '\e1049';
|
||||
$glyph-icon-activity: '\e1100';
|
||||
$glyph-icon-activity-mode: '\e1101';
|
||||
$glyph-icon-autoflow-tabular: '\e1102';
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
// VERSION MANUALLY MIGRATED FROM VUE-TOOLBAR
|
||||
// VERSION MANUALLY RESTORED FROM VUE-LAYOUT
|
||||
|
||||
/******************************************************** PLACEHOLDERS */
|
||||
@mixin cControl() {
|
||||
@@ -80,7 +80,7 @@
|
||||
&:hover {
|
||||
background: $colorBtnCautionBgHov;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin cClickIcon() {
|
||||
@@ -113,24 +113,24 @@
|
||||
// Wraps --menu elements, contains button and menu
|
||||
overflow: visible;
|
||||
|
||||
.c-menu {
|
||||
.c-menu {
|
||||
// Default position of contained menu
|
||||
top: 100%; left: 0;
|
||||
}
|
||||
top: 100%; left: 0;
|
||||
}
|
||||
|
||||
&[class*='--menus-up'] {
|
||||
.c-menu {
|
||||
top: auto; bottom: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--menus-left'] {
|
||||
.c-menu {
|
||||
left: auto; right: 0;
|
||||
}
|
||||
.c-menu {
|
||||
top: auto; bottom: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--menus-left'] {
|
||||
.c-menu {
|
||||
left: auto; right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/********* Buttons */
|
||||
// Optionally can include icon in :before via markup
|
||||
.c-button,
|
||||
@@ -235,39 +235,13 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************************************** FORM ELEMENTS */
|
||||
input, textarea {
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=search],
|
||||
input[type=number] {
|
||||
@include reactive-input();
|
||||
padding: $inputTextP;
|
||||
&.numeric {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.c-input {
|
||||
&--datetime {
|
||||
// Sized for values such as 2018-09-28 22:32:33.468Z
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
&--hrs-min-sec {
|
||||
// Sized for values such as 00:25:00
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
&-inline,
|
||||
&--inline {
|
||||
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
|
||||
@include reactive-input();
|
||||
box-shadow: none;
|
||||
/********* Inline inputs */
|
||||
.c-input-inline {
|
||||
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
|
||||
@include input-base();
|
||||
border: 1px solid transparent;
|
||||
display: block !important;
|
||||
min-width: 0;
|
||||
padding-left: 0;
|
||||
@@ -285,18 +259,13 @@ input[type=number] {
|
||||
padding-left: $inputTextPLeftRight;
|
||||
padding-right: $inputTextPLeftRight;
|
||||
}
|
||||
}
|
||||
|
||||
&--labeled {
|
||||
// TODO: replace .c-labeled-input with this
|
||||
// An input used in the Toolbar
|
||||
// Assumes label is before the input
|
||||
@include cControl();
|
||||
|
||||
input {
|
||||
margin-left: $interiorMarginSm;
|
||||
&:hover {
|
||||
border-color: rgba($colorBodyFg, 0.2);
|
||||
}
|
||||
&:focus {
|
||||
@include nice-input($shdw: rgba(0, 0, 0, 0.6) 0 1px 3px);
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-labeled-input {
|
||||
@@ -338,7 +307,6 @@ input[type=number] {
|
||||
@include cControl();
|
||||
justify-content: start;
|
||||
color: $colorMenuFg;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: nth($menuItemPad, 1) nth($menuItemPad, 2);
|
||||
transition: $transIn;
|
||||
@@ -582,9 +550,3 @@ input[type=number] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************** SLIDERS */
|
||||
.c-slider {
|
||||
@include cControl();
|
||||
> * + * { margin-left: $interiorMargin; }
|
||||
}
|
||||
|
||||
@@ -89,6 +89,13 @@ body.desktop {
|
||||
}
|
||||
}
|
||||
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
//margin: -1px -5px inherit -5px !important;
|
||||
margin-right: -5px !important;
|
||||
margin-top: -1px !important;
|
||||
}
|
||||
|
||||
/************************** HTML ENTITIES */
|
||||
a {
|
||||
color: $colorA;
|
||||
@@ -118,6 +125,22 @@ em {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=search],
|
||||
input[type=number] {
|
||||
@include nice-input();
|
||||
padding: $inputTextP;
|
||||
&.numeric {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
letter-spacing: 0.04em;
|
||||
margin: 0;
|
||||
@@ -267,21 +290,6 @@ a.disabled {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-selection {
|
||||
// aka selection = "None". Used in palettes and their menu buttons.
|
||||
$c: red;
|
||||
$s: 48%;
|
||||
$e: 52%;
|
||||
background-image: linear-gradient(-45deg,
|
||||
transparent $s - 5%,
|
||||
$c $s,
|
||||
$c $e,
|
||||
transparent $e + 5%
|
||||
);
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.scrolling,
|
||||
.scroll {
|
||||
overflow: auto;
|
||||
@@ -364,3 +372,8 @@ a.disabled {
|
||||
.t-imagery {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.t-frame-outer {
|
||||
min-width: 200px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
.icon-refresh { @include glyphBefore($glyph-icon-refresh); }
|
||||
.icon-save { @include glyphBefore($glyph-icon-save); }
|
||||
.icon-sine { @include glyphBefore($glyph-icon-sine); }
|
||||
.icon-T { @include glyphBefore($glyph-icon-T); }
|
||||
.icon-font { @include glyphBefore($glyph-icon-font); }
|
||||
.icon-thumbs-strip { @include glyphBefore($glyph-icon-thumbs-strip); }
|
||||
.icon-two-parts-both { @include glyphBefore($glyph-icon-two-parts-both); }
|
||||
.icon-two-parts-one-only { @include glyphBefore($glyph-icon-two-parts-one-only); }
|
||||
@@ -108,6 +108,7 @@
|
||||
.icon-frame-hide { @include glyphBefore($glyph-icon-frame-hide); }
|
||||
.icon-import { @include glyphBefore($glyph-icon-import); }
|
||||
.icon-export { @include glyphBefore($glyph-icon-export); }
|
||||
.icon-font-size { @include glyphBefore($glyph-icon-font-size); }
|
||||
.icon-activity { @include glyphBefore($glyph-icon-activity); }
|
||||
.icon-activity-mode { @include glyphBefore($glyph-icon-activity-mode); }
|
||||
.icon-autoflow-tabular { @include glyphBefore($glyph-icon-autoflow-tabular); }
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
// VERSION MANUALLY MIGRATED FROM VUE-TOOLBAR
|
||||
// VERSION MANUALLY RESTORED FROM VUE-LAYOUT
|
||||
|
||||
/************************** VISUALS */
|
||||
@mixin ancillaryIcon($d, $c) {
|
||||
@@ -164,28 +164,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0 2px) {
|
||||
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0px 2px) {
|
||||
@include input-base();
|
||||
background: $bg;
|
||||
color: $fg;
|
||||
box-shadow: inset $shdw;
|
||||
}
|
||||
|
||||
@mixin reactive-input($bg: $colorInputBg, $fg: $colorInputFg) {
|
||||
@include input-base();
|
||||
background: $bg;
|
||||
box-shadow: $shdwInput;
|
||||
color: $fg;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shdwInputHov;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: $shdwInputFoc;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) {
|
||||
background: $bg;
|
||||
color: $fg;
|
||||
@@ -269,13 +254,4 @@
|
||||
|
||||
@mixin userSelectNone() {
|
||||
@include browserPrefix(user-select, none);
|
||||
}
|
||||
|
||||
@mixin cursorGrab() {
|
||||
cursor: grab;
|
||||
cursor: -webkit-grab;
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/******************************************************** TABLE */
|
||||
.c-table {
|
||||
// Can be used by any type of table, scrolling, LAD, etc.
|
||||
$min-w: 50px;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: flex-start;
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: 0; left: 0;
|
||||
|
||||
&__control-bar,
|
||||
&__headers-w {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/******************************* ELEMENTS */
|
||||
th, td {
|
||||
white-space: nowrap;
|
||||
min-width: $min-w;
|
||||
padding: $tabularTdPadTB $tabularTdPadLR;
|
||||
}
|
||||
|
||||
td {
|
||||
color: $colorTelemFresh;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&__control-bar {
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
[class*="__header"] {
|
||||
background: $colorTabHeaderBg;
|
||||
|
||||
th {
|
||||
&:not(:first-child) {
|
||||
border-left: 1px solid $colorTabHeaderBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
tr {
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid $colorTabBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--sortable {
|
||||
.is-sorting {
|
||||
&:after {
|
||||
color: $colorIconAlias;
|
||||
content: $glyph-icon-arrow-tall-up;
|
||||
font-family: symbolsfont;
|
||||
font-size: 8px;
|
||||
display: inline-block;
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
&.desc:after {
|
||||
content: $glyph-icon-arrow-tall-down;
|
||||
}
|
||||
}
|
||||
.is-sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"metadata": {
|
||||
"name": "openmct-symbols-16px",
|
||||
"lastOpened": 0,
|
||||
"created": 1529545133464
|
||||
"created": 1537817705550
|
||||
},
|
||||
"iconSets": [
|
||||
{
|
||||
@@ -525,7 +525,7 @@
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 102,
|
||||
"order": 149,
|
||||
"prevSize": 24,
|
||||
"name": "icon-T",
|
||||
"id": 84,
|
||||
@@ -660,13 +660,21 @@
|
||||
"code": 921672,
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 150,
|
||||
"id": 119,
|
||||
"name": "icon-font-size-alt1",
|
||||
"prevSize": 24,
|
||||
"code": 921673,
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 37,
|
||||
"prevSize": 24,
|
||||
"name": "icon-activity",
|
||||
"id": 32,
|
||||
"code": 921856,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 36,
|
||||
@@ -674,7 +682,7 @@
|
||||
"name": "icon-activity-mode",
|
||||
"id": 31,
|
||||
"code": 921857,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 52,
|
||||
@@ -682,7 +690,7 @@
|
||||
"name": "icon-autoflow-tabular",
|
||||
"id": 47,
|
||||
"code": 921858,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 55,
|
||||
@@ -690,7 +698,7 @@
|
||||
"name": "icon-clock",
|
||||
"id": 50,
|
||||
"code": 921859,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 58,
|
||||
@@ -698,7 +706,7 @@
|
||||
"name": "icon-database",
|
||||
"id": 53,
|
||||
"code": 921860,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 57,
|
||||
@@ -706,7 +714,7 @@
|
||||
"name": "icon-database-query",
|
||||
"id": 52,
|
||||
"code": 921861,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 17,
|
||||
@@ -714,7 +722,7 @@
|
||||
"name": "icon-dataset",
|
||||
"id": 12,
|
||||
"code": 921862,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 22,
|
||||
@@ -722,7 +730,7 @@
|
||||
"name": "icon-datatable",
|
||||
"id": 17,
|
||||
"code": 921863,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 59,
|
||||
@@ -730,7 +738,7 @@
|
||||
"name": "icon-dictionary",
|
||||
"id": 54,
|
||||
"code": 921864,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 62,
|
||||
@@ -738,7 +746,7 @@
|
||||
"name": "icon-folder",
|
||||
"id": 57,
|
||||
"code": 921865,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 66,
|
||||
@@ -746,7 +754,7 @@
|
||||
"name": "icon-image",
|
||||
"id": 61,
|
||||
"code": 921872,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 68,
|
||||
@@ -754,7 +762,7 @@
|
||||
"name": "icon-layout",
|
||||
"id": 63,
|
||||
"code": 921873,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 77,
|
||||
@@ -762,7 +770,7 @@
|
||||
"name": "icon-object",
|
||||
"id": 72,
|
||||
"code": 921874,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 78,
|
||||
@@ -770,7 +778,7 @@
|
||||
"name": "icon-object-unknown",
|
||||
"id": 73,
|
||||
"code": 921875,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 79,
|
||||
@@ -778,7 +786,7 @@
|
||||
"name": "icon-packet",
|
||||
"id": 74,
|
||||
"code": 921876,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 80,
|
||||
@@ -786,7 +794,7 @@
|
||||
"name": "icon-page",
|
||||
"id": 75,
|
||||
"code": 921877,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 135,
|
||||
@@ -794,7 +802,7 @@
|
||||
"name": "icon-plot-overlay",
|
||||
"prevSize": 24,
|
||||
"code": 921878,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 113,
|
||||
@@ -802,7 +810,7 @@
|
||||
"name": "icon-plot-stacked",
|
||||
"prevSize": 24,
|
||||
"code": 921879,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 10,
|
||||
@@ -810,7 +818,7 @@
|
||||
"name": "icon-session",
|
||||
"id": 5,
|
||||
"code": 921880,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 24,
|
||||
@@ -818,7 +826,7 @@
|
||||
"name": "icon-tabular",
|
||||
"id": 19,
|
||||
"code": 921881,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 7,
|
||||
@@ -826,7 +834,7 @@
|
||||
"name": "icon-tabular-lad",
|
||||
"id": 2,
|
||||
"code": 921888,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 6,
|
||||
@@ -834,7 +842,7 @@
|
||||
"name": "icon-tabular-lad-set",
|
||||
"id": 1,
|
||||
"code": 921889,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 8,
|
||||
@@ -842,7 +850,7 @@
|
||||
"name": "icon-tabular-realtime",
|
||||
"id": 3,
|
||||
"code": 921890,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 23,
|
||||
@@ -850,7 +858,7 @@
|
||||
"name": "icon-tabular-scrolling",
|
||||
"id": 18,
|
||||
"code": 921891,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 112,
|
||||
@@ -858,7 +866,7 @@
|
||||
"name": "icon-telemetry",
|
||||
"id": 86,
|
||||
"code": 921892,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 90,
|
||||
@@ -866,7 +874,7 @@
|
||||
"name": "icon-telemetry-panel",
|
||||
"id": 85,
|
||||
"code": 921893,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 93,
|
||||
@@ -874,15 +882,15 @@
|
||||
"name": "icon-timeline",
|
||||
"id": 88,
|
||||
"code": 921894,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 116,
|
||||
"id": 101,
|
||||
"name": "icon-timer-v1.5",
|
||||
"name": "icon-timer-v15",
|
||||
"prevSize": 24,
|
||||
"code": 921895,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 11,
|
||||
@@ -890,7 +898,7 @@
|
||||
"name": "icon-topic",
|
||||
"id": 6,
|
||||
"code": 921896,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 115,
|
||||
@@ -898,7 +906,7 @@
|
||||
"name": "icon-box-with-dashed-lines",
|
||||
"id": 29,
|
||||
"code": 921897,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 126,
|
||||
@@ -906,7 +914,7 @@
|
||||
"name": "icon-summary-widget",
|
||||
"prevSize": 24,
|
||||
"code": 921904,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 139,
|
||||
@@ -914,13 +922,13 @@
|
||||
"name": "icon-notebook",
|
||||
"prevSize": 24,
|
||||
"code": 921905,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"name": "openmct-symbols-16px",
|
||||
"importSize": {
|
||||
"width": 512,
|
||||
"width": 745,
|
||||
"height": 512
|
||||
},
|
||||
"designer": "Charles Hacskaylo",
|
||||
@@ -2360,7 +2368,7 @@
|
||||
},
|
||||
{
|
||||
"paths": [
|
||||
"M0 0v256h128v-64h256v704h-192v128h640v-128h-192v-704h256v64h128v-256z"
|
||||
"M800 1024h224l-384-1024h-256l-384 1024h224l84-224h408zM380 608l132-352 132 352z"
|
||||
],
|
||||
"grid": 16,
|
||||
"tags": [
|
||||
@@ -2368,9 +2376,15 @@
|
||||
],
|
||||
"defaultCode": 228,
|
||||
"id": 84,
|
||||
"attrs": [],
|
||||
"attrs": [
|
||||
{}
|
||||
],
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"colorPermutations": {
|
||||
"1161751207457516161751": []
|
||||
"1161751207457516161751": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -2840,6 +2854,30 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 119,
|
||||
"paths": [
|
||||
"M1226.4 320h-176l-76.22 203.24 77 205.34 87.22-232.58 90.74 242h-174.44l49.5 132h174.44l57.76 154h154l-264-704z",
|
||||
"M384 0l-384 1024h224l84-224h408l84 224h224l-384-1024zM380 608l132-352 132 352z"
|
||||
],
|
||||
"attrs": [
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"width": 1490,
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"grid": 16,
|
||||
"tags": [
|
||||
"icon-font-size-alt1"
|
||||
],
|
||||
"colorPermutations": {
|
||||
"1161751207457516161751": [
|
||||
{},
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"paths": [
|
||||
"M576 64h-256l320 320h-290.256c-44.264-76.516-126.99-128-221.744-128h-128v512h128c94.754 0 177.48-51.484 221.744-128h290.256l-320 320h256l448-448-448-448z"
|
||||
@@ -3740,7 +3778,9 @@
|
||||
"classSelector": ".ui-symbol",
|
||||
"showMetrics": true,
|
||||
"showMetadata": true,
|
||||
"embed": false
|
||||
"embed": false,
|
||||
"noie8": true,
|
||||
"ie7": false
|
||||
},
|
||||
"imagePref": {
|
||||
"prefix": "icon-",
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<glyph unicode="󡀩" glyph-name="icon-refresh" d="M960 528v432l-164.8-164.8c-79.8 65.2-178.8 100.8-283.2 100.8-119.6 0-232.2-46.6-316.8-131.2s-131.2-197.2-131.2-316.8 46.6-232.2 131.2-316.8c84.6-84.6 197.2-131.2 316.8-131.2s232.2 46.6 316.8 131.2c69.4 69.4 113.2 157.4 126.6 252.8h-130c-29.8-145.8-159-256-313.6-256-176.4 0-320 143.6-320 320s143.8 320 320.2 320c72 0 138.4-23.8 192-64l-176-176h432z" />
|
||||
<glyph unicode="󡀰" glyph-name="icon-save" d="M192.2 384c-0.2 0-0.2 0 0 0l-0.2-448h640v447.8c0 0 0 0-0.2 0.2h-639.6zM978.8 749.2l-165.4 165.4c-25 25-74.2 45.4-109.4 45.4h-576c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128v448c0 35.2 28.8 64 64 64h640c35.2 0 64-28.8 64-64v-448c70.4 0 128 57.6 128 128v576c0 35.2-20.4 84.4-45.2 109.2zM704 704c0-35.2-28.8-64-64-64h-448c-35.2 0-64 28.8-64 64v192h320v-192h128v192h128v-192z" />
|
||||
<glyph unicode="󡀱" glyph-name="icon-sine" d="M1022.294 448c-1.746 7.196-3.476 14.452-5.186 21.786-20.036 85.992-53.302 208.976-98 306.538-22.42 48.938-45.298 86.556-69.946 115.006-48.454 55.93-98.176 67.67-131.356 67.67s-82.902-11.74-131.356-67.672c-24.648-28.45-47.528-66.068-69.948-115.006-44.696-97.558-77.962-220.544-98-306.538-21.646-92.898-46.444-175.138-71.71-237.836-16.308-40.46-30.222-66.358-40.6-82.604-10.378 16.246-24.292 42.142-40.6 82.604-23.272 57.75-46.144 132.088-66.524 216.052h-197.362c1.746-7.196 3.476-14.452 5.186-21.786 20.036-85.992 53.302-208.976 98-306.538 22.42-48.938 45.298-86.556 69.946-115.006 48.454-55.932 98.176-67.672 131.356-67.672s82.902 11.74 131.356 67.672c24.648 28.45 47.528 66.068 69.948 115.006 44.696 97.558 77.962 220.544 98 306.538 21.646 92.898 46.444 175.138 71.71 237.836 16.308 40.46 30.222 66.358 40.6 82.604 10.378-16.246 24.292-42.142 40.6-82.604 23.274-57.748 46.146-132.086 66.526-216.050h197.36z" />
|
||||
<glyph unicode="󡀲" glyph-name="icon-T" d="M0 960v-256h128v64h256v-704h-192v-128h640v128h-192v704h256v-64h128v256z" />
|
||||
<glyph unicode="󡀲" glyph-name="icon-T" d="M800-64h224l-384 1024h-256l-384-1024h224l84 224h408zM380 352l132 352 132-352z" />
|
||||
<glyph unicode="󡀳" glyph-name="icon-thumbs-strip" d="M448 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM448 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320z" />
|
||||
<glyph unicode="󡀴" glyph-name="icon-two-parts-both" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM128 832h320v-768h-320v768zM896 64h-320v768h320v-768z" />
|
||||
<glyph unicode="󡀵" glyph-name="icon-two-parts-one-only" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM896 64h-320v768h320v-768z" />
|
||||
@@ -88,6 +88,7 @@
|
||||
<glyph unicode="󡁆" glyph-name="icon-frame-hide" d="M128 770h420l104 128h-652v-802.4l128 157.4zM896 130h-420l-104-128h652v802.4l-128-157.4zM832 962l-832-1024h192l832 1024zM392 578l104 128h-304v-128z" />
|
||||
<glyph unicode="󡁇" glyph-name="icon-import" d="M832 767.6v-639.4c0-0.2-0.2-0.2-0.4-0.4h-319.6v-192h320c105.6 0 192 86.4 192 192v640.2c0 105.6-86.4 192-192 192h-320v-192h319.6c0.2 0 0.4-0.2 0.4-0.4zM192 256v-192l384 384-384 384v-192h-192v-384z" />
|
||||
<glyph unicode="󡁈" glyph-name="icon-export" d="M192 128.34v639.32l0.34 0.34h319.66v192h-320c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h320v192h-319.66zM1024 448l-384 384v-192h-192v-384h192v-192l384 384z" />
|
||||
<glyph unicode="󡁉" glyph-name="icon-font-size-alt1" horiz-adv-x="1490" d="M1226.4 640h-176l-76.22-203.24 77-205.34 87.22 232.58 90.74-242h-174.44l49.5-132h174.44l57.76-154h154l-264 704zM384 960l-384-1024h224l84 224h408l84-224h224l-384 1024zM380 352l132 352 132-352z" />
|
||||
<glyph unicode="󡄀" glyph-name="icon-activity" d="M576 896h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" />
|
||||
<glyph unicode="󡄁" glyph-name="icon-activity-mode" d="M512 960c-214.866 0-398.786-132.372-474.744-320h90.744c56.86 0 107.938-24.724 143.094-64h240.906l-192 192h256l320-320-320-320h-256l192 192h-240.906c-35.156-39.276-86.234-64-143.094-64h-90.744c75.958-187.628 259.878-320 474.744-320 282.77 0 512 229.23 512 512s-229.23 512-512 512z" />
|
||||
<glyph unicode="󡄂" glyph-name="icon-autoflow-tabular" d="M192 960c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 960h256v-1024h-256v1024zM832 960h-64v-704h256v512c0 105.6-86.4 192-192 192z" />
|
||||
@@ -115,7 +116,7 @@
|
||||
<glyph unicode="󡄤" glyph-name="icon-telemetry" d="M32 328.34c14.28 5.62 54.44 47.54 92.96 146 42.46 108.38 116.32 237.66 227.040 237.66 52.4 0 101.42-29.16 145.7-86.68 37.34-48.5 64.84-108.92 81.34-151.080 38.52-98.38 78.68-140.3 92.96-146 14.28 5.62 54.44 47.54 92.96 146 37.4 95.5 99.14 207.14 188.94 232.46-90.462 152.598-254.314 253.3-441.686 253.3-0.075 0-0.15 0-0.226 0-282.748 0-511.988-229.24-511.988-512 0-0.032 0-0.070 0-0.108 0-35.719 3.641-70.587 10.572-104.255 8.968-7.457 16.648-13.417 21.428-15.297zM992 567.66c-14.28-5.62-54.44-47.52-92.96-146-42.46-108.38-116.32-237.66-227.040-237.66-52.4 0-101.42 29.16-145.7 86.68-37.34 48.5-64.84 108.92-81.34 151.080-38.52 98.38-78.68 140.3-92.96 146-14.28-5.62-54.44-47.52-92.96-146-37.4-95.5-99.14-207.14-188.94-232.46 90.462-152.598 254.314-253.3 441.686-253.3 0.075 0 0.15 0 0.226 0 282.748 0 511.988 229.24 511.988 512 0 0.032 0 0.070 0 0.108 0 35.719-3.641 70.587-10.572 104.255-8.968 7.457-16.648 13.417-21.428 15.297z" />
|
||||
<glyph unicode="󡄥" glyph-name="icon-telemetry-panel" d="M169.2 512c14 56.4 33 122 56.6 176.8 15.4 35.8 31.2 63.2 48.2 84 18.4 22.4 49 49.2 91 49.2s72.6-26.8 91-49.2c17-20.6 32.6-48.2 48.2-84 23.6-54.8 42.8-120.4 56.6-176.8h461.2v256c0 105.6-86.4 192-192 192h-640c-105.6 0-192-86.4-192-192v-256h171.2zM718.6 384h-127.2c25-93.4 48.4-144.4 63.6-168.6 15.2 24.2 38.6 75.2 63.6 168.6zM301.4 512h127.2c-25 93.4-48.4 144.4-63.6 168.6-15.2-24.2-38.6-75.2-63.6-168.6zM850.8 384c-14-56.4-33-122-56.6-176.8-15.4-35.8-31.2-63.2-48.2-84-18.4-22.4-49-49.2-91-49.2s-72.6 26.8-91 49.2c-17 20.6-32.6 48.2-48.2 84-23.6 54.8-42.8 120.4-56.6 176.8h-461.2v-256c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v256h-171.2z" />
|
||||
<glyph unicode="󡄦" glyph-name="icon-timeline" d="M256 704h384v-128h-384v128zM384 512h384v-128h-384v128zM320 320h384v-128h-384v128zM832 960h-128v-192h127.6c0.2 0 0.2-0.2 0.4-0.4v-639.4c0-0.2-0.2-0.2-0.4-0.4h-127.6v-192h128c105.6 0 192 86.4 192 192v640.2c0 105.6-86.4 192-192 192zM192 128.4v639.2c0 0.2 0.2 0.2 0.4 0.4h127.6v192h-128c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h128v192h-127.6c-0.2 0-0.4 0.2-0.4 0.4z" />
|
||||
<glyph unicode="󡄧" glyph-name="icon-timer-v1.5" horiz-adv-x="896" d="M576 813.4v82.58c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-82.58c-185.040-55.080-320-226.48-320-429.42 0-247.42 200.58-448 448-448s448 200.58 448 448c0 202.96-135 374.4-320 429.42zM468 363.98l-263.76-211c-57.105 59.935-92.24 141.251-92.24 230.772 0 0.080 0 0.16 0 0.24 0 185.268 150.72 335.988 336 335.988 6.72 0 13.38-0.22 20-0.62v-355.38z" />
|
||||
<glyph unicode="󡄧" glyph-name="icon-timer-v15" horiz-adv-x="896" d="M576 813.4v82.58c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-82.58c-185.040-55.080-320-226.48-320-429.42 0-247.42 200.58-448 448-448s448 200.58 448 448c0 202.96-135 374.4-320 429.42zM468 363.98l-263.76-211c-57.105 59.935-92.24 141.251-92.24 230.772 0 0.080 0 0.16 0 0.24 0 185.268 150.72 335.988 336 335.988 6.72 0 13.38-0.22 20-0.62v-355.38z" />
|
||||
<glyph unicode="󡄨" glyph-name="icon-topic" d="M454.36 483.36l86.3 86.3c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c19.328-19.358 42.832-34.541 69.047-44.082l1.313 171.722-57.64 57.64c-34.407 34.33-81.9 55.558-134.35 55.558s-99.943-21.228-134.354-55.562l-86.296-86.297c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-28.674 28.654v-172.14c19.045-7.022 41.040-11.084 63.984-11.084 52.463 0 99.966 21.239 134.379 55.587zM505.64 412.64l-86.3-86.3c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-86.294 86.294c-2 2-4.2 4-6.36 6v-197.36c33.664-30.72 78.65-49.537 128.031-49.537 52.44 0 99.923 21.22 134.333 55.541l86.296 86.296c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c2-2 4.2-4 6.36-6v197.36c-33.664 30.72-78.65 49.537-128.031 49.537-52.44 0-99.923-21.22-134.333-55.541zM832 960h-128v-192h127.66l0.34-0.34v-639.32l-0.34-0.34h-127.66v-192h128c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM320 128h-127.66l-0.34 0.34v639.32l0.34 0.34h127.66v192h-128c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h128v192z" />
|
||||
<glyph unicode="󡄩" glyph-name="icon-box-with-dashed-lines" d="M0 576h128v-256h-128v256zM128 831.78l0.22 0.22h191.78v128h-192c-70.606-0.215-127.785-57.394-128-127.979v-192.021h128v191.78zM128 64.22v191.78h-128v-192c0.215-70.606 57.394-127.785 127.979-128h192.021v128h-191.78zM384 960h256v-128h-256v128zM896 64.22l-0.22-0.22h-191.78v-128h192c70.606 0.215 127.785 57.394 128 127.979v192.021h-128v-191.78zM896 960h-192v-128h191.78l0.22-0.22v-191.78h128v192c-0.215 70.606-57.394 127.785-127.979 128zM896 576h128v-256h-128v256zM384 64h256v-128h-256v128zM256 704h512v-512h-512v512z" />
|
||||
<glyph unicode="󡄰" glyph-name="icon-summary-widget" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM847.8 349.6l-82.6-143.2-189.6 131.6 19.2-230h-165.4l19.2 230-189.6-131.6-82.6 143.2 208.6 98.4-208.8 98.4 82.6 143.2 189.6-131.6-19.2 230h165.4l-19.2-230 189.6 131.6 82.6-143.2-208.6-98.4 208.8-98.4z" />
|
||||
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="c-create-button--w">
|
||||
<div class="c-ctrl-wrapper">
|
||||
<div class="c-create-button c-button--menu c-button--major icon-plus"
|
||||
@click="toggleCreateMenu">
|
||||
<span class="c-button__label">Create</span>
|
||||
@@ -31,10 +31,6 @@
|
||||
|
||||
.c-create-button,
|
||||
.c-create-menu {
|
||||
&--w {
|
||||
// Wrapper for Create button and menu
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
168
src/ui/components/controls/checkboxCustom.vue
Normal file
168
src/ui/components/controls/checkboxCustom.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="c-custom-checkbox">
|
||||
<input type="checkbox"
|
||||
:id="id"
|
||||
:name="name"
|
||||
:value="value"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
@change="onChange"
|
||||
:checked="state">
|
||||
<label :for="id">
|
||||
<div class="c-custom-checkbox__box"></div>
|
||||
<div class="c-custom-checkbox__label-text">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.c-custom-checkbox {
|
||||
$d: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
label {
|
||||
@include userSelectNone();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__box {
|
||||
@include nice-input();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: $d;
|
||||
width: $d;
|
||||
height: $d;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
|
||||
&:checked + label > .c-custom-checkbox__box {
|
||||
background: $colorKey;
|
||||
&:before {
|
||||
color: $colorKeyFg;
|
||||
content: $glyph-icon-check;
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.6em;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:disabled) + label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:disabled + label {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/*
|
||||
Custom checkbox control. Use just like a checkbox in HTML, except label string is passed within tag.
|
||||
Supports value, true-value, false-value, checked and disabled attributes.
|
||||
Example usage:
|
||||
<checkbox checked>Enable markers</checkbox>
|
||||
*/
|
||||
export default {
|
||||
model: {
|
||||
prop: 'modelValue',
|
||||
event: 'input'
|
||||
},
|
||||
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: function () {
|
||||
return 'checkbox-id-' + this._uid;
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
value: {
|
||||
default: null,
|
||||
},
|
||||
modelValue: {
|
||||
default: undefined,
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
model: {}
|
||||
},
|
||||
|
||||
computed: {
|
||||
state() {
|
||||
if (this.modelValue === undefined) {
|
||||
return this.checked;
|
||||
}
|
||||
|
||||
if (Array.isArray(this.modelValue)) {
|
||||
return this.modelValue.indexOf(this.value) > -1;
|
||||
}
|
||||
|
||||
return !!this.modelValue;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange() {
|
||||
this.toggle();
|
||||
},
|
||||
|
||||
toggle() {
|
||||
let value;
|
||||
|
||||
if (Array.isArray(this.modelValue)) {
|
||||
value = this.modelValue.slice(0);
|
||||
|
||||
if (this.state) {
|
||||
value.splice(value.indexOf(this.value), 1);
|
||||
} else {
|
||||
value.push(this.value);
|
||||
}
|
||||
} else {
|
||||
value = !this.state;
|
||||
}
|
||||
|
||||
this.$emit('input', value);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
checked(newValue) {
|
||||
if (newValue !== this.state) {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.checked && !this.state) {
|
||||
this.toggle();
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
51
src/ui/components/controls/labeledNumberInput.vue
Normal file
51
src/ui/components/controls/labeledNumberInput.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="c-labeled-input"
|
||||
:title="title">
|
||||
<div class="c-labeled-input__label">{{ label }}</div>
|
||||
<input type="number"
|
||||
v-bind="$attrs"
|
||||
v-bind:value="value"
|
||||
v-on="inputListeners"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* Emits input and clear events */
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
value: String,
|
||||
label: String,
|
||||
title: String
|
||||
},
|
||||
computed: {
|
||||
inputListeners: function () {
|
||||
let vm = this;
|
||||
return Object.assign({},
|
||||
this.$listeners,
|
||||
{
|
||||
input: function (event) {
|
||||
vm.$emit('input', event.target.value);
|
||||
},
|
||||
change: function (event) {
|
||||
vm.$emit('change', event.target.value);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
// active: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearInput() {
|
||||
// Clear the user's input and set 'active' to false
|
||||
this.value = '';
|
||||
this.$emit('clear','');
|
||||
this.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -15,45 +15,15 @@
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* SEARCH */
|
||||
.c-search {
|
||||
@include nice-input();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 4px;
|
||||
@include wrappedInput();
|
||||
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
&:before {
|
||||
// Mag glass icon
|
||||
content: $glyph-icon-magnify;
|
||||
direction: rtl; // Aligns glyph to right-hand side of container, for transition
|
||||
display: block;
|
||||
font-family: symbolsfont;
|
||||
flex: 0 0 auto;
|
||||
opacity: 0.5;
|
||||
overflow: hidden;
|
||||
padding: 2px 0; // Prevents clipping
|
||||
transition: width 250ms ease;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset rgba(black, 0.8) 0 0px 2px;
|
||||
&:before {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
&--major {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
&__input {
|
||||
background: none !important;
|
||||
box-shadow: none !important; // !important needed to override default for [input]
|
||||
flex: 1 1 auto;
|
||||
padding-left: 2px !important;
|
||||
padding-right: 2px !important;
|
||||
min-width: 10px; // Must be set to allow input to collapse below browser min
|
||||
}
|
||||
|
||||
&__clear-input {
|
||||
@@ -61,11 +31,6 @@
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
&:before {
|
||||
padding: 2px 0px;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.c-search__clear-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
176
src/ui/components/controls/toggleButton.vue
Normal file
176
src/ui/components/controls/toggleButton.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div class="c-togglebutton">
|
||||
<input type="checkbox"
|
||||
:id="id"
|
||||
:name="name"
|
||||
:value="value"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
@change="onChange"
|
||||
:checked="state">
|
||||
<label :for="id">
|
||||
<div class="c-togglebutton__on"
|
||||
:class="innerClassOn"></div>
|
||||
<div class="c-togglebutton__off"
|
||||
:class="innerClassOff"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.c-togglebutton {
|
||||
$d: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.c-togglebutton__on {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
|
||||
&:checked + label {
|
||||
.c-togglebutton__on {
|
||||
display: block;
|
||||
}
|
||||
.c-togglebutton__off {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:disabled) + label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:disabled + label {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/*
|
||||
Toggle button control, based on checkboxCustom. Use just like a checkbox in HTML.
|
||||
Requires inner-class-on and -off attributes to be passed.
|
||||
Supports checked and disabled attributes.
|
||||
Example usage:
|
||||
<toggle-button checked
|
||||
class="c-click-icon"
|
||||
inner-class-on="icon-grid-snap-to"
|
||||
inner-class-off="icon-grid-snap-no"></toggle-button>
|
||||
*/
|
||||
export default {
|
||||
model: {
|
||||
prop: 'modelValue',
|
||||
event: 'input'
|
||||
},
|
||||
|
||||
props: {
|
||||
innerClassOn: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: true
|
||||
},
|
||||
innerClassOff: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: function () {
|
||||
return 'checkbox-id-' + this._uid;
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
value: {
|
||||
default: null,
|
||||
},
|
||||
modelValue: {
|
||||
default: undefined,
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
model: {}
|
||||
},
|
||||
|
||||
computed: {
|
||||
state() {
|
||||
if (this.modelValue === undefined) {
|
||||
return this.checked;
|
||||
}
|
||||
|
||||
if (Array.isArray(this.modelValue)) {
|
||||
return this.modelValue.indexOf(this.value) > -1;
|
||||
}
|
||||
return !!this.modelValue;
|
||||
},
|
||||
stateClass() {
|
||||
return this.onClass;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange() {
|
||||
this.toggle();
|
||||
},
|
||||
|
||||
toggle() {
|
||||
let value;
|
||||
|
||||
if (Array.isArray(this.modelValue)) {
|
||||
value = this.modelValue.slice(0);
|
||||
|
||||
if (this.state) {
|
||||
value.splice(value.indexOf(this.value), 1);
|
||||
} else {
|
||||
value.push(this.value);
|
||||
}
|
||||
} else {
|
||||
value = !this.state;
|
||||
}
|
||||
|
||||
this.$emit('input', value);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
checked(newValue) {
|
||||
if (newValue !== this.state) {
|
||||
this.toggle();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.checked && !this.state) {
|
||||
this.toggle();
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
|
||||
<div class="l-browse-bar__end">
|
||||
<div class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
|
||||
<div class="l-browse-bar__view-switcher c-menu-button--w c-menu-button--menus-left"
|
||||
v-if="views.length > 1">
|
||||
<div class="c-button--menu"
|
||||
:class="currentView.cssClass"
|
||||
@@ -113,6 +113,8 @@
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* START */
|
||||
|
||||
.l-browse-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
15
src/ui/components/layout/Conductor.vue
Normal file
15
src/ui/components/layout/Conductor.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include>
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,14 +3,8 @@
|
||||
<div class="l-shell__head">
|
||||
<CreateButton class="l-shell__create-button"></CreateButton>
|
||||
<div class="l-shell__controls">
|
||||
<a class="c-click-icon icon-new-window" title="Open in a new browser tab"
|
||||
@click="openInNewTab"
|
||||
target="_blank">
|
||||
</a>
|
||||
<a v-bind:class="['c-click-icon', fullScreen ? 'icon-fullscreen-expand' : 'icon-fullscreen-collapse']"
|
||||
v-bind:title="`${fullScreen ? 'Exit' : 'Enable'} full screen mode`"
|
||||
@click="fullScreenToggle">
|
||||
</a>
|
||||
<a class="c-click-icon icon-new-window" title="Open in a new browser tab"></a>
|
||||
<a class="c-click-icon icon-fullscreen-collapse" title="Enable full screen mode"></a>
|
||||
</div>
|
||||
<div class="l-shell__app-logo">[ App Logo ]</div>
|
||||
</div>
|
||||
@@ -27,16 +21,15 @@
|
||||
<mct-tree></mct-tree>
|
||||
</div>
|
||||
</pane>
|
||||
<pane class="l-shell__pane-main">
|
||||
<pane class="l-shell__pane-main"
|
||||
:class="{ 'is-editing' : true }">
|
||||
<browse-bar class="l-shell__main-view-browse-bar"
|
||||
ref="browseBar">
|
||||
</browse-bar>
|
||||
ref="browseBar"></browse-bar>
|
||||
<toolbar class="l-shell__toolbar"></toolbar>
|
||||
<object-view class="l-shell__main-container"
|
||||
ref="browseObject">
|
||||
</object-view>
|
||||
<component class="l-shell__time-conductor"
|
||||
:is="conductorComponent">
|
||||
</component>
|
||||
ref="browseObject"></object-view>
|
||||
<mct-template template-key="conductor"
|
||||
class="l-shell__time-conductor"></mct-template>
|
||||
</pane>
|
||||
<pane class="l-shell__pane-inspector l-pane--holds-multipane"
|
||||
handle="before"
|
||||
@@ -135,10 +128,6 @@
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
body.mobile & .l-shell__main-view-browse-bar {
|
||||
margin-left: $mobileMenuIconD - $interiorMarginLg; // Make room for the hamburger!
|
||||
}
|
||||
|
||||
&__head {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -162,7 +151,7 @@
|
||||
margin-right: 2.5%;
|
||||
}
|
||||
|
||||
/********** MAIN AREA */
|
||||
/******************************* MAIN AREA */
|
||||
&__main-container {
|
||||
// Wrapper for main views
|
||||
flex: 1 1 auto;
|
||||
@@ -196,6 +185,11 @@
|
||||
&__pane-inspector {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
&__toolbar {
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -212,34 +206,7 @@
|
||||
import multipane from '../controls/multipane.vue';
|
||||
import pane from '../controls/pane.vue';
|
||||
import BrowseBar from './BrowseBar.vue';
|
||||
|
||||
var enterFullScreen = () => {
|
||||
var docElm = document.documentElement;
|
||||
|
||||
if (docElm.requestFullscreen) {
|
||||
docElm.requestFullscreen();
|
||||
} else if (docElm.mozRequestFullScreen) { /* Firefox */
|
||||
docElm.mozRequestFullScreen();
|
||||
} else if (docElm.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
|
||||
docElm.webkitRequestFullscreen();
|
||||
} else if (docElm.msRequestFullscreen) { /* IE/Edge */
|
||||
docElm.msRequestFullscreen();
|
||||
}
|
||||
};
|
||||
var exitFullScreen = () => {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
}
|
||||
else if (document.webkitCancelFullScreen) {
|
||||
document.webkitCancelFullScreen();
|
||||
}
|
||||
else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
}
|
||||
import Toolbar from './Toolbar.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -253,28 +220,8 @@
|
||||
search,
|
||||
multipane,
|
||||
pane,
|
||||
BrowseBar
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
fullScreen: false,
|
||||
conductorComponent: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fullScreenToggle() {
|
||||
|
||||
if (this.fullScreen) {
|
||||
this.fullScreen = false;
|
||||
exitFullScreen();
|
||||
} else {
|
||||
this.fullScreen = true;
|
||||
enterFullScreen();
|
||||
}
|
||||
},
|
||||
openInNewTab(event) {
|
||||
event.target.href = window.location.href;
|
||||
}
|
||||
BrowseBar,
|
||||
Toolbar
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.l-object-view {
|
||||
.c-object-view {
|
||||
display: contents;
|
||||
}
|
||||
</style>
|
||||
@@ -33,6 +33,7 @@ export default {
|
||||
this.debounceUpdateView = _.debounce(this.updateView, 10);
|
||||
},
|
||||
mounted() {
|
||||
this.currentObject = this.object;
|
||||
this.updateView();
|
||||
},
|
||||
methods: {
|
||||
@@ -50,7 +51,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.viewContainer = document.createElement('div');
|
||||
this.viewContainer.classList.add('l-object-view');
|
||||
this.viewContainer.classList.add('c-object-view');
|
||||
this.$el.append(this.viewContainer);
|
||||
let provider = this.openmct.objectViews.getByProviderKey(this.viewKey);
|
||||
if (!provider) {
|
||||
|
||||
291
src/ui/components/layout/Toolbar.vue
Normal file
291
src/ui/components/layout/Toolbar.vue
Normal file
@@ -0,0 +1,291 @@
|
||||
<template>
|
||||
<div class="c-toolbar">
|
||||
<!-- VERSION MANUALLY RESTORED FROM VUE-LAYOUT -->
|
||||
<div class="c-button-set">
|
||||
<div class="c-ctrl-wrapper">
|
||||
<div class="c-button--menu js-add-button icon-plus"
|
||||
@click="toggleMenus">
|
||||
<div class="c-button__label">Add</div>
|
||||
</div>
|
||||
<div class="c-menu" v-if="showMenus">
|
||||
<ul>
|
||||
<li v-for="item in addMenuItems"
|
||||
:class="item.class"
|
||||
:title="item.title">
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-button-set"
|
||||
v-if="toolsItemSelected">
|
||||
<div class="c-ctrl-wrapper"
|
||||
v-if="toolsItemSelected">
|
||||
<div class="c-click-icon c-click-icon--menu js-layers icon-layers"
|
||||
@click="toggleMenus"></div>
|
||||
<div class="c-menu" v-if="showMenus">
|
||||
<ul>
|
||||
<li v-for="item in layersMenuItems"
|
||||
:class="item.class"
|
||||
:title="item.title">
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-ctrl-wrapper"
|
||||
v-if="toolsColorFill">
|
||||
<div class="c-click-icon c-click-icon--swatched js-color-fill icon-paint-bucket"
|
||||
@click="toggleMenus">
|
||||
<div class="c-swatch" style="background: #33ff00;"></div>
|
||||
</div>
|
||||
<div class="c-menu c-palette c-palette--color"
|
||||
v-if="showMenus">
|
||||
<div class="c-palette__item-none"
|
||||
vif="this.palette.itemNone === true">
|
||||
<div class="c-palette__item"
|
||||
@click="this.setColor('no-color')"></div>
|
||||
No fill
|
||||
</div>
|
||||
<div class="c-palette__items">
|
||||
<div class="c-palette__item"
|
||||
v-for="color in colorPalette"
|
||||
:style="{ background: color.value }"
|
||||
@click="this.setColor(color.value)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-ctrl-wrapper"
|
||||
v-if="toolsColorStroke">
|
||||
<div class="c-click-icon c-click-icon--swatched js-color-stroke icon-pencil">
|
||||
<div class="c-toolbar-button__swatch" style="background: #ffffff;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-ctrl-wrapper"
|
||||
v-if="toolsColorText">
|
||||
<div class="c-click-icon c-click-icon--swatched js-color-text icon-font">
|
||||
<div class="c-toolbar-button__swatch" style="background: #333333;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="c-button-set"
|
||||
v-if="toolsItemSelected && toolsFontSize">
|
||||
<div class="c-ctrl-wrapper">
|
||||
<div class="c-click-icon c-click-icon--menu js-font-size"
|
||||
@click="toggleMenus">
|
||||
<div class="c-button__label">11 px</div>
|
||||
</div>
|
||||
<div class="c-menu" v-if="showMenus">
|
||||
<ul>
|
||||
<li v-for="item in fontSizeMenuItems">
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-button-set"
|
||||
v-if="toolsItemSelected && toolsEditProperties">
|
||||
<div class="c-ctrl-wrapper">
|
||||
<div class="c-click-icon js-image icon-gear"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="c-button-set"
|
||||
v-if="toolsItemSelected">
|
||||
<labeledNumberInput label="X" value=1 title="X position"></labeledNumberInput>
|
||||
<labeledNumberInput label="Y" value=2 title="Y position"></labeledNumberInput>
|
||||
<labeledNumberInput label="W" value=3 title="Width"></labeledNumberInput>
|
||||
<labeledNumberInput label="H" value=4 title="Height"></labeledNumberInput>
|
||||
</div>
|
||||
|
||||
<div class="c-button-set"
|
||||
v-if="toolsItemSelected">
|
||||
<div class="c-click-icon c-click-icon--caution icon-trash"></div>
|
||||
</div>
|
||||
|
||||
<div class="c-button-set"
|
||||
v-if="toolsItemSelected">
|
||||
<checkbox checked title="This is a checkbox">Checkbox</checkbox>
|
||||
</div>
|
||||
|
||||
<div class="c-button-set"
|
||||
v-if="toolsItemSelected">
|
||||
<toggle-button title="Toggle frame" checked
|
||||
class="c-click-icon"
|
||||
inner-class-on="icon-frame-show"
|
||||
inner-class-off="icon-frame-hide"></toggle-button>
|
||||
<toggle-button title="Snap to grid" checked
|
||||
class="c-click-icon"
|
||||
inner-class-on="icon-grid-snap-to"
|
||||
inner-class-off="icon-grid-snap-no"></toggle-button>
|
||||
<toggle-button title="Show label and value" checked
|
||||
class="c-click-icon"
|
||||
inner-class-on="icon-two-parts-both"
|
||||
inner-class-off="icon-two-parts-one-only"></toggle-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import labeledNumberInput from '../controls/labeledNumberInput.vue';
|
||||
import checkbox from '../controls/checkboxCustom.vue';
|
||||
import toggleButton from '../controls/toggleButton.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
labeledNumberInput,
|
||||
checkbox,
|
||||
toggleButton
|
||||
},
|
||||
methods: {
|
||||
toggleMenus: function () {
|
||||
this.showMenus = !this.showMenus;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
toolsItemSelected: { type: Boolean, default: true },
|
||||
toolsColorFill: { type: Boolean, default: true },
|
||||
toolsColorStroke: { type: Boolean, default: true },
|
||||
toolsColorText: { type: Boolean, default: true },
|
||||
toolsFontSize: { type: Boolean, default: true },
|
||||
toolsEditProperties: { type: Boolean, default: true },
|
||||
toolSetBox: ['toolsColorFill', 'toolsColorStroke'],
|
||||
toolSetLine: ['toolsColorStroke'],
|
||||
toolSetText: ['toolsColorFill', 'toolsColorStroke', 'toolsColorText', 'toolsFontSize', 'toolsEditProperties'],
|
||||
toolSetImage: ['toolsColorStroke', 'toolsEditProperties'],
|
||||
toolSetTelemetry: ['toolsColorFill', 'toolsColorStroke', 'toolsColorText', 'toolsFontSize', 'toolsLabelValue']
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
showMenus: false,
|
||||
addMenuItems: [
|
||||
{ name: 'Box', class: 'icon-box', title: 'Add Box' },
|
||||
{ name: 'Line', class: 'icon-line-horz', title: 'Add Line' },
|
||||
{ name: 'Text', class: 'icon-font', title: 'Add Text' },
|
||||
{ name: 'Image', class: 'icon-image', title: 'Add Image' }
|
||||
],
|
||||
layersMenuItems: [
|
||||
{ name: 'Move to top', class: 'icon-arrow-double-up', title: 'Move to top' },
|
||||
{ name: 'Move up', class: 'icon-arrow-up', title: 'Move up' },
|
||||
{ name: 'Move down', class: 'icon-arrow-down', title: 'Move down' },
|
||||
{ name: 'Move to bottom', class: 'icon-arrow-double-down', title: 'Move to bottom' }
|
||||
],
|
||||
fontSizeMenuItems: [
|
||||
{ value: '9', name: '9 px' },
|
||||
{ value: '10', name: '10 px' },
|
||||
{ value: '11', name: '11 px' },
|
||||
{ value: '12', name: '12 px' },
|
||||
{ value: '13', name: '13 px' },
|
||||
{ value: '14', name: '14 px' },
|
||||
{ value: '16', name: '16 px' },
|
||||
{ value: '18', name: '18 px' },
|
||||
{ value: '20', name: '20 px' },
|
||||
{ value: '24', name: '24 px' },
|
||||
{ value: '28', name: '28 px' },
|
||||
{ value: '32', name: '32 px' },
|
||||
{ value: '40', name: '40 px' },
|
||||
{ value: '48', name: '48 px' },
|
||||
{ value: '56', name: '56 px' },
|
||||
{ value: '64', name: '64 px' },
|
||||
{ value: '72', name: '72 px' },
|
||||
{ value: '80', name: '80 px' },
|
||||
{ value: '88', name: '88 px' },
|
||||
{ value: '96', name: '96 px' },
|
||||
{ value: '128', name: '128 px' },
|
||||
{ value: '160', name: '160 px' }
|
||||
],
|
||||
colorPalette: [
|
||||
{ value: '#000000' },
|
||||
{ value: '#434343' },
|
||||
{ value: '#666666' },
|
||||
{ value: '#999999' },
|
||||
{ value: '#b7b7b7' },
|
||||
{ value: '#cccccc' },
|
||||
{ value: '#d9d9d9' },
|
||||
{ value: '#efefef' },
|
||||
{ value: '#f3f3f3' },
|
||||
{ value: '#ffffff' },
|
||||
{ value: '#980000' },
|
||||
{ value: '#ff0000' },
|
||||
{ value: '#ff9900' },
|
||||
{ value: '#ffff00' },
|
||||
{ value: '#00ff00' },
|
||||
{ value: '#00ffff' },
|
||||
{ value: '#4a86e8' },
|
||||
{ value: '#0000ff' },
|
||||
{ value: '#9900ff' },
|
||||
{ value: '#ff00ff' },
|
||||
{ value: '#e6b8af' },
|
||||
{ value: '#f4cccc' },
|
||||
{ value: '#fce5cd' },
|
||||
{ value: '#fff2cc' },
|
||||
{ value: '#d9ead3' },
|
||||
{ value: '#d0e0e3' },
|
||||
{ value: '#c9daf8' },
|
||||
{ value: '#cfe2f3' },
|
||||
{ value: '#d9d2e9' },
|
||||
{ value: '#ead1dc' },
|
||||
{ value: '#dd7e6b' },
|
||||
{ value: '#dd7e6b' },
|
||||
{ value: '#f9cb9c' },
|
||||
{ value: '#ffe599' },
|
||||
{ value: '#b6d7a8' },
|
||||
{ value: '#a2c4c9' },
|
||||
{ value: '#a4c2f4' },
|
||||
{ value: '#9fc5e8' },
|
||||
{ value: '#b4a7d6' },
|
||||
{ value: '#d5a6bd' },
|
||||
{ value: '#cc4125' },
|
||||
{ value: '#e06666' },
|
||||
{ value: '#f6b26b' },
|
||||
{ value: '#ffd966' },
|
||||
{ value: '#93c47d' },
|
||||
{ value: '#76a5af' },
|
||||
{ value: '#6d9eeb' },
|
||||
{ value: '#6fa8dc' },
|
||||
{ value: '#8e7cc3' },
|
||||
{ value: '#c27ba0' },
|
||||
{ value: '#a61c00' },
|
||||
{ value: '#cc0000' },
|
||||
{ value: '#e69138' },
|
||||
{ value: '#f1c232' },
|
||||
{ value: '#6aa84f' },
|
||||
{ value: '#45818e' },
|
||||
{ value: '#3c78d8' },
|
||||
{ value: '#3d85c6' },
|
||||
{ value: '#674ea7' },
|
||||
{ value: '#a64d79' },
|
||||
{ value: '#85200c' },
|
||||
{ value: '#990000' },
|
||||
{ value: '#b45f06' },
|
||||
{ value: '#bf9000' },
|
||||
{ value: '#38761d' },
|
||||
{ value: '#134f5c' },
|
||||
{ value: '#1155cc' },
|
||||
{ value: '#0b5394' },
|
||||
{ value: '#351c75' },
|
||||
{ value: '#741b47' },
|
||||
{ value: '#5b0f00' },
|
||||
{ value: '#660000' },
|
||||
{ value: '#783f04' },
|
||||
{ value: '#7f6000' },
|
||||
{ value: '#274e13' },
|
||||
{ value: '#0c343d' },
|
||||
{ value: '#1c4587' },
|
||||
{ value: '#073763' },
|
||||
{ value: '#20124d' },
|
||||
{ value: '#4c1130' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -22,8 +22,6 @@
|
||||
/*global console */
|
||||
|
||||
define([], function () {
|
||||
const DEFAULT_VIEW_PRIORITY = 100;
|
||||
|
||||
/**
|
||||
* A ViewRegistry maintains the definitions for different kinds of views
|
||||
* that may occur in different places in the user interface.
|
||||
@@ -42,17 +40,10 @@ define([], function () {
|
||||
* which can provide views of this object
|
||||
*/
|
||||
ViewRegistry.prototype.get = function (item) {
|
||||
function byPriority(providerA, providerB) {
|
||||
let priorityA = providerA.priority ? providerA.priority(item) : DEFAULT_VIEW_PRIORITY;
|
||||
let priorityB = providerB.priority ? providerB.priority(item) : DEFAULT_VIEW_PRIORITY;
|
||||
|
||||
return priorityB - priorityA;
|
||||
}
|
||||
|
||||
return this.getAllProviders()
|
||||
.filter(function (provider) {
|
||||
return provider.canView(item);
|
||||
}).sort(byPriority);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ const webpackConfig = {
|
||||
"bourbon": "bourbon.scss",
|
||||
"espresso": path.join(__dirname, "src/styles/theme-espresso.scss"),
|
||||
"snow": path.join(__dirname, "src/styles/theme-snow.scss"),
|
||||
"vue": path.join(__dirname, "node_modules/vue/dist/vue.min.js"),
|
||||
"vue": path.join(__dirname, "node_modules/vue/dist/vue.js"),
|
||||
"d3-scale": path.join(__dirname, "node_modules/d3-scale/build/d3-scale.min.js"),
|
||||
"styles": path.join(__dirname, "src/styles-new")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user