Compare commits

..

29 Commits

Author SHA1 Message Date
charlesh88
0b828b68a3 Getting vue-toolbar reverted back to latest
- Merging this branch with vue-layout may cause conflicts;
2018-09-27 15:09:31 -07:00
charlesh88
76c21ba73a Custom checkbox code cleanups, sanding 2018-09-25 17:33:30 -07:00
charlesh88
d35800a307 Add toggleButton component
- Code and examples
2018-09-25 17:32:53 -07:00
charlesh88
ebd266cbbb Add custom checkbox control.
- Also, code cleanup.
2018-09-25 15:28:50 -07:00
charlesh88
de900702a6 Fixes for toolbar toggleMenus and labeledNumberInput 2018-09-24 15:57:48 -07:00
charlesh88
ae8202b90e Styling tweak for c-labeled-input
- Code cleanup as well;
2018-09-24 15:57:10 -07:00
charlesh88
84350d1776 Font, font-size glyphs and size menu, and more
- Added art for font glyph and renamed from .icon-T;
- Added new glyph for font-size;
- Fixed font-sizing in controls;
- Added font-size menu;
- Re-org'd toolbar items;
2018-09-24 13:40:50 -07:00
charlesh88
88a23151ec Add color palette markup and CSS
- Also added Layers menu example;
2018-09-24 11:57:54 -07:00
charlesh88
1a60a2e13a Fix issues with relative font-sizing 2018-09-21 18:26:12 -07:00
charlesh88
6e81b985a6 Merge in latest from vue-layout
- Conflicts in DisplayLayout.vue resolved;
2018-09-21 18:11:24 -07:00
charlesh88
0ee881f2d2 Significant refactoring of button and click-icon classes
- Markup and CSS updated;
- Toolbar in good shape, prior to merge of vue-layout;
2018-09-21 18:07:13 -07:00
charlesh88
908b843d8b Fixes for selection on nested selected elements 2018-09-21 16:58:17 -07:00
charlesh88
16df2567ed Refactoring button classes
- Lots of stuff broken right now;
- TODO: lots of renaming (c-menu-button, c-icon-button, etc.);
- Removed import of _controls in search.vue;
2018-09-21 16:45:52 -07:00
charlesh88
1aea27f9b6 Toolbar, editing and selection style refinements
- Moved toolbar back into Layout.vue;
- Hard-coded 'is-editing' onto __pane-main for now,
removed from DisplayLayout.vue;
- Styles for frame in LayoutFrame.vue:
-- editing default (dotted border)
-- editing .s-selected
-- .s-drilled-in (renamed .is-drilled-in)
2018-09-21 16:17:42 -07:00
charlesh88
0d1acfd4df Frame edit handles markup and styling 2018-09-21 14:27:54 -07:00
charlesh88
13c984f3c1 Merge remote-tracking branch 'origin/vue-layout' into vue-toolbar 2018-09-21 13:20:21 -07:00
Pegah Sarram
f8aea00792 Define a computed property for the css class object. 2018-09-21 11:28:42 -07:00
charlesh88
15c52fe097 New toolbar buttons WIP 2018-09-21 00:45:55 -07:00
charlesh88
1b4aa5cbbf Toolbar WIP
- Custom wrapped number input added;
- Toolbar buttons WIP;
2018-09-20 18:01:52 -07:00
charlesh88
11acc1cfe5 Edit styling and toolbar WIP
- c-search styles moved mostly into mixin;
- New .c-labeled-input class;
- Browser overrides for number-type input spinners in webkit;
2018-09-20 17:17:59 -07:00
Pegah Sarram
d4c2a44a96 Hide the background grid when a frame is drilled in 2018-09-19 23:59:05 -07:00
Pegah Sarram
7792396c46 Implement drill in gesture. 2018-09-19 23:46:38 -07:00
charlesh88
984cf0ca1e Merge branch 'topic-core-refactor' into vue-layout 2018-09-18 15:01:50 -07:00
charlesh88
792633c0d1 Display Layout and frames major improvements
- Moved Toolbar out of Layout.vue and into DisplayLayout.vue;
- Styles for object view, Layout, Frame, etc.
- Major refactor of markup for frame;
- Added abs() mixin;
- Styles for is-editing done;
- Styles for
- TODO: styles for selectable, moveable, etc.
2018-09-18 14:46:15 -07:00
Pegah Sarram
dfd33251f0 Use default position and dimensions if the layout panel is missing those values. Set s-status-editing on the main div to get the drag handles appear for now. 2018-09-17 23:12:27 -07:00
Pegah Sarram
734d368c0b Put the div back with height set to 100% in ObjectView.
Add markup for  drag handles.
2018-09-17 21:59:42 -07:00
Pegah Sarram
794098740e Position the panels by setting the style 2018-09-17 21:30:25 -07:00
Pegah Sarram
9c178a870d Fix conflict 2018-09-17 21:22:30 -07:00
Pegah Sarram
8b3a058b03 Saving work 2018-09-17 21:20:11 -07:00
75 changed files with 4576 additions and 2542 deletions

4
app.js
View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,7 +41,7 @@ define([], function () {
this.timeFormat = 'local-format';
this.durationFormat = 'duration';
this.isUTCBased = false;
this.isUTCBased = true;
}
return LocalTimeSystem;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -71,7 +71,7 @@
<glyph unicode="&#xe1029;" 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="&#xe1030;" 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="&#xe1031;" 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="&#xe1032;" glyph-name="icon-T" d="M0 960v-256h128v64h256v-704h-192v-128h640v128h-192v704h256v-64h128v256z" />
<glyph unicode="&#xe1032;" glyph-name="icon-T" d="M800-64h224l-384 1024h-256l-384-1024h224l84 224h408zM380 352l132 352 132-352z" />
<glyph unicode="&#xe1033;" 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="&#xe1034;" 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="&#xe1035;" 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="&#xe1046;" 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="&#xe1047;" 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="&#xe1048;" 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="&#xe1049;" 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="&#xe1100;" 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="&#xe1101;" 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="&#xe1102;" 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="&#xe1124;" 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="&#xe1125;" 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="&#xe1126;" 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="&#xe1127;" 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="&#xe1127;" 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="&#xe1128;" 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="&#xe1129;" 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="&#xe1130;" 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

View File

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

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

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

View File

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

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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