Compare commits

...

43 Commits

Author SHA1 Message Date
ad89d647e2 Mobile styling for Time Conductor
- New modalFullScreen mixin;
- Datepicker now a fullscreen modal element in body.phone;
2018-10-03 23:56:06 -07:00
0cd81198ca Merge branch 'topic-core-css' into vue-conductor-mobile 2018-10-03 23:41:20 -07:00
a070aac579 Mobile styling for Time Conductor
- WIP!
2018-10-03 23:41:04 -07:00
80eb3a9668 Remove _constants-mobile.scss
- Moved used var to _constants;
- Normalize mobile detection approach in Layout.vue;
2018-10-03 22:33:59 -07:00
f503eae959 Removed injection of domainObject 2018-10-03 14:55:20 -07:00
c86ed3be78 Removed injection of domainObject 2018-10-03 14:51:10 -07:00
ae3d566854 Improved click area and draggable styling for tree-item 2018-10-03 14:43:40 -07:00
68da31f092 Add copyright to .scss files; code cleanup 2018-10-03 14:40:31 -07:00
7355767b2d Merge latest topic-core-refactor into topic-core-css
- Resolved conflicts;
2018-10-02 17:39:31 -07:00
56b9708ab7 Review and integrate Time Conductor Vue style conversion (#2180)
Styling updates for Vue version of Time Conductor
2018-10-02 17:31:45 -07:00
70d9ad6f2c Accessibility mods, convert elements to <button>
- Better reset styles for htmlInputReset mixin to allow use of <button>
without browser default styling;
- Create button;
- BrowseBar action buttons;
- c-click-icons;
- Removed Preview button from BrowseBar.vue;
2018-10-02 17:25:32 -07:00
1a3500ed1f Alias and type-icon fixes
- More robust approach to alias indicators;
- Added alias indication to tree-item.vue;
- TODO: wire up alias indication tree-item.vue;
2018-10-02 16:29:36 -07:00
40ad56d3cb Merge branch 'topic-core-refactor' into topic-core-css 2018-10-02 14:46:19 -07:00
e6e5b6a64a remove angular list/folder view 2018-10-01 11:42:12 -07:00
d298e614cf Code cleanup 2018-09-28 16:48:48 -07:00
e32a85b099 Resolve bizarre merge conflict when applying stash 2018-09-28 16:46:25 -07:00
943e61cf8f Various from vue-conductor-style
- Mods to input styling;
- Input[] styles moved to _controls;
- New/revised constants vals;
2018-09-28 16:36:04 -07:00
15f6b75334 Update markup to use latest button classnames
- c-menu-button > c-button--menu;
- c-icon-button > c-click-icon;
2018-09-28 16:33:09 -07:00
1ce79a76dd Manually update constants-snow from vue-toolbar branch 2018-09-28 16:33:09 -07:00
27783d8c27 New branch from topic-core-refactor to use as central point for common
CSS work

- Manually migrated changes from vue-toolbar, expect conflicts there and
 in vue-layout;
2018-09-28 16:33:09 -07:00
58da916d18 remove arrow function from data 2018-09-28 10:10:38 -07:00
a0327b56aa remove unused/empty update lifecycle method from layout.vue 2018-09-28 10:05:26 -07:00
57d60128a2 fix not-showing of conductor caused by merge with topic-core-refactor 2018-09-27 14:59:26 -07:00
987740c649 Reimplementation of time conductor in Vue.js (#2173)
* Refactoring conductor to use Vue

* Conditionally render Time Conductor

* Created ConductorOptions SFC

* Copyright notice examples

* Added time system selector component

* Use capture for event bubbling with popups

* Added Conductor Axis. Simplified Axis formatting and removed scale formatting from formatters. Added date picker.

* Sync axis on zoom

* Fixed sync between conductor and axis

* Changed 'InspectorComponent' to 'ConductorComponent' in Layout. Fixed race condition with panning and RequestAnimationFrame

* Renamed properties in conductor to clarify their role. Fixed some bugs

* Removed old conductor

* Fix layout issue with legacy Conductor markup

* Added missing copyright notice
2018-09-26 15:41:04 -07:00
944505a5f1 Folder views refactor (#2171)
Folder views rewritten in Vue.js
2018-09-26 23:34:44 +01:00
c5187d8509 Sort views by priority (#2174) 2018-09-26 12:32:48 -07:00
c7b73bdc3f Table fixes
- Fixed sorting and filtering on non-string columns
- Fixed warnings about missing required prop and invalid default value in table rows
- Fixed error occuring when formatting non-existent column
2018-09-25 11:40:50 -07:00
7483e886f1 Fix layout issues with overflow, shrinking, etc.
- Tree, main area, header, browse-bar
2018-09-18 15:01:09 -07:00
ef92da4d9d Custom search input component integrated (#2170)
- Integrated into main layout already; added to table.vue and
Notebook;
2018-09-17 16:06:46 -07:00
80b02672a6 Fix new ObjectView
- Removed empty <div> in template;
- Added display: contents to container div;
2018-09-17 11:45:56 -07:00
fbf1c68c7a Notebook vue styling (#2169)
* Notebook styling WIP

- New notebook scss file;
- notebook.html converted;

* Notebook styling WIP

- Entries and embeds
- New discreteItem theme constants;

* Notebook styling shippable

- Notebook entries and embeds finished;
- TODO: Fix styling for c-input-inline when SCSS is merged and remove
s-input-inline class in entry.html;

* Notebook styling shippable

- Remove legacy Notebook styles;
- Painterro overrides;
- Code cleanup

* Notebook mobile styling; entry layout still WIP

- Entries now layout better in narrow containers but still WIP,
particularly for delete icon;
- Mobile styling;

* Notebook entry markup for better narrow Notebook layout
2018-09-17 11:02:38 -07:00
5eac6e646b ObjectView supports reuse and props
Update ObjectView to better support reuse in layout and preview.

Register as component, and then mount like so:
<object-view :object="domainObject"></object-view>

It will show the default view for that object.  If you want to
specify a specific view type, you can pass an optional "view" prop
which will specify the view key to load.
2018-09-14 10:50:19 -07:00
82e5bf2325 remove unnecessary container 2018-09-13 18:44:51 -07:00
40b7117987 Object views (#2165)
* Use new style view providers for all object views.

* support legacy views with deprecation warning

* tidy deprecation warnings
2018-09-13 18:37:20 -07:00
07ca60e13a Dynamically generate create menu items (#2163)
* dynamically generate create menu items, populate before mount

* make reviewer requested changes:
1.Use type.get to get type definition
2.Fix type adapter for creatable properties
3.populate menu items in data function, and inject openmct

* use simpler data name (item) and remove prefix string from key
2018-09-13 15:15:01 -07:00
08cd6b1d99 Vue Browse Bar (#2164)
* Object browse bar IN PROGRESS

* Object browse bar VERY WIP

* Object browse bar WIP

- view-control renamed to disclosure-triangle;
- Good progress on object browse bar elements;

* Object browse bar WIP

- Layout of start-side elements now working with ellipsis;
- TODO: cleanups and consolidation;

* Object browse bar shippable

- Better layout approach;
- Refinements to button classes;

* Sanding and shimming on misc styles

- Tree icon shrinkage fixed;
- c-icon-button much better relative sizing;
- Removed c-button-set wrapper from Layout.vue;
- Added uppercasing of Create button for Snow theme;

* working object name, css class

* working dynamic name, css classes and view switcher
2018-09-13 15:14:28 -07:00
78ae7b334c Copyright notice examples 2018-09-13 14:19:55 -07:00
3a28caac06 Notebook Vue Version (#2160)
* working entries... wip

* componentize entries

* embed ability with drag into entry

* snapshot action partial

* working search

* abstract EntryController, working search, drag and drop

* keep deleteEntry local to entryController

* working snapshotOverlay

* fix snapshot object

* abstract Embed Controller, working context menu, working remove embed

* working preview action in embed context menu

* add overlay header with timestamp

* working annotate and new entry contextual

* Remove old notebook

* use same key, remove extra style files

* working embeds

* fixed markup for snapshot overlay
2018-09-12 15:27:19 -07:00
c883bbe6c2 Vue tables followup work. (#2162)
- Allow 'editable' property on view providers to optionally be a function
 - CSVExporter now only exports visible columns. Updated CSVExporter to ES6 exports / imports
 - Shortcut sortedIndex in insert if value is outside of first or last value in collection
 - Added table view icon
2018-09-11 11:03:32 -07:00
eaa971cb56 Vue main section overhaul (#2161)
* Separate browse object from conductor

* Main layout convert to flex; padding and overflow at leaf node

* Inspector converted to BEM - WIP

 - Properties pool upper level styles converted;
 - Grid mixins moved out of Inspector and into _mixins;

* Refinements to misc elements

- user-select: none on tree;
- :before and :after reset globally to use box-sizing: border-box;
- li reset globally;

* Styling for buttons and menus; add Create button

 - WIP

* Context and Create menus

 - WIP

* Add glyph backgrounds as data URIs

- For Create menu, items grid;
- Objects only;

* Create menu refinements

- Min/max height handling;
- Code cleanup;

* Main layout head styling; various sanding

- head layout refined;
- c-icon-button, c-button-set added;
- background glyph mixin refined;
- Antialiasing refined to increase icon sharpness;

* Fix SVG data URLs: encode # chars as %23
2018-09-11 10:10:59 -07:00
0301d88033 [Table] style refactor (#2157)
* [Table] Use Vue SFCs

Use Vue SFCs.  Use inject/provide to pass services to components
instead of wrapping components in closures.

* Convert CSS to BEM  - WIP!

- All in progress;
- Headers table divorced from old;
- Sizing working properly at this point;

* Reset legacy file, undo unintended change commit

* Convert CSS to BEM  - WIP!

- All in progress;
- Sizing table divorced from legacy;

* Convert CSS to BEM  - WIP!

- All in progress;
- Table body divorced from legacy;

* Convert CSS to BEM  - WIP!

- Near done, converted tabular-holder from legacy;
- Unit testing in main view and in Layout frames;
- Modded legacy CSS to properly hide control-bar with new naming
when in Layout frame;

* Convert CSS to BEM - done

- Cleanup and organization;

* Convert CSS to BEM

- Further code cleanup;

* Convert CSS to BEM

- Further code cleanup;
- Remove legacy table style imports;
2018-09-05 15:05:40 -07:00
e2e0cf17db Splitter refinement
- Arrow icons;
- Code cleanup;
2018-09-05 13:29:14 -07:00
01a39f4fb7 [Inspector] More vue
commit 9b735b4f70bf4d21f64a1e418a5f9d7342567104
Author: Pete Richards <peter.l.richards@nasa.gov>
Date:   Fri Aug 31 16:31:48 2018 -0700

    Slight HTML tweak

commit 3e58e140f9ea3640e5551d5f43abf837610c6fb4
Author: Pete Richards <peter.l.richards@nasa.gov>
Date:   Fri Aug 31 16:26:36 2018 -0700

    [Inspector] Wire up elements pool

    Elements pool wired up to show angular elements pool.

commit d9c60f31bd6d32a5d2e2227d5476edd81e2e4360
Author: Pete Richards <peter.l.richards@nasa.gov>
Date:   Fri Aug 31 13:56:04 2018 -0700

    [Inspector] vue inspector

    Create a vue inspctor which responds to selection events and shows
    object properties and inspector views.
2018-08-31 16:34:03 -07:00
145 changed files with 7004 additions and 6368 deletions

2
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

View File

@ -18,10 +18,4 @@
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='items-holder grid abs'>
<mct-representation key="'grid-item'"
ng-repeat="childObject in composition"
mct-object="childObject">
</mct-representation>
</div>
-->

View File

@ -18,27 +18,4 @@
* 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 () {
function MCTGesture(gestureService) {
return {
restrict : 'A',
scope: {
domainObject: '=mctObject'
},
link : function ($scope, $element, attrs) {
var activeGestures = gestureService.attachGestures(
$element,
$scope.domainObject,
$scope.$eval(attrs.mctGesture)
);
$scope.$on('$destroy', function () {
activeGestures.destroy();
delete this.activeGestures;
});
}
};
}
return MCTGesture;
});
*****************************************************************************/

View File

@ -75,6 +75,7 @@
}));
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

@ -35,12 +35,10 @@ define([
"./src/windowing/WindowTitler",
"./res/templates/browse.html",
"./res/templates/browse-object.html",
"./res/templates/items/grid-item.html",
"./res/templates/browse/object-header.html",
"./res/templates/browse/object-header-frame.html",
"./res/templates/menu-arrow.html",
"./res/templates/back-arrow.html",
"./res/templates/items/items.html",
"./res/templates/browse/object-properties.html",
"./res/templates/browse/inspector-region.html",
'legacyRegistry'
@ -59,12 +57,10 @@ define([
WindowTitler,
browseTemplate,
browseObjectTemplate,
gridItemTemplate,
objectHeaderTemplate,
objectHeaderFrameTemplate,
menuArrowTemplate,
backArrowTemplate,
itemsTemplate,
objectPropertiesTemplate,
inspectorRegionTemplate,
legacyRegistry
@ -156,19 +152,6 @@ define([
"view"
]
},
{
"key": "grid-item",
"template": gridItemTemplate,
"uses": [
"type",
"action",
"location"
],
"gestures": [
"info",
"menu"
]
},
{
"key": "object-header",
"template": objectHeaderTemplate,
@ -251,23 +234,6 @@ define([
"priority": "default"
}
],
"views": [
{
"key": "items",
"name": "Grid",
"cssClass": "icon-thumbs-strip",
"description": "Grid of available items",
"template": itemsTemplate,
"uses": [
"composition"
],
"gestures": [
"drop"
],
"type": "folder",
"editable": false
}
],
"runs": [
{
"implementation": WindowTitler,

View File

@ -66,5 +66,4 @@
</mct-representation>
</div>
</div>
<mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include>
</div>

View File

@ -1,45 +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.
-->
<!-- For selected, add class 'selected' to outer div -->
<div class='item grid-item' ng-click='action.perform("navigate")'>
<div class='contents abs'>
<div class='top-bar bar abs'>
<span class='icon-people' title='Shared'></span>
<mct-representation class="desktop-hide" key="'info-button'" mct-object="domainObject"></mct-representation>
</div>
<div class='item-main abs lg'>
<span class="t-item-icon" ng-class="{ 'l-icon-link':location.isLink() }">
<span class="t-item-icon-glyph ng-binding {{type.getCssClass()}}"></span>
</span>
<div class='abs item-open icon-pointer-right'></div>
</div>
<div class='bottom-bar bar abs'>
<div class='title'>{{model.name}}</div>
<div class='details'>
<span>{{type.getName()}}</span>
<span ng-show="model.composition !== undefined">
- {{model.composition.length}} Item<span ng-show="model.composition.length > 1">s</span>
</span>
</div>
</div>
</div>
</div>

View File

@ -319,6 +319,12 @@ define([
]
}
],
"templates": [
{
key: "elementsPool",
template: elementsTemplate
}
],
"components": [
{
"type": "decorator",

View File

@ -55,7 +55,7 @@ define(
// A view is editable unless explicitly flagged as not
(views || []).forEach(function (view) {
if (view.editable === true ||
if (isEditable(view) ||
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
(view.key === 'table' && type.getKey() === 'table') ||
(view.key === 'rt-table' && type.getKey() === 'rttable')
@ -64,6 +64,14 @@ define(
}
});
function isEditable(view) {
if (typeof view.editable === Function) {
return view.editable(domainObject.useCapability('adapter'));
} else {
return view.editable === true;
}
}
return count;
};

View File

@ -56,7 +56,7 @@ define([
};
DurationFormat.prototype.validate = function (text) {
return moment.utc(text, DATE_FORMATS).isValid();
return moment.utc(text, DATE_FORMATS, true).isValid();
};
return DurationFormat;

View File

@ -29,6 +29,7 @@ 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"
@ -52,70 +53,14 @@ 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 (arguments.length > 1) {
return getScaledFormat(value);
} else if (value !== undefined) {
if (value !== undefined) {
return moment.utc(value).format(DATE_FORMAT) + "Z";
} else {
return value;
@ -130,7 +75,7 @@ define([
};
UTCTimeFormat.prototype.validate = function (text) {
return moment.utc(text, DATE_FORMATS).isValid();
return moment.utc(text, DATE_FORMATS, true).isValid();
};
return UTCTimeFormat;

View File

@ -1,62 +0,0 @@
/*****************************************************************************
* 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

@ -1,43 +0,0 @@
/*****************************************************************************
* 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

@ -1,95 +0,0 @@
/*****************************************************************************
* 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

@ -1,148 +0,0 @@
/*****************************************************************************
* 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

@ -1,46 +0,0 @@
<!--
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

@ -1,33 +0,0 @@
<!--
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

@ -1,117 +0,0 @@
<!-- 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

@ -1,12 +0,0 @@
<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

@ -1,236 +0,0 @@
/*****************************************************************************
* 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

@ -1,169 +0,0 @@
/*****************************************************************************
* 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

@ -1,56 +0,0 @@
/*****************************************************************************
* 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

@ -1,123 +0,0 @@
/*****************************************************************************
* 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

@ -1,153 +0,0 @@
/*****************************************************************************
* 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

@ -1,63 +0,0 @@
/*****************************************************************************
* 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

@ -1,49 +0,0 @@
/*****************************************************************************
* 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

@ -1,554 +0,0 @@
/*****************************************************************************
* 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

@ -1,513 +0,0 @@
/*****************************************************************************
* 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

@ -1,69 +0,0 @@
/*****************************************************************************
* 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

@ -1,73 +0,0 @@
/*****************************************************************************
* 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

@ -1,97 +0,0 @@
/*****************************************************************************
* 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

@ -1,109 +0,0 @@
/*****************************************************************************
* 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

@ -1,64 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./src/controllers/ListViewController',
'./src/directives/MCTGesture',
'./res/templates/listview.html',
'legacyRegistry'
], function (
ListViewController,
MCTGesture,
listViewTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/listview", {
"name": "List View Plugin",
"description": "Allows folder contents to be shown in list format",
"extensions":
{
"views": [
{
"key": "list",
"type": "folder",
"name": "List",
"cssClass": "icon-list-view",
"template": listViewTemplate
}
],
"controllers": [
{
"key": "ListViewController",
"implementation": ListViewController,
"depends": ["$scope", "formatService"]
}
],
"directives": [
{
"key": "mctGesture",
"implementation" : MCTGesture,
"depends": ["gestureService"]
}
]
}
});
});

View File

@ -1,88 +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.
-->
<div ng-controller="ListViewController">
<table class="list-view">
<thead>
<tr>
<th class="sortable"
ng-click="reverseSort = (orderByField === 'title')? !reverseSort:false; orderByField='title';"
ng-class="{
sort: orderByField == 'title',
asc: !reverseSort,
desc: reverseSort
}">
Name
</th>
<th class="sortable"
ng-click="reverseSort = (orderByField === 'type')? !reverseSort:false; orderByField='type';"
ng-class="{
sort: orderByField == 'type',
asc: !reverseSort,
desc: reverseSort
}">
Type
</th>
<th class="sortable"
ng-click="reverseSort = (orderByField === 'persisted')? !reverseSort:true; orderByField='persisted';"
ng-class="{
sort: orderByField == 'persisted',
asc: !reverseSort,
desc: reverseSort
}">
Created Date
</th>
<th class="sortable"
ng-click="reverseSort = (orderByField === 'modified')? !reverseSort:true; orderByField='modified';"
ng-class="{
sort: orderByField == 'modified',
asc: !reverseSort,
desc: reverseSort
}">
Update Date
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="child in children|orderBy:orderByField:reverseSort"
mct-gesture="['menu','info']"
mct-object="child.asDomainObject"
ng-click="child.action.perform('navigate')">
<td>
<div class="l-flex-row">
<span class="flex-elem t-item-icon" ng-class="{ 'l-icon-link': child.location.isLink()}">
<span class="t-item-icon-glyph {{child.icon}}"></span>
</span>
<span class="t-title-label flex-elem grows">{{child.title}}</span>
</div>
</td>
<td>{{child.type}}</td>
<td>{{child.persisted}}</td>
<td>{{child.modified}}</td>
</tr>
</tbody>
</table>
</div>

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(function () {
function ListViewController($scope, formatService) {
this.$scope = $scope;
$scope.orderByField = 'title';
$scope.reverseSort = false;
this.updateView();
var unlisten = $scope.domainObject.getCapability('mutation')
.listen(this.updateView.bind(this));
this.utc = formatService.getFormat('utc');
//Trigger digestive cycle with $apply to update list view
setTimeout(function () {
$scope.$apply();
});
$scope.$on('$destroy', function () {
unlisten();
});
}
ListViewController.prototype.updateView = function () {
this.$scope.domainObject.useCapability('composition')
.then(function (children) {
var formattedChildren = this.formatChildren(children);
this.$scope.children = formattedChildren;
this.$scope.data = {children: formattedChildren};
}.bind(this)
);
};
ListViewController.prototype.formatChildren = function (children) {
return children.map(function (child) {
return {
icon: child.getCapability('type').getCssClass(),
title: child.getModel().name,
type: child.getCapability('type').getName(),
persisted: this.utc.format(child.getModel().persisted),
modified: this.utc.format(child.getModel().modified),
asDomainObject: child,
location: child.getCapability('location'),
action: child.getCapability('action')
};
}, this);
};
return ListViewController;
});

View File

@ -1,157 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/controllers/ListViewController"],
function (ListViewController) {
describe("The Controller for the ListView", function () {
var scope,
unlistenFunc,
domainObject,
childObject,
childModel,
typeCapability,
mutationCapability,
formatService,
compositionPromise,
controller;
beforeEach(function () {
unlistenFunc = jasmine.createSpy("unlisten");
mutationCapability = jasmine.createSpyObj(
"mutationCapability",
["listen"]
);
mutationCapability.listen.and.returnValue(unlistenFunc);
formatService = jasmine.createSpyObj(
"formatService",
["getFormat"]
);
formatService.getFormat.and.returnValue(jasmine.createSpyObj(
'utc',
["format"]
));
formatService.getFormat().format.and.callFake(function (v) {
return "formatted " + v;
});
typeCapability = jasmine.createSpyObj(
"typeCapability",
["getCssClass", "getName"]
);
typeCapability.getCssClass.and.returnValue("icon-folder");
typeCapability.getName.and.returnValue("Folder");
childModel = jasmine.createSpyObj(
"childModel",
["persisted", "modified", "name"]
);
childModel.persisted = 1496867697303;
childModel.modified = 1496867697303;
childModel.name = "Battery Charge Status";
childObject = jasmine.createSpyObj(
"childObject",
["getModel", "getCapability"]
);
childObject.getModel.and.returnValue(
childModel
);
childObject.getCapability.and.callFake(function (arg) {
if (arg === 'location') {
return '';
} else if (arg === 'type') {
return typeCapability;
}
});
childObject.location = '';
domainObject = jasmine.createSpyObj(
"domainObject",
["getCapability", "useCapability"]
);
compositionPromise = Promise.resolve([childObject]);
domainObject.useCapability.and.returnValue(compositionPromise);
domainObject.getCapability.and.returnValue(
mutationCapability
);
scope = jasmine.createSpyObj(
"$scope",
["$on", "$apply"]
);
scope.domainObject = domainObject;
controller = new ListViewController(scope, formatService);
return compositionPromise;
});
it("uses the UTC time format", function () {
expect(formatService.getFormat).toHaveBeenCalledWith('utc');
});
it("updates the view", function () {
var child = scope.children[0];
var testChild = {
icon: "icon-folder",
title: "Battery Charge Status",
type: "Folder",
persisted: formatService.getFormat('utc')
.format(childModel.persisted),
modified: formatService.getFormat('utc')
.format(childModel.modified),
asDomainObject: childObject,
location: '',
action: childObject.getCapability('action')
};
expect(child).toEqual(testChild);
});
it("updates the scope when mutation occurs", function () {
var applyPromise = new Promise(function (resolve) {
scope.$apply.and.callFake(resolve);
});
domainObject.useCapability.and.returnValue(Promise.resolve([]));
expect(mutationCapability.listen).toHaveBeenCalledWith(jasmine.any(Function));
mutationCapability.listen.calls.mostRecent().args[0]();
return applyPromise.then(function () {
expect(scope.children.length).toEqual(0);
expect(scope.$apply).toHaveBeenCalled();
});
});
it("releases listeners on $destroy", function () {
expect(scope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
scope.$on.calls.mostRecent().args[1]();
expect(unlistenFunc).toHaveBeenCalled();
});
});
}
);

View File

@ -1,86 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
["../../src/directives/MCTGesture"],
function (MCTGesture) {
describe("The Gesture Listener for the ListView items", function () {
var mctGesture,
gestureService,
scope,
element,
attrs,
attachedGesture;
beforeEach(function () {
attachedGesture = jasmine.createSpyObj(
"attachedGesture",
['destroy']
);
gestureService = jasmine.createSpyObj(
"gestureService",
["attachGestures"]
);
gestureService.attachGestures.and.returnValue(
attachedGesture
);
mctGesture = MCTGesture(gestureService);
});
it("creates a directive Object", function () {
expect(mctGesture).toBeDefined();
});
it("has link function that attaches gesture to gestureService",
function () {
attrs = {
mctGesture: "menu,info"
};
element = jasmine.createSpy("element");
scope = jasmine.createSpyObj(
"$scope",
["$on", "$eval"]
);
scope.domainObject = "fake domainObject";
mctGesture.link(scope, element, attrs);
expect(gestureService.attachGestures).toHaveBeenCalled();
}
);
it("release gesture service on $destroy", function () {
attrs = {
mctGesture: "menu,info"
};
element = jasmine.createSpy("element");
scope = jasmine.createSpyObj(
"$scope",
["$on", "$eval"]
);
scope.domainObject = "fake domainObject";
mctGesture.link(scope, element, attrs);
expect(scope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
scope.$on.calls.mostRecent().args[1]();
expect(attachedGesture.destroy).toHaveBeenCalled();
});
});
}
);

View File

@ -1,294 +0,0 @@
define([
"legacyRegistry",
"./src/controllers/NotebookController",
"./src/controllers/NewEntryController",
"./src/controllers/SelectSnapshotController",
"./src/controllers/LayoutNotebookController",
"./src/directives/MCTSnapshot",
"./src/directives/EntryDnd",
"./src/actions/ViewSnapshot",
"./src/actions/AnnotateSnapshot",
"./src/actions/RemoveEmbed",
"./src/actions/RemoveSnapshot",
"./src/actions/NewEntryContextual",
"./src/capabilities/NotebookCapability",
"./src/policies/CompositionPolicy",
"./res/templates/notebook.html",
"./res/templates/entry.html",
"./res/templates/annotation.html",
"./res/templates/notifications.html",
"../layout/res/templates/frame.html",
"./res/templates/controls/embedControl.html",
"./res/templates/controls/snapSelect.html"
], function (
legacyRegistry,
NotebookController,
NewEntryController,
SelectSnapshotController,
LayoutNotebookController,
MCTSnapshot,
MCTEntryDnd,
ViewSnapshotAction,
AnnotateSnapshotAction,
RemoveEmbedAction,
RemoveSnapshotAction,
newEntryAction,
NotebookCapability,
CompositionPolicy,
notebookTemplate,
entryTemplate,
annotationTemplate,
notificationsTemplate,
frameTemplate,
embedControlTemplate,
snapSelectTemplate
) {
legacyRegistry.register("platform/features/notebook", {
"name": "Notebook Plugin",
"description": "Create and save timestamped notes with embedded object snapshots.",
"extensions":
{
"types": [
{
"key": "notebook",
"name": "Notebook",
"cssClass": "icon-notebook",
"description": "Create and save timestamped notes with embedded object snapshots.",
"features": ["creation"],
"model": {
"entries": [],
"composition": [],
"entryTypes": [],
"defaultSort": "-createdOn"
},
"properties": [
{
"key": "defaultSort",
"name": "Default Sort",
"control": "select",
"options": [
{
"name": "Newest First",
"value": "-createdOn"
},
{
"name": "Oldest First",
"value": "createdOn"
}
],
"cssClass": "l-inline"
}
]
}
],
"views": [
{
"key": "notebook.view",
"type": "notebook",
"cssClass": "icon-notebook",
"name": "notebook",
"template": notebookTemplate,
"editable": false,
"uses": [
"composition",
"action"
],
"gestures": [
"drop"
]
}
],
"controllers": [
{
"key": "NotebookController",
"implementation": NotebookController,
"depends": [
"$scope",
"dialogService",
"popupService",
"agentService",
"objectService",
"navigationService",
"now",
"actionService",
"$timeout",
"$element",
"$sce"
]
},
{
"key": "NewEntryController",
"implementation": NewEntryController,
"depends": ["$scope",
"$rootScope"
]
},
{
"key": "selectSnapshotController",
"implementation": SelectSnapshotController,
"depends": ["$scope",
"$rootScope"
]
},
{
"key": "LayoutNotebookController",
"implementation": LayoutNotebookController,
"depends": ["$scope"]
}
],
"representations": [
{
"key": "draggedEntry",
"template": entryTemplate
},
{
"key": "frameLayoutNotebook",
"template": frameTemplate
}
],
"templates": [
{
"key": "annotate-snapshot",
"template": annotationTemplate
},
{
"key": "notificationTemplate",
"template": notificationsTemplate
}
],
"directives": [
{
"key": "mctSnapshot",
"implementation": MCTSnapshot,
"depends": [
"$rootScope",
"$document",
"exportImageService",
"dialogService",
"notificationService"
]
},
{
"key": "mctEntryDnd",
"implementation": MCTEntryDnd,
"depends": [
"$rootScope",
"$compile",
"dndService",
"typeService",
"notificationService"
]
}
],
"actions": [
{
"key": "view-snapshot",
"implementation": ViewSnapshotAction,
"name": "View Snapshot",
"description": "View the large image in a modal",
"category": "embed",
"depends": [
"$compile"
]
},
{
"key": "annotate-snapshot",
"implementation": AnnotateSnapshotAction,
"name": "Annotate Snapshot",
"cssClass": "icon-pencil labeled",
"description": "Annotate embed's snapshot",
"category": "embed",
"depends": [
"dialogService",
"dndService",
"$rootScope"
]
},
{
"key": "remove-embed",
"implementation": RemoveEmbedAction,
"name": "Remove...",
"cssClass": "icon-trash labeled",
"description": "Remove this embed",
"category": [
"embed",
"embed-no-snap"
],
"depends": [
"dialogService"
]
},
{
"key": "remove-snapshot",
"implementation": RemoveSnapshotAction,
"name": "Remove Snapshot",
"cssClass": "icon-trash labeled",
"description": "Remove Snapshot of the embed",
"category": "embed",
"depends": [
"dialogService"
]
},
{
"key": "notebook-new-entry",
"implementation": newEntryAction,
"name": "New Notebook Entry",
"cssClass": "icon-notebook labeled",
"description": "Add a new Notebook entry",
"category": [
"view-control"
],
"depends": [
"$compile",
"$rootScope",
"dialogService",
"notificationService",
"linkService"
],
"priority": "preferred"
}
],
"licenses": [
{
"name": "painterro",
"version": "0.2.65",
"author": "Ivan Borshchov",
"description": "Painterro is JavaScript paint widget which allows editing images directly in a browser.",
"website": "https://github.com/ivictbor/painterro",
"copyright": "Copyright 2017 Ivan Borshchov",
"license": "MIT",
"link": "https://github.com/ivictbor/painterro/blob/master/LICENSE"
}
],
"capabilities": [
{
"key": "notebook",
"name": "Notebook Capability",
"description": "Provides a capability for looking for a notebook domain object",
"implementation": NotebookCapability,
"depends": [
"typeService"
]
}
],
"policies": [
{
"category": "composition",
"implementation": CompositionPolicy,
"message": "Objects of this type cannot contain objects of that type."
}
],
"controls": [
{
"key": "embed-control",
"template": embedControlTemplate
},
{
"key": "snapshot-select",
"template": snapSelectTemplate
}
]
}
});
});

View File

@ -1,118 +0,0 @@
<div ng-controller="NotebookController as controller" class="mct-notebook w-notebook l-flex-col">
<div class="l-notebook-head holder l-flex-row flex-elem">
<div class="c-search flex-elem holder grows">
<input class="c-search__search-input"
type="text" tabindex="10000"
ng-model="entrySearch"
ng-keyup="controller.search()"/>
<a class="c-search__clear-input clear-icon icon-x-in-circle"
ng-class="{show: !(entrySearch === '' || entrySearch === undefined)}"
ng-click="entrySearch = ''; controller.search()"></a>
</div>
<div class="notebook-view-controls l-flex-row flex-elem holder">
<div class="select notebook-view-controls__filter-time">
<select ng-model="showTime">
<option value="0" selected="selected">Show all</option>
<option value="1">Last hour</option>
<option value="8">Last 8 hours</option>
<option value="24">Last 24 hours</option>
</select>
</div>
<div class="select notebook-view-controls__sort">
<select ng-model="sortEntries">
<option value="-createdOn" selected="selected">Newest first</option>
<option value="createdOn">Oldest first</option>
</select>
</div>
</div>
</div>
<!-- drag area -->
<div class="holder flex-elem l-flex-row icon-plus labeled l-notebook-drag-area" ng-click="newEntry($event)"
id="newEntry" mct-entry-dnd>
<span class="label">To start a new entry, click here or drag and drop any object</span>
</div>
<!-- entries -->
<div class="holder flex-elem grows w-notebook-entries t-entries-list" ng-mouseover="handleActive()">
<ul>
<li class="l-flex-row has-local-controls l-notebook-entry s-notebook-entry"
id="{{'entry_'+ entry.id}}"
ng-if="hoursFilter(showTime,entry.createdOn)"
ng-repeat="entry in model.entries | filter:entrySearch | orderBy: sortEntries track by $index"
ng-init="$last && finished(model.entries)"
mct-entry-dnd>
<div class="holder flex-elem l-flex-row grows w-notebook-entry-time-and-content">
<div class="holder flex-elem s-notebook-entry-time">
<span>{{entry.createdOn | date:'yyyy-MM-dd'}}</span>
<span>{{entry.createdOn | date:'HH:mm:ss'}}</span>
</div>
<div class="holder flex-elem l-flex-col grows l-notebook-entry-content">
<div contenteditable="true"
ng-blur="textBlur($event, entry.id)"
ng-focus="textFocus($event, entry.id)"
ng-model="entry.text"
placeholder="Enter text here"
class="flex-elem s-input-inline t-notebook-entry-input s-notebook-entry-text"
ng-bind="entry.text">
</div>
<!-- embeds -->
<div class="flex-elem entry-embeds l-flex-row">
<div class="l-flex-row l-entry-embed {{embed.cssClass}}"
ng-repeat="embed in entry.embeds track by $index"
ng-class="{ 'has-snapshot' : embed.snapshot }"
id="{{embed.id}}">
<div class="snap-thumb"
ng-if="embed.snapshot"
ng-click="viewSnapshot($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)">
<img ng-src="{{embed.snapshot.src}}" src="//:0" alt="{{embed.id}}">
</div>
<div class="embed-info l-flex-col">
<div class="embed-title object-header">
<a ng-click='navigate($event,embed.type)'>{{embed.name}}</a>
<a class='context-available' ng-click='openMenu($event,embed.type)'></a>
</div>
<div class="hide-menu" ng-show="false">
<div class="menu-element context-menu-wrapper mobile-disable-select">
<div class="menu context-menu">
<ul>
<li ng-repeat="menu in menuEmbed"
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)"
title="{{menu.getMetadata().description}}"
class="{{menu.getMetadata().cssClass}}"
ng-if="embed.snapshot">
{{menu.getMetadata().name}}
</li>
<li ng-repeat="menu in menuEmbedNoSnap"
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this)"
title="{{menu.getMetadata().description}}"
class="{{menu.getMetadata().cssClass}}"
ng-if="!embed.snapshot">
{{menu.getMetadata().name}}
</li>
<li ng-repeat="menu in embedActions"
ng-click="menu.perform()"
title="{{menu.name}}"
class="{{menu.cssClass}}">
{{menu.name}}
</li>
</ul>
</div>
</div>
</div>
<div class="embed-date"
ng-if="embed.snapshot">{{embed.id| date:'yyyy-MM-dd HH:mm:ss'}}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- delete entry -->
<div class="holder flex-elem local-control local-controls-hidden notebook-entry-delete">
<a class="s-icon-button icon-trash" id={{entry.id}} title="Delete Entry" ng-click="deleteEntry($event)"></a>
</div>
</li>
</ul>
</div>
</div>

View File

@ -1,8 +0,0 @@
<span class="status block">
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<span class="status-indicator icon-bell"></span>
<span class="label">
Notifications
</span>
<span class="count"></span>
</span>

View File

@ -1,34 +0,0 @@
<div class="t-snapshot abs l-view-header">
<div class="abs object-browse-bar l-flex-row">
<div class="left flex-elem l-flex-row grows">
<div class="object-header flex-elem l-flex-row grows">
<div class="type-icon flex-elem embed-icon holder" ng-class="cssClass"></div>
<div class="title-label flex-elem holder flex-can-shrink">{{entryName}}</div>
<a class="context-available flex-elem holder" ng-click="openMenu($event,embedType)"></a>
<div class="hide-menu" ng-show="false">
<div class="menu-element menu-view context-menu-wrapper mobile-disable-select">
<div class="menu context-menu">
<ul>
<li ng-repeat="menu in embedActions"
ng-click="menuPerform(menu)"
title="{{menu.name}}"
class="{{menu.cssClass}}">
{{menu.name}}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
SNAPSHOT {{snapDate | date:'yyyy-MM-dd HH:mm:ss'}}
</div>
<a class="s-button icon-pencil" title="Annotate">
<span class="title-label">Annotate</span>
</a>
</div>
</div>
</div>

View File

@ -1,72 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
function RemoveEmbed(dialogService,context) {
context = context || {};
this.domainObject = context.selectedObject || context.domainObject;
this.dialogService = dialogService;
}
RemoveEmbed.prototype.perform = function ($event,snapshot,embedId,entryId) {
var domainObject = this.domainObject;
var errorDialog = this.dialogService.showBlockingMessage({
severity: "error",
title: "This action will permanently delete this Embed. Do you want to continue?",
minimized: true, // want the notification to be minimized initially (don't show banner)
options: [{
label: "OK",
callback: function () {
errorDialog.dismiss();
remove();
}
},{
label: "Cancel",
callback: function () {
errorDialog.dismiss();
}
}]
});
function remove() {
domainObject.useCapability('mutation', function (model) {
var elementPos = model.entries.map(function (x) {
return x.createdOn;
}).indexOf(entryId);
var entryEmbeds = model.entries[elementPos].embeds;
var embedPos = entryEmbeds.map(function (x) {
return x.id;
}).indexOf(embedId);
model.entries[elementPos].embeds.splice(embedPos, 1);
});
}
};
return RemoveEmbed;
}
);

View File

@ -1,74 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
function RemoveSnapshot(dialogService, context) {
context = context || {};
this.domainObject = context.selectedObject || context.domainObject;
this.dialogService = dialogService;
}
RemoveSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId) {
var domainObject = this.domainObject;
var errorDialog = this.dialogService.showBlockingMessage({
severity: "error",
title: "This action will permanently delete this Snapshot. Do you want to continue?",
minimized: true, // want the notification to be minimized initially (don't show banner)
options: [{
label: "OK",
callback: function () {
errorDialog.dismiss();
remove();
}
},{
label: "Cancel",
callback: function () {
errorDialog.dismiss();
}
}]
});
function remove() {
domainObject.useCapability('mutation', function (model) {
var elementPos = model.entries.map(function (x) {
return x.createdOn;
}).indexOf(entryId);
var entryEmbeds = model.entries[elementPos].embeds;
var embedPos = entryEmbeds.map(function (x) {
return x.id;
}).indexOf(embedId);
model.entries[elementPos].embeds[embedPos].snapshot = "";
});
}
};
return RemoveSnapshot;
}
);

View File

@ -1,132 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/**
* Module defining ViewSnapshot
*/
var OVERLAY_TEMPLATE = '' +
' <div class="abs blocker"></div>' +
' <div class="abs outer-holder">' +
' <a class="close icon-x-in-circle"></a>' +
' <div class="abs inner-holder l-flex-col">' +
' <div class="t-contents flex-elem holder grows"></div>' +
' <div class="bottom-bar flex-elem holder">' +
' <a class="t-done s-button major">Done</a>' +
' </div>' +
' </div>' +
' </div>';
define([
'zepto',
"../../res/templates/snapshotHeader.html"
],
function ($, headerTemplate) {
var toggleOverlay,
overlay,
closeButton,
doneButton,
blocker,
overlayContainer,
img,
annotateButton,
annotateImg;
function ViewSnapshot($compile) {
this.$compile = $compile;
}
function openOverlay(url, header) {
overlay = document.createElement('div');
$(overlay).addClass('abs overlay l-large-view');
overlay.innerHTML = OVERLAY_TEMPLATE;
overlayContainer = overlay.querySelector('.t-contents');
closeButton = overlay.querySelector('a.close');
closeButton.addEventListener('click', toggleOverlay);
doneButton = overlay.querySelector('a.t-done');
doneButton.addEventListener('click', toggleOverlay);
blocker = overlay.querySelector('.abs.blocker');
blocker.addEventListener('click', toggleOverlay);
annotateButton = header.querySelector('a.icon-pencil');
annotateButton.addEventListener('click', annotateImg);
document.body.appendChild(overlay);
img = document.createElement('div');
$(img).addClass('abs object-holder t-image-holder s-image-holder');
img.innerHTML = '<div class="image-main s-image-main" style="background-image: url(' + url + ');"></div>';
overlayContainer.appendChild(header);
overlayContainer.appendChild(img);
}
function closeOverlay() {
overlayContainer.removeChild(img);
document.body.removeChild(overlay);
closeButton.removeEventListener('click', toggleOverlay);
closeButton = undefined;
doneButton.removeEventListener('click', toggleOverlay);
doneButton = undefined;
blocker.removeEventListener('click', toggleOverlay);
blocker = undefined;
overlayContainer = undefined;
overlay = undefined;
img = undefined;
}
ViewSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId, $scope, embed) {
var isOpen = false;
// onclick for menu items in overlay header context menu
$scope.menuPerform = function (menu) {
menu.perform();
closeOverlay();
};
// Create the overlay element and add it to the document's body
$scope.cssClass = embed.cssClass;
$scope.embedType = embed.type;
$scope.entryName = embed.name;
$scope.snapDate = +embedId;
var element = this.$compile(headerTemplate)($scope);
var annotateAction = $scope.action.getActions({category: 'embed'})[1];
toggleOverlay = function () {
if (!isOpen) {
openOverlay(snapshot, element[0]);
isOpen = true;
} else {
closeOverlay();
isOpen = false;
}
};
annotateImg = function () {
closeOverlay();
annotateAction.perform($event, snapshot, embedId, entryId, $scope);
};
toggleOverlay();
};
return ViewSnapshot;
}
);

View File

@ -1,50 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
function () {
/**
* The notebook capability allows a domain object to know whether the
* notebook plugin is present or not.
*
* @constructor
*/
function NotebookCapability(typeService, domainObject) {
this.domainObject = domainObject;
this.typeService = typeService;
return this;
}
/**
* Returns true if there is a notebook domain Object.
*
* @returns {Boolean}
*/
NotebookCapability.prototype.isNotebook = function () {
return !!this.typeService.getType('notebook');
};
return NotebookCapability;
}
);

View File

@ -1,367 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/*-- main controller file, here is the core functionality of the notebook plugin --*/
define(
['zepto'],
function ($) {
function NotebookController(
$scope,
dialogService,
popupService,
agentService,
objectService,
navigationService,
now,
actionService,
$timeout,
$element,
$sce
) {
$scope.entriesEl = $(document.body).find('.t-entries-list');
$scope.sortEntries = $scope.domainObject.getModel().defaultSort;
$scope.showTime = "0";
$scope.editEntry = false;
$scope.entrySearch = '';
$scope.entryTypes = [];
$scope.embedActions = [];
$scope.currentEntryValue = '';
var SECONDS_IN_AN_HOUR = 60 * 60 * 1000;
this.scope = $scope;
$scope.hoursFilter = function (hours,entryTime) {
if (+hours) {
return entryTime > (Date.now() - SECONDS_IN_AN_HOUR * (+hours));
}else {
return true;
}
};
$scope.scrollToTop = function () {
var entriesContainer = $scope.entriesEl.parent();
entriesContainer[0].scrollTop = 0;
};
$scope.findEntryEl = function (entryId) {
var element = $($scope.entriesEl).find('#entry_' + entryId);
if (element[0]) {
return element.find("[contenteditable='true']");
} else {
var entries = $scope.entriesEl.children().children(),
lastOrFirst = $scope.sortEntries === "-createdOn" ? 0 : (entries.length - 1);
return $(entries[lastOrFirst]).find("[contenteditable='true']");
}
};
$scope.findEntryPositionById = function (id) {
var foundId = -1;
$scope.domainObject.model.entries.forEach(function (element, index) {
if (element.id === id) {
foundId = index;
return;
}
});
return foundId;
};
$scope.newEntry = function ($event) {
$scope.scrollToTop();
var entries = $scope.domainObject.model.entries,
lastEntry = entries[entries.length - 1],
id = Date.now();
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
var createdEntry = {'id': id, 'createdOn': id};
$scope.domainObject.useCapability('mutation', function (model) {
model.entries.push(createdEntry);
});
} else {
$scope.findEntryEl(lastEntry.id).focus();
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entries.length - 1].createdOn = id;
});
}
$scope.entrySearch = '';
};
$scope.deleteEntry = function ($event) {
var delId = +$event.currentTarget.id;
var errorDialog = dialogService.showBlockingMessage({
severity: "error",
title: "This action will permanently delete this Notebook entry. Do you want to continue?",
minimized: true, // want the notification to be minimized initially (don't show banner)
options: [{
label: "OK",
callback: function () {
errorDialog.dismiss();
var elementPos = $scope.findEntryPositionById(delId);
if (elementPos !== -1) {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries.splice(elementPos, 1);
});
} else {
window.console.log('delete error');
}
}
},{
label: "Cancel",
callback: function () {
errorDialog.dismiss();
}
}]
});
};
$scope.textFocus = function ($event, entryId) {
if ($event.srcElement) {
$scope.currentEntryValue = $event.srcElement.innerText;
} else {
$event.target.innerText = '';
}
};
//On text blur(when focus is removed)
$scope.textBlur = function ($event, entryId) {
// entryId is the unique numeric based on the original createdOn
if ($event.target) {
var elementPos = $scope.findEntryPositionById(+entryId);
// If the text of an entry has been changed, then update the text and the modifiedOn numeric
// Otherwise, don't do anything
if ($scope.currentEntryValue !== $event.target.innerText) {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[elementPos].text = $event.target.innerText;
model.entries[elementPos].modified = Date.now();
});
}
}
};
$scope.finished = function (model) {
var lastEntry = model[model.length - 1];
if (!lastEntry.text) {
$scope.findEntryEl(lastEntry.id).focus();
}
};
$scope.handleActive = function () {
var newEntry = $scope.entriesEl.find('.active');
if (newEntry) {
newEntry.removeClass('active');
}
};
$scope.clearSearch = function () {
$scope.entrySearch = '';
};
$scope.viewSnapshot = function ($event,snapshot,embedId,entryId,$innerScope,domainObject) {
var viewAction = $scope.action.getActions({category: 'embed'})[0];
viewAction.perform($event, snapshot, embedId, entryId, $innerScope, domainObject);
};
$scope.renderImage = function (img) {
return URL.createObjectURL(img);
};
$scope.getDomainObj = function (id) {
return objectService.getObjects([id]);
};
function refreshComp(change) {
if (change && change.length) {
change[0].getCapability('action').getActions({key: 'remove'})[0].perform();
}
}
$scope.actionToMenuOption = function (action) {
return {
key: action.getMetadata().key,
name: action.getMetadata().name,
cssClass: action.getMetadata().cssClass,
perform: action.perform
};
};
// Maintain all "conclude-editing" and "save" actions in the
// present context.
function updateActions() {
$scope.menuEmbed = $scope.action ?
$scope.action.getActions({category: 'embed'}) :
[];
$scope.menuEmbedNoSnap = $scope.action ?
$scope.action.getActions({category: 'embed-no-snap'}) :
[];
$scope.menuActions = $scope.action ?
$scope.action.getActions({key: 'window'}) :
[];
}
// Update set of actions whenever the action capability
// changes or becomes available.
$scope.$watch("action", updateActions);
$scope.navigate = function ($event,embedType) {
if ($event) {
$event.preventDefault();
}
$scope.getDomainObj(embedType).then(function (resp) {
navigationService.setNavigation(resp[embedType]);
});
};
$scope.saveSnap = function (url,embedPos,entryPos) {
var snapshot = false;
if (url) {
if (embedPos !== -1 && entryPos !== -1) {
var reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
snapshot = reader.result;
$scope.domainObject.useCapability('mutation', function (model) {
if (model.entries[entryPos]) {
model.entries[entryPos].embeds[embedPos].snapshot = {
'src': snapshot,
'type': url.type,
'size': url.size,
'modified': Date.now()
};
model.entries[entryPos].embeds[embedPos].id = Date.now();
}
});
};
}
}else {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entryPos].embeds[embedPos].snapshot = snapshot;
});
}
};
/*---popups menu embeds----*/
function getEmbedActions(embedType) {
if (!$scope.embedActions.length) {
$scope.getDomainObj(embedType).then(function (resp) {
$scope.embedActions = [];
$scope.embedActions.push($scope.actionToMenuOption(
$scope.action.getActions({key: 'mct-preview-action', selectedObject: resp[embedType]})[0]
));
$scope.embedActions.push($scope.actionToMenuOption(
$scope.action.getActions({key: 'window', selectedObject: resp[embedType]})[0]
));
$scope.embedActions.push({
key: 'navigate',
name: 'Go to Original',
cssClass: '',
perform: function () {
$scope.navigate('', embedType);
}
});
});
}
}
$scope.openMenu = function ($event,embedType) {
$event.preventDefault();
getEmbedActions(embedType);
var body = $(document).find('body'),
initiatingEvent = agentService.isMobile() ?
'touchstart' : 'mousedown',
dismissExistingMenu,
menu;
var container = $($event.currentTarget).parent().parent();
menu = container.find('.menu-element');
// Remove the context menu
function dismiss() {
container.find('.hide-menu').append(menu);
body.off("mousedown", dismiss);
dismissExistingMenu = undefined;
$scope.embedActions = [];
}
// Dismiss any menu which was already showing
if (dismissExistingMenu) {
dismissExistingMenu();
}
// ...and record the presence of this menu.
dismissExistingMenu = dismiss;
popupService.display(menu, [$event.pageX,$event.pageY], {
marginX: 0,
marginY: -50
});
// Stop propagation so that clicks or touches on the menu do not close the menu
menu.on(initiatingEvent, function (event) {
event.stopPropagation();
$timeout(dismiss, 300);
});
// Dismiss the menu when body is clicked/touched elsewhere
// ('mousedown' because 'click' breaks left-click context menus)
// ('touchstart' because 'touch' breaks context menus up)
body.on(initiatingEvent, dismiss);
};
$scope.$watchCollection("composition", refreshComp);
$scope.$watch('domainObject.getModel().defaultSort', function (newDefaultSort, oldDefaultSort) {
if (newDefaultSort !== oldDefaultSort) {
$scope.sortEntries = newDefaultSort;
}
});
$scope.$on('$destroy', function () {});
}
return NotebookController;
});

View File

@ -1,44 +0,0 @@
/******************************************************************************
* Open MCT, Copyright (c) 2014-2017, 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.
*****************************************************************************/
/**
* This bundle implements "containment" rules, which determine which objects
* can be contained within a notebook.
*/
define(
[],
function () {
function CompositionPolicy() {
}
CompositionPolicy.prototype.allow = function (parent, child) {
var parentDef = parent.getCapability('type').getName();
if (parentDef === 'Notebook' && child.getCapability('status').list().length) {
return false;
}
return true;
};
return CompositionPolicy;
}
);

View File

@ -31,13 +31,14 @@ define([
'./plugins/plugins',
'./adapter/indicators/legacy-indicators-plugin',
'./plugins/buildInfo/plugin',
'./adapter/vue-adapter/install',
'./ui/registries/ViewRegistry',
'./ui/registries/InspectorViewRegistry',
'./ui/registries/ToolbarRegistry',
'./ui/router/ApplicationRouter',
'./ui/router/Browse',
'../platform/framework/src/Main',
'./styles-new/core.scss',
'./styles-new/notebook.scss',
'./ui/components/layout/Layout.vue',
'vue'
], function (
@ -51,13 +52,14 @@ define([
plugins,
LegacyIndicatorsPlugin,
buildInfoPlugin,
installVueAdapter,
ViewRegistry,
InspectorViewRegistry,
ToolbarRegistry,
ApplicationRouter,
Browse,
Main,
coreStyles,
NotebookStyles,
Layout,
Vue
) {
@ -275,6 +277,7 @@ define([
}.bind(this)
});
// TODO: remove with legacy types.
this.types.listKeys().forEach(function (typeKey) {
var type = this.types.get(typeKey);
var legacyDefinition = type.toLegacyDefinition();
@ -282,26 +285,6 @@ define([
this.legacyExtension('types', legacyDefinition);
}.bind(this));
// TODO: move this to adapter bundle.
this.legacyExtension('runs', {
depends: ['types[]'],
implementation: (types) => {
this.types.importLegacyTypes(types);
}
});
this.objectViews.getAllProviders().forEach(function (p) {
this.legacyExtension('views', {
key: p.key,
provider: p,
name: p.name,
cssClass: p.cssClass,
description: p.description,
editable: p.editable,
template: '<mct-view mct-provider-key="' + p.key + '"/>'
});
}, this);
legacyRegistry.register('adapter', this.legacyBundle);
legacyRegistry.enable('adapter');
@ -326,8 +309,6 @@ define([
// something has depended upon objectService. Cool, right?
this.$injector.get('objectService');
console.log('Rendering app layout.');
var appLayout = new Vue({
mixins: [Layout.default],
provide: {
@ -335,10 +316,8 @@ define([
}
});
domElement.appendChild(appLayout.$mount().$el);
console.log('Attaching adapter');
installVueAdapter(appLayout, this);
this.layout = appLayout;
Browse(this);
this.router.start();
this.emit('start');
}.bind(this));

View File

@ -34,7 +34,9 @@ define([
'./runs/TimeSettingsURLHandler',
'./runs/TypeDeprecationChecker',
'./runs/LegacyTelemetryProvider',
'./services/LegacyObjectAPIInterceptor'
'./runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews'
], function (
legacyRegistry,
ActionDialogDecorator,
@ -49,7 +51,9 @@ define([
TimeSettingsURLHandler,
TypeDeprecationChecker,
LegacyTelemetryProvider,
LegacyObjectAPIInterceptor
RegisterLegacyTypes,
LegacyObjectAPIInterceptor,
installLegacyViews
) {
legacyRegistry.register('src/adapter', {
"extensions": {
@ -149,6 +153,21 @@ define([
"openmct",
"instantiate"
]
},
{
implementation: installLegacyViews,
depends: [
"openmct",
"views[]",
"instantiate"
]
},
{
implementation: RegisterLegacyTypes,
depends: [
"types[]",
"openmct"
]
}
],
licenses: [

View File

@ -0,0 +1,17 @@
define([
], function (
) {
function RegisterLegacyTypes(types, openmct) {
types.forEach(function (legacyDefinition) {
if (!openmct.types.get(legacyDefinition.key)) {
console.warn(`DEPRECATION WARNING: Migrate type ${legacyDefinition.key} from ${legacyDefinition.bundle.path} to use the new Types API. Legacy type support will be removed soon.`);
}
});
openmct.types.importLegacyTypes(types);
}
return RegisterLegacyTypes;
});

View File

@ -0,0 +1,110 @@
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.`);
return {
key: legacyView.key,
name: legacyView.name,
cssClass: legacyView.cssClass,
description: legacyView.description,
editable: legacyView.editable,
canView: function (domainObject) {
if (!domainObject || !domainObject.identifier) {
return false;
}
if (legacyView.type) {
return domainObject.type === legacyView.type;
}
let legacyObject = convertToLegacyObject(domainObject);
if (legacyView.needs) {
let meetsNeeds = legacyView.needs.every(k => legacyObject.hasCapability(k));
if (!meetsNeeds) {
return false;
}
}
return openmct.$injector.get('policyService').allow(
'view', legacyView, legacyObject
);
},
view: function (domainObject) {
let $rootScope = openmct.$injector.get('$rootScope');
let templateLinker = openmct.$injector.get('templateLinker');
let scope = $rootScope.$new();
let legacyObject = convertToLegacyObject(domainObject);
let isDestroyed = false;
scope.domainObject = legacyObject;
scope.model = legacyObject.getModel();
return {
show: function (container) {
// TODO: implement "gestures" support ?
let uses = legacyView.uses || [];
let promises = [];
let results = uses.map(function (capabilityKey, i) {
let result = legacyObject.useCapability(capabilityKey);
if (result.then) {
promises.push(result.then(function (r) {
results[i] = r;
}));
}
return result;
});
function link() {
if (isDestroyed) {
return;
}
uses.forEach(function (key, i) {
scope[key] = results[i];
});
templateLinker.link(
scope,
openmct.$angular.element(container),
legacyView
);
container.style.height = '100%';
}
if (promises.length) {
Promise.all(promises)
.then(function () {
link();
scope.$digest();
});
} else {
link();
}
},
destroy: function () {
scope.$destroy();
}
}
},
priority: function () {
let priority = legacyView.priority || DEFAULT_VIEW_PRIORITY;
if (typeof priority === 'string') {
priority = PRIORITY_LEVELS[priority];
}
return priority;
}
};
};
return LegacyViewProvider;
});

View File

@ -0,0 +1,22 @@
define([
'./LegacyViewProvider',
'../../api/objects/object-utils'
], function (
LegacyViewProvider,
objectUtils
) {
function installLegacyViews(openmct, legacyViews, instantiate) {
function convertToLegacyObject(domainObject) {
let keyString = objectUtils.makeKeyString(domainObject.identifier);
let oldModel = objectUtils.toOldFormat(domainObject);
return instantiate(oldModel, keyString);
}
legacyViews.forEach(function (legacyView) {
openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject));
});
}
return installLegacyViews;
});

View File

@ -1,41 +0,0 @@
define([
], function (
) {
class InspectorAdapter {
constructor(layout, openmct) {
console.log('installing inspector adapter');
this.openmct = openmct;
this.layout = layout;
this.$injector = openmct.$injector;
this.angular = openmct.$angular;
this.objectService = this.$injector.get('objectService');
this.templateLinker = this.$injector.get('templateLinker');
this.$timeout = this.$injector.get('$timeout');
this.templateMap = {};
this.$injector.get('templates[]').forEach((t) => {
this.templateMap[t.key] = this.templateMap[t.key] || t;
});
var $rootScope = this.$injector.get('$rootScope');
this.scope = $rootScope.$new();
this.templateLinker.link(
this.scope,
angular.element(layout.$refs.inspector.$refs.properties),
this.templateMap["inspectorRegion"]
);
this.$timeout(function () {
//hello!
});
}
}
return InspectorAdapter;
});

View File

@ -1,16 +0,0 @@
define([
'./main-adapter',
'./tree-adapter',
'./inspector-adapter'
], function (
MainAdapter,
TreeAdapter,
InspectorAdapter
) {
return function install(layout, openmct) {
let main = new MainAdapter(layout, openmct);
let tree = new TreeAdapter(layout, openmct);
let inspector = new InspectorAdapter(layout, openmct);
}
});

View File

@ -1,153 +0,0 @@
define([
], function (
) {
// Find an object in an array of objects.
function findObject(domainObjects, id) {
var i;
for (i = 0; i < domainObjects.length; i += 1) {
if (domainObjects[i].getId() === id) {
return domainObjects[i];
}
}
}
// recursively locate and return an object inside of a container
// via a path. If at any point in the recursion it fails to find
// the next object, it will return the parent.
function findViaComposition(containerObject, path) {
var nextId = path.shift();
if (!nextId) {
return containerObject;
}
return containerObject.useCapability('composition')
.then(function (composees) {
var nextObject = findObject(composees, nextId);
if (!nextObject) {
return containerObject;
}
if (!nextObject.hasCapability('composition')) {
return nextObject;
}
return findViaComposition(nextObject, path);
});
}
function getLastChildIfRoot(object) {
if (object.getId() !== 'ROOT') {
return object;
}
return object.useCapability('composition')
.then(function (composees) {
return composees[composees.length - 1];
});
}
function pathForObject(domainObject) {
var context = domainObject.getCapability('context'),
objectPath = context ? context.getPath() : [],
ids = objectPath.map(function (domainObj) {
return domainObj.getId();
});
return "/browse/" + ids.slice(1).join("/");
}
class MainAdapter {
constructor(layout, openmct) {
this.openmct = openmct;
this.layout = layout;
this.$injector = openmct.$injector;
this.angular = openmct.$angular;
this.objectService = this.$injector.get('objectService');
this.templateLinker = this.$injector.get('templateLinker');
this.navigationService = this.$injector.get('navigationService');
this.$timeout = this.$injector.get('$timeout');
this.templateMap = {};
this.$injector.get('templates[]').forEach((t) => {
this.templateMap[t.key] = this.templateMap[t.key] || t;
});
var $rootScope = this.$injector.get('$rootScope');
this.scope = $rootScope.$new();
this.scope.representation = {};
openmct.router.route(/^\/browse\/(.*)$/, (path, results) => {
let navigatePath = results[1];
if (!navigatePath) {
navigatePath = 'mine';
}
this.navigateToPath(navigatePath);
});
this.navigationService.addListener(o => this.navigateToObject(o));
}
navigateToPath(path) {
if (!Array.isArray(path)) {
path = path.split('/');
}
return this.getObject('ROOT')
.then(root => {
return findViaComposition(root, path);
})
.then(getLastChildIfRoot)
.then(object => {
this.setMainViewObject(object);
});
}
setMainViewObject(object) {
this.scope.domainObject = object;
this.scope.navigatedObject = object;
this.templateLinker.link(
this.scope,
angular.element(this.layout.$refs.mainContainer),
this.templateMap["browseObject"]
);
document.title = object.getModel().name;
this.scheduleDigest();
}
idsForObject(domainObject) {
return this.urlService
.urlForLocation("", domainObject)
.replace('/', '');
}
navigateToObject(object) {
let path = pathForObject(object);
let views = object.useCapability('view');
let params = this.openmct.router.getParams();
let currentViewIsValid = views.some(v => v.key === params['view']);
if (!currentViewIsValid) {
this.scope.representation = {
selected: views[0]
}
this.openmct.router.update(path, {
view: views[0].key
});
} else {
this.openmct.router.setPath(path);
}
}
scheduleDigest() {
this.$timeout(function () {
// digest done!
});
}
getObject(id) {
return this.objectService.getObjects([id])
.then(function (results) {
return results[id];
});
}
}
return MainAdapter;
});

View File

@ -1,15 +0,0 @@
define([
], function (
) {
class TreeAdapter {
constructor(layout, openmct) {
}
}
return TreeAdapter;
});

View File

@ -89,12 +89,13 @@ define(function () {
}
}
}
if (Array.isArray(legacyDefinition.creatable) && 'creation' in legacyDefinition.creatable) {
if (legacyDefinition.features && legacyDefinition.features.includes("creation")) {
definition.creatable = true;
}
return definition;
}
};
return Type;
});

View File

@ -55,11 +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',
'../platform/features/my-items/bundle',
'../platform/features/pages/bundle',
'../platform/features/hyperlink/bundle',
@ -103,7 +100,6 @@ define([
'platform/features/fixed',
'platform/features/imagery',
'platform/features/layout',
'platform/features/listview',
'platform/features/pages',
'platform/features/hyperlink',
'platform/features/timeline',

View File

@ -20,20 +20,18 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'csv',
'saveAs'
], function (CSV, saveAs) {
class CSVExporter {
export(rows, options) {
let headers = (options && options.headers) ||
(Object.keys((rows[0] || {})).sort());
let filename = (options && options.filename) || "export.csv";
let csvText = new CSV(rows, { header: headers }).encode();
let blob = new Blob([csvText], { type: "text/csv" });
saveAs(blob, filename);
}
}
import CSV from 'comma-separated-values';
import {saveAs} from 'file-saver/FileSaver';
return CSVExporter;
});
class CSVExporter {
export(rows, options) {
let headers = (options && options.headers) ||
(Object.keys((rows[0] || {})).sort());
let filename = (options && options.filename) || "export.csv";
let csvText = new CSV(rows, { header: headers }).encode();
let blob = new Blob([csvText], { type: "text/csv" });
saveAs(blob, filename);
}
};
export default CSVExporter;

View File

@ -0,0 +1,67 @@
/*****************************************************************************
* 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

@ -0,0 +1,70 @@
/*****************************************************************************
* 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

@ -0,0 +1,200 @@
<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>
<button class="c-click-icon icon-info c-info-button" title='More Info'></button>
<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;
font-size: 2em; // Drives the size of the alias indicator when present
margin-right: $interiorMarginLg;
}
&.is-alias {
// Object is an alias to an original.
[class*='__type-icon'] {
@include isAlias();
color: $colorIconAliasForKeyFilter;
}
}
&__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;
font-size: 6em; // Drives the size of the alias indicator when present
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

@ -0,0 +1,215 @@
<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

@ -20,34 +20,17 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
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;
}
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));
};
};
StringFormat.prototype.parse = function (string) {
return string;
};
StringFormat.prototype.validate = function (string) {
return typeof string === 'string';
};
return StringFormat;
});

View File

@ -51,64 +51,12 @@ 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 = true;
this.isUTCBased = false;
}
return LocalTimeSystem;

View File

@ -0,0 +1,215 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"./src/controllers/NotebookController",
"./src/controllers/NewEntryController",
"./src/controllers/SelectSnapshotController",
"./src/actions/NewEntryContextual",
"./src/actions/AnnotateSnapshot",
"./src/directives/MCTSnapshot",
"./src/directives/EntryDnd",
"./res/templates/controls/snapSelect.html",
"./res/templates/controls/embedControl.html",
"./res/templates/annotation.html",
"./res/templates/draggedEntry.html"
], function (
NotebookController,
NewEntryController,
SelectSnapshotController,
newEntryAction,
AnnotateSnapshotAction,
MCTSnapshotDirective,
EntryDndDirective,
snapSelectTemplate,
embedControlTemplate,
annotationTemplate,
draggedEntryTemplate
) {
var installed = false;
function NotebookPlugin() {
return function install(openmct) {
if (installed) {
return;
}
installed = true;
openmct.legacyRegistry.register('notebook', {
name: 'Notebook Plugin',
extensions: {
types: [
{
key: 'notebook',
name: 'Notebook',
cssClass: 'icon-notebook',
description: 'Create and save timestamped notes with embedded object snapshots.',
features: 'creation',
model: {
entries: [],
composition: [],
entryTypes: [],
defaultSort: '-createdOn'
},
properties: [
{
key: 'defaultSort',
name: 'Default Sort',
control: 'select',
options: [
{
name: 'Newest First',
value: "-createdOn",
},
{
name: 'Oldest First',
value: "createdOn"
}
],
cssClass: 'l-inline'
}
]
}
],
actions: [
{
"key": "notebook-new-entry",
"implementation": newEntryAction,
"name": "New Notebook Entry",
"cssClass": "icon-notebook labeled",
"description": "Add a new Notebook entry",
"category": [
"view-control"
],
"depends": [
"$compile",
"$rootScope",
"dialogService",
"notificationService",
"linkService"
],
"priority": "preferred"
},
{
"key": "annotate-snapshot",
"implementation": AnnotateSnapshotAction,
"name": "Annotate Snapshot",
"cssClass": "icon-pencil labeled",
"description": "Annotate embed's snapshot",
"category": "embed",
"depends": [
"dialogService",
"dndService",
"$rootScope"
]
}
],
controllers: [
{
"key": "NewEntryController",
"implementation": NewEntryController,
"depends": ["$scope",
"$rootScope"
]
},
{
"key": "selectSnapshotController",
"implementation": SelectSnapshotController,
"depends": ["$scope",
"$rootScope"
]
}
],
controls: [
{
"key": "snapshot-select",
"template": snapSelectTemplate
},
{
"key": "embed-control",
"template": embedControlTemplate
}
],
templates: [
{
"key": "annotate-snapshot",
"template": annotationTemplate
}
],
directives: [
{
"key": "mctSnapshot",
"implementation": MCTSnapshotDirective,
"depends": [
"$rootScope",
"$document",
"exportImageService",
"dialogService",
"notificationService"
]
},
{
"key": "mctEntryDnd",
"implementation": EntryDndDirective,
"depends": [
"$rootScope",
"$compile",
"dndService",
"typeService",
"notificationService"
]
}
],
representations: [
{
"key": "draggedEntry",
"template": draggedEntryTemplate
}
]
}
});
openmct.legacyRegistry.enable('notebook');
openmct.objectViews.addProvider({
key: 'notebook-vue',
name: 'Notebook View',
cssClass: 'icon-notebook',
canView: function (domainObject) {
return domainObject.type === 'notebook';
},
view: function (domainObject) {
var controller = new NotebookController (openmct, domainObject);
return {
show: controller.show,
destroy: controller.destroy
};
}
});
};
}
return NotebookPlugin;
});

View File

@ -25,6 +25,5 @@
ng-options="opt.value as opt.name for opt in options"
ng-required="ngRequired"
name="mctControl">
<!-- <option value="" ng-show="!ngModel[field]">- Select One -</option> -->
</select>
</div>

View File

@ -0,0 +1,32 @@
<div class="c-ne__embed">
<div class="c-ne__embed__snap-thumb"
v-if="embed.snapshot"
v-on:click="openSnapshot">
<img v-bind:src="embed.snapshot.src">
</div>
<div class="c-ne__embed__info">
<div class="c-ne__embed__name">
<a class="c-ne__embed__link"
v-on:click="navigate(embed.type)"
v-bind:class="[embed.cssClass]">{{embed.name}}</a>
<a class="c-ne__embed__context-available icon-arrow-down"
v-on:click="toggleActionMenu"></a>
</div>
<div class="hide-menu hidden">
<div class="menu-element context-menu-wrapper mobile-disable-select">
<div class="menu context-menu">
<ul>
<li v-for="action in actions"
v-bind:class="[action.cssClass]"
v-on:click="action.perform(embed, entry)">
{{ action.name }}
</li>
</ul>
</div>
</div>
</div>
<div class="c-ne__embed__time" v-if="embed.snapshot">
{{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
<li class="c-notebook__entry c-ne has-local-controls"
v-on:drop="dropOnEntry(entry.id)"
v-on:dragover="dragoverOnEntry"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<span>{{formatTime(entry.createdOn, 'YYYY-MM-DD')}}</span>
<span>{{formatTime(entry.createdOn, 'HH:mm:ss')}}</span>
</div>
<div class="c-ne__content">
<!-- TODO: fix styling for c-input-inline when SCSS is merged and remove s-input-inline class here -->
<div class="c-ne__text c-input-inline s-input-inline"
contenteditable="true"
ref="contenteditable"
v-on:blur="textBlur($event, entry.id)"
v-on:focus="textFocus($event, entry.id)"
v-bind:key="entry.id"
v-html="entry.text">
</div>
<div class="c-ne__embeds">
<notebook-embed
v-for="(embed, index) in entry.embeds"
v-bind:embed="embed"
v-bind:entry="entry"
></notebook-embed>
</div>
</div>
</div>
<div class="c-ne__local-controls--hidden">
<button class="c-click-icon icon-trash"
title="Delete this entry"
v-on:click="deleteEntry"></button>
</div>
</li>

View File

@ -0,0 +1,37 @@
<div class="c-notebook">
<div class="c-notebook__head">
<search class="c-notebook__search"
v-model="entrySearch"
v-on:input="search($event)"
v-on:clear="entrySearch = ''; search($event)"></search>
<div class="c-notebook__controls">
<div class="select c-notebook__controls__time">
<select v-model="showTime">
<option value="0" selected="selected">Show all</option>
<option value="1">Last hour</option>
<option value="8">Last 8 hours</option>
<option value="24">Last 24 hours</option>
</select>
</div>
<div class="select c-notebook__controls__sort">
<select v-model="sortEntries">
<option value="-createdOn" selected="selected">Newest first</option>
<option value="createdOn">Oldest first</option>
</select>
</div>
</div>
</div>
<div class="c-notebook__drag-area icon-plus"
v-on:click="newEntry($event)"
id="newEntry" mct-entry-dnd>
<span class="c-notebook__drag-area__label">To start a new entry, click here or drag and drop any object</span>
</div>
<div class="c-notebook__entries" ng-mouseover="handleActive()">
<ul>
<notebook-entry
v-for="entry in filterBySearch(entries, entrySearch)"
v-bind:entry="entry"
></notebook-entry>
</ul>
</div>
</div>

View File

@ -0,0 +1,50 @@
<div class="abs overlay l-large-view">
<div class="abs blocker" v-on:click="close"></div>
<div class="abs outer-holder">
<a
class="close icon-x-in-circle"
v-on:click="close">
</a>
<div class="abs inner-holder l-flex-col">
<div class="t-contents flex-elem holder grows">
<div class="t-snapshot abs l-view-header">
<div class="abs object-browse-bar l-flex-row">
<div class="left flex-elem l-flex-row grows">
<div class="object-header flex-elem l-flex-row grows">
<div class="type-icon flex-elem embed-icon holder" v-bind:class="embed.cssClass"></div>
<div class="title-label flex-elem holder flex-can-shrink">{{embed.name}}</div>
</div>
</div>
</div>
</div>
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div>
<a class="s-button icon-pencil" title="Annotate">
<span class="title-label">Annotate</span>
</a>
</div>
<div class="abs object-holder t-image-holder s-image-holder">
<div
class="image-main s-image-main"
v-bind:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }">
</div>
</div>
</div>
<div
class="bottom-bar flex-elem holder"
v-on:click="close">
<a class="t-done s-button major">Done</a>
</div>
</div>
</div>
</div>

View File

@ -26,6 +26,7 @@
define(
["painterro", "zepto"],
function (Painterro, $) {
var annotationStruct = {
title: "Annotate Snapshot",
template: "annotate-snapshot",
@ -107,9 +108,6 @@ define(
done(true);
}
}).show(snapshot);
$(document.body).find('.ptro-icon-btn').addClass('s-button');
$(document.body).find('.ptro-input').addClass('s-button');
});
}];

View File

@ -90,7 +90,7 @@ define(
var dialogService = this.dialogService;
var rootScope = this.$rootScope;
rootScope.newEntryText = '';
// Create the overlay element and add it to the document's body
// // Create the overlay element and add it to the document's body
this.$rootScope.selObj = domainObj;
this.$rootScope.selValue = "";
var newScope = rootScope.$new();
@ -187,7 +187,7 @@ define(
var domainObject = context.domainObject;
if (domainObject) {
if (domainObject.getModel().type === 'Notebook') {
if (domainObject.getModel().type === 'notebook') {
// do not allow in context of a notebook
return false;
} else if (domainObject.getModel().type.includes('imagery')) {

View File

@ -0,0 +1,130 @@
/*****************************************************************************
* 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(
['zepto'],
function ($) {
function SnapshotAction (exportImageService, dialogService, context) {
this.exportImageService = exportImageService;
this.dialogService = dialogService;
this.domainObject = context.domainObject;
}
SnapshotAction.prototype.perform = function () {
var elementToSnapshot =
$(document.body).find(".overlay .object-holder")[0] ||
$(document.body).find("[key='representation.selected.key']")[0];
$(elementToSnapshot).addClass("s-status-taking-snapshot");
this.exportImageService.exportPNGtoSRC(elementToSnapshot).then(function (blob) {
$(elementToSnapshot).removeClass("s-status-taking-snapshot");
if (blob) {
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function () {
this.saveSnapshot(reader.result, blob.type, blob.size);
}.bind(this);
}
}.bind(this));
};
SnapshotAction.prototype.saveSnapshot = function (imageURL, imageType, imageSize) {
var taskForm = this.generateTaskForm(),
domainObject = this.domainObject,
domainObjectId = domainObject.getId(),
cssClass = domainObject.getCapability('type').typeDef.cssClass,
name = domainObject.model.name;
this.dialogService.getDialogResponse(
'overlay-dialog',
taskForm,
function () {
return taskForm.value;
}
).then(function (options) {
var snapshotObject = {
src: imageURL,
type: imageType,
size: imageSize
};
options.notebook.useCapability('mutation', function (model) {
var date = Date.now();
model.entries.push({
id: 'entry-' + date,
createdOn: date,
text: options.entry,
embeds: [{
name: name,
cssClass: cssClass,
type: domainObjectId,
id: 'embed-' + date,
createdOn: date,
snapshot: snapshotObject
}]
});
});
});
};
SnapshotAction.prototype.generateTaskForm = function () {
var taskForm = {
name: "Create a Notebook Entry",
hint: "Please select a Notebook",
sections: [{
rows: [{
name: 'Entry',
key: 'entry',
control: 'textarea',
required: false,
"cssClass": "l-textarea-sm"
},
{
name: 'Save in Notebook',
key: 'notebook',
control: 'locator',
validate: validateLocation
}]
}]
};
var overlayModel = {
title: taskForm.name,
message: 'AHAHAH',
structure: taskForm,
value: {'entry': ""}
};
function validateLocation(newParentObj) {
return newParentObj.model.type === 'notebook';
}
return overlayModel;
};
return SnapshotAction;
}
);

View File

@ -0,0 +1,198 @@
/*****************************************************************************
* 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([
'moment',
'zepto',
'../utils/SnapshotOverlay',
],
function (
Moment,
$,
SnapshotOverlay
) {
function EmbedController (openmct, domainObject) {
this.openmct = openmct;
this.domainObject = domainObject;
this.objectService = openmct.$injector.get('objectService');
this.navigationService = openmct.$injector.get('navigationService');
this.popupService = openmct.$injector.get('popupService');
this.agentService = openmct.$injector.get('agentService');
this.dialogService = openmct.$injector.get('dialogService');
this.navigate = this.navigate.bind(this);
this.exposedData = this.exposedData.bind(this);
this.exposedMethods = this.exposedMethods.bind(this);
this.toggleActionMenu = this.toggleActionMenu.bind(this);
}
EmbedController.prototype.navigate = function (embedType) {
this.objectService.getObjects([embedType]).then(function (objects) {
this.navigationService.setNavigation(objects[embedType]);
}.bind(this));
};
EmbedController.prototype.openSnapshot = function () {
if (!this.snapshotOverlay) {
this.snapShotOverlay = new SnapshotOverlay(this.embed, this.formatTime);
} else {
this.snapShotOverlay = undefined;
}
};
EmbedController.prototype.formatTime = function (unixTime, timeFormat) {
return Moment(unixTime).format(timeFormat);
};
EmbedController.prototype.findInArray = function (array, id) {
var foundId = -1;
array.forEach(function (element, index) {
if (element.id === id) {
foundId = index;
return;
}
});
return foundId;
};
EmbedController.prototype.actionToMenuDecorator = function (action) {
return {
name: action.getMetadata().name,
cssClass: action.getMetadata().cssClass,
perform: action.perform
};
};
EmbedController.prototype.populateActionMenu = function (objectService, actionService) {
return function () {
var self = this;
objectService.getObjects([self.embed.type]).then(function (resp) {
var domainObject = resp[self.embed.type],
previewAction = actionService.getActions({key: 'mct-preview-action', domainObject: domainObject})[0];
self.actions.push(self.actionToMenuDecorator(previewAction));
});
};
};
EmbedController.prototype.removeEmbedAction = function () {
var self = this;
return {
name: 'Remove Embed',
cssClass: 'icon-trash',
perform: function (embed, entry) {
var entryPosition = self.findInArray(self.domainObject.entries, entry.id),
embedPosition = self.findInArray(entry.embeds, embed.id);
var warningDialog = self.dialogService.showBlockingMessage({
severity: "error",
title: "This action will permanently delete this embed. Do you wish to continue?",
options: [{
label: "OK",
callback: function () {
entry.embeds.splice(embedPosition, 1);
var dirString = 'entries[' + entryPosition + '].embeds';
self.openmct.objects.mutate(self.domainObject, dirString, entry.embeds);
warningDialog.dismiss();
}
},{
label: "Cancel",
callback: function () {
warningDialog.dismiss();
}
}]
});
}
};
};
EmbedController.prototype.toggleActionMenu = function (event) {
event.preventDefault();
var body = $(document.body),
container = $(event.target.parentElement.parentElement),
initiatingEvent = this.agentService.isMobile() ?
'touchstart' : 'mousedown',
menu = container.find('.menu-element'),
dismissExistingMenu;
// Remove the context menu
function dismiss() {
container.find('.hide-menu').append(menu);
body.off(initiatingEvent, dismiss);
menu.off(initiatingEvent, menuClickHandler);
dismissExistingMenu = undefined;
}
function menuClickHandler(e) {
e.stopPropagation();
window.setTimeout(dismiss, 300);
}
// Dismiss any menu which was already showing
if (dismissExistingMenu) {
dismissExistingMenu();
}
// ...and record the presence of this menu.
dismissExistingMenu = dismiss;
this.popupService.display(menu, [event.pageX,event.pageY], {
marginX: 0,
marginY: -50
});
// Stop propagation so that clicks or touches on the menu do not close the menu
menu.on(initiatingEvent, menuClickHandler);
body.on(initiatingEvent, dismiss);
};
EmbedController.prototype.exposedData = function () {
return {
actions: [this.removeEmbedAction()],
showActionMenu: false
};
};
EmbedController.prototype.exposedMethods = function () {
var self = this;
return {
navigate: self.navigate,
openSnapshot: self.openSnapshot,
formatTime: self.formatTime,
toggleActionMenu: self.toggleActionMenu,
actionToMenuDecorator: self.actionToMenuDecorator
};
};
return EmbedController;
});

View File

@ -0,0 +1,150 @@
/*****************************************************************************
* 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([
'moment'
],
function (
Moment
) {
function EntryController (openmct, domainObject) {
this.openmct = openmct;
this.domainObject = domainObject;
this.dndService = this.openmct.$injector.get('dndService');
this.dialogService = this.openmct.$injector.get('dialogService');
this.currentEntryValue = '';
this.exposedMethods = this.exposedMethods.bind(this);
this.exposedData = this.exposedData.bind(this);
}
EntryController.prototype.entryPosById = function (entryId) {
var foundId = -1;
this.domainObject.entries.forEach(function (element, index) {
if (element.id === entryId) {
foundId = index;
return;
}
});
return foundId;
};
EntryController.prototype.textFocus = function ($event) {
if ($event.target) {
this.currentEntryValue = $event.target.innerText;
} else {
$event.target.innerText = '';
}
};
EntryController.prototype.textBlur = function ($event, entryId) {
if ($event.target) {
var entryPos = this.entryPosById(entryId);
if (this.currentEntryValue !== $event.target.innerText) {
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].text', $event.target.innerText);
}
}
};
EntryController.prototype.formatTime = function (unixTime, timeFormat) {
return Moment(unixTime).format(timeFormat);
};
EntryController.prototype.deleteEntry = function () {
var entryPos = this.entryPosById(this.entry.id),
domainObject = this.domainObject,
openmct = this.openmct;
if (entryPos !== -1) {
var errorDialog = this.dialogService.showBlockingMessage({
severity: "error",
title: "This action will permanently delete this Notebook entry. Do you wish to continue?",
options: [{
label: "OK",
callback: function () {
domainObject.entries.splice(entryPos, 1);
openmct.objects.mutate(domainObject, 'entries', domainObject.entries);
errorDialog.dismiss();
}
},{
label: "Cancel",
callback: function () {
errorDialog.dismiss();
}
}]
});
}
};
EntryController.prototype.dropOnEntry = function (entryId) {
var selectedObject = this.dndService.getData('mct-domain-object'),
selectedObjectId = selectedObject.getId(),
selectedModel = selectedObject.getModel(),
cssClass = selectedObject.getCapability('type').typeDef.cssClass,
entryPos = this.entryPosById(entryId),
currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
newEmbed = {
type: selectedObjectId,
id: '' + Date.now(),
cssClass: cssClass,
name: selectedModel.name,
snapshot: ''
};
currentEntryEmbeds.push(newEmbed);
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
};
EntryController.prototype.dragoverOnEntry = function () {
};
EntryController.prototype.exposedData = function () {
return {
openmct: this.openmct,
domainObject: this.domainObject,
dndService: this.dndService,
dialogService: this.dialogService,
currentEntryValue: this.currentEntryValue
};
};
EntryController.prototype.exposedMethods = function () {
return {
entryPosById: this.entryPosById,
textFocus: this.textFocus,
textBlur: this.textBlur,
formatTime: this.formatTime,
deleteEntry: this.deleteEntry,
dropOnEntry: this.dropOnEntry,
dragoverOnEntry: this.dragoverOnEntry
};
};
return EntryController;
});

View File

@ -31,8 +31,7 @@ define(
$scope.snapshot = undefined;
$scope.snapToggle = true;
$scope.entryText = '';
var annotateAction = $rootScope.selObj.getCapability('action').getActions(
{category: 'embed'})[1];
var annotateAction = $rootScope.selObj.getCapability('action').getActions({key: 'annotate-snapshot'})[0];
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = $rootScope.selObj;
$scope.objectName = $rootScope.selObj.getModel().name;

View File

@ -0,0 +1,177 @@
/*****************************************************************************
* 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([
'vue',
'./EntryController',
'./EmbedController',
'../../res/templates/notebook.html',
'../../res/templates/entry.html',
'../../res/templates/embed.html',
'../../../../ui/components/controls/search.vue'
],
function (
Vue,
EntryController,
EmbedController,
NotebookTemplate,
EntryTemplate,
EmbedTemplate,
search
) {
function NotebookController(openmct, domainObject) {
this.openmct = openmct;
this.domainObject = domainObject;
this.entrySearch = '';
this.objectService = openmct.$injector.get('objectService');
this.actionService = openmct.$injector.get('actionService');
this.show = this.show.bind(this);
this.destroy = this.destroy.bind(this);
this.newEntry = this.newEntry.bind(this);
this.entryPosById = this.entryPosById.bind(this);
}
NotebookController.prototype.initializeVue = function (container) {
var self = this,
entryController = new EntryController(this.openmct, this.domainObject),
embedController = new EmbedController(this.openmct, this.domainObject);
this.container = container;
var notebookEmbed = {
props:['embed', 'entry'],
template: EmbedTemplate,
data: embedController.exposedData,
methods: embedController.exposedMethods(),
beforeMount: embedController.populateActionMenu(self.objectService, self.actionService)
};
var entryComponent = {
props:['entry'],
template: EntryTemplate,
components: {
'notebook-embed': notebookEmbed
},
data: entryController.exposedData,
methods: entryController.exposedMethods(),
mounted: self.focusOnEntry
};
var notebookVue = Vue.extend({
template: NotebookTemplate,
components: {
'notebook-entry': entryComponent,
'search': search.default
},
data: function () {
return {
entrySearch: self.entrySearch,
showTime: '0',
sortEntries: '-createdOn',
entries: self.domainObject.entries,
currentEntryValue: ''
};
},
methods: {
search: function (event) {
if (event.target.value) {
this.entrySearch = event.target.value;
}
},
newEntry: self.newEntry,
filterBySearch: self.filterBySearch
}
});
this.NotebookVue = new notebookVue();
container.appendChild(this.NotebookVue.$mount().$el);
};
NotebookController.prototype.newEntry = function (event) {
var entries = this.domainObject.entries,
lastEntryIndex = entries.length - 1,
lastEntry = entries[lastEntryIndex],
date = Date.now();
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds.length) {
var createdEntry = {'id': 'entry-' + date, 'createdOn': date, 'embeds':[]};
entries.push(createdEntry);
this.openmct.objects.mutate(this.domainObject, 'entries', entries);
} else {
lastEntry.createdOn = date;
this.openmct.objects.mutate(this.domainObject, 'entries[entries.length-1]', lastEntry);
this.focusOnEntry.bind(this.NotebookVue.$children[lastEntryIndex])();
}
this.entrySearch = '';
};
NotebookController.prototype.entryPosById = function (entryId) {
var foundId = -1;
this.domainObject.entries.forEach(function (element, index) {
if (element.id === entryId) {
foundId = index;
return;
}
});
return foundId;
};
NotebookController.prototype.focusOnEntry = function () {
if (!this.entry.text) {
this.$refs.contenteditable.focus();
}
};
NotebookController.prototype.filterBySearch = function (entryArray, filterString) {
if (filterString) {
var lowerCaseFilterString = filterString.toLowerCase();
return entryArray.filter(function (entry) {
if (entry.text) {
return entry.text.toLowerCase().includes(lowerCaseFilterString);
} else {
return false;
}
});
} else {
return entryArray;
}
};
NotebookController.prototype.show = function (container) {
this.initializeVue(container);
};
NotebookController.prototype.destroy = function (container) {
this.NotebookVue.$destroy(true);
};
return NotebookController;
});

View File

@ -31,32 +31,34 @@ define(['zepto'], function ($) {
var selectedModel = selectedObject.getModel();
var cssClass = selectedObject.getCapability('type').typeDef.cssClass;
var entryId = -1;
var embedId = -1;
$scope.clearSearch();
if ($element[0].id === 'newEntry') {
entryId = $scope.domainObject.model.entries.length;
embedId = 0;
var lastEntry = $scope.domainObject.model.entries[entryId - 1];
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries.push({'createdOn': +Date.now(),
'id': +Date.now(),
'embeds': [{'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
}]
});
'id': +Date.now(),
'embeds': [{'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
}]
});
});
}else {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entryId - 1] =
{'createdOn': +Date.now(),
'embeds': [{'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
}]
'embeds': [{'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
}]
};
});
}
@ -75,13 +77,15 @@ define(['zepto'], function ($) {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entryId].embeds.push({'type': selectedObject.getId(),
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
});
'id': '' + Date.now(),
'cssClass': cssClass,
'name': selectedModel.name,
'snapshot': ''
});
});
embedId = $scope.domainObject.model.entries[entryId].embeds.length - 1;
if (selectedObject) {
e.preventDefault();

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -20,35 +20,47 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* This bundle implements object types and associated views for
* display-building.
*/
define(
[],
function () {
/**
* The LayoutNotebookController is responsible for supporting the
* notebook feature creation on theLayout view.
**/
define([
'vue',
'../../res/templates/viewSnapshot.html'
], function (
Vue,
snapshotOverlayTemplate
) {
function SnapshotOverlay (embedObject, formatTime) {
this.embedObject = embedObject;
function LayoutNotebookController($scope) {
$scope.hasNotebookAction = undefined;
$scope.newNotebook = undefined;
var actions = $scope.domainObject.getCapability('action');
var notebookAction = actions.getActions({'key': 'notebook-new-entry'});
if (notebookAction.length > 0) {
$scope.hasNotebookAction = true;
$scope.newNotebook = function () {
notebookAction[0].perform();
this.snapshotOverlayVue = new Vue({
template: snapshotOverlayTemplate,
data: function () {
return {
embed: embedObject
};
},
methods: {
close: this.close.bind(this),
formatTime: formatTime
}
}
});
return LayoutNotebookController;
this.open();
}
);
SnapshotOverlay.prototype.open = function () {
this.overlay = document.createElement('div');
this.overlay.classList.add('abs');
document.body.appendChild(this.overlay);
this.overlay.appendChild(this.snapshotOverlayVue.$mount().$el);
};
SnapshotOverlay.prototype.close = function (event) {
event.stopPropagation();
this.snapshotOverlayVue.$destroy();
this.overlay.parentNode.removeChild(this.overlay);
};
return SnapshotOverlay;
});

View File

@ -27,14 +27,15 @@ define([
'./autoflow/AutoflowTabularPlugin',
'./timeConductor/plugin',
'../../example/imagery/plugin',
'../../platform/features/notebook/bundle',
'../../platform/import-export/bundle',
'./summaryWidget/plugin',
'./URLIndicatorPlugin/URLIndicatorPlugin',
'./telemetryMean/plugin',
'./plot/plugin',
'./telemetryTable/plugin',
'./staticRootPlugin/plugin'
'./staticRootPlugin/plugin',
'./notebook/plugin',
'./folderView/plugin'
], function (
_,
UTCTimeSystem,
@ -42,19 +43,19 @@ define([
AutoflowPlugin,
TimeConductorPlugin,
ExampleImagery,
Notebook,
ImportExport,
SummaryWidget,
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
TelemetryTablePlugin,
StaticRootPlugin
StaticRootPlugin,
Notebook,
FolderView
) {
var bundleMap = {
LocalStorage: 'platform/persistence/local',
MyItems: 'platform/features/my-items',
Notebook: 'platform/features/notebook'
MyItems: 'platform/features/my-items'
};
var plugins = _.mapValues(bundleMap, function (bundleName, pluginName) {
@ -102,7 +103,7 @@ define([
*/
plugins.AutoflowView = AutoflowPlugin;
plugins.Conductor = TimeConductorPlugin;
plugins.Conductor = TimeConductorPlugin.default;
plugins.CouchDB = function (url) {
return function (openmct) {
@ -155,10 +156,12 @@ define([
plugins.ExampleImagery = ExampleImagery;
plugins.Plot = PlotPlugin;
plugins.TelemetryTable = TelemetryTablePlugin;
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin;
plugins.Notebook = Notebook;
plugins.FolderView = FolderView;
return plugins;
});

View File

@ -8,14 +8,15 @@ define([
objectUtils
) {
const DEFAULT_VIEW_PRIORITY = 100;
/**
*
*/
function SummaryWidgetViewProvider(openmct) {
return {
key: 'summary-widget-viewer',
name: 'Widget View',
name: 'Summary View',
cssClass: 'icon-summary-widget',
canView: function (domainObject) {
return domainObject.type === 'summary-widget';
},
@ -33,7 +34,11 @@ define([
},
editable: true,
priority: function (domainObject) {
return 1;
if (domainObject.type === 'summary-widget') {
return Number.MAX_VALUE;
} else {
return DEFAULT_VIEW_PRIORITY;
}
}
};
}

View File

@ -1,87 +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([
'lodash',
'vue',
'./table-configuration.html',
'./TelemetryTableConfiguration'
],function (
_,
Vue,
TableConfigurationTemplate,
TelemetryTableConfiguration
) {
return function TableConfigurationComponent(domainObject, openmct) {
const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
let unlisteners = [];
return new Vue({
template: TableConfigurationTemplate,
data() {
return {
headers: {},
configuration: tableConfiguration.getConfiguration()
}
},
methods: {
updateHeaders(headers) {
this.headers = headers;
},
toggleColumn(key) {
let isHidden = this.configuration.hiddenColumns[key] === true;
this.configuration.hiddenColumns[key] = !isHidden;
tableConfiguration.updateConfiguration(this.configuration);
},
addObject(domainObject) {
tableConfiguration.addColumnsForObject(domainObject, true);
this.updateHeaders(tableConfiguration.getAllHeaders());
},
removeObject(objectIdentifier) {
tableConfiguration.removeColumnsForObject(objectIdentifier, true);
this.updateHeaders(tableConfiguration.getAllHeaders());
}
},
mounted() {
let compositionCollection = openmct.composition.get(domainObject);
compositionCollection.load()
.then((composition) => {
tableConfiguration.addColumnsForAllObjects(composition);
this.updateHeaders(tableConfiguration.getAllHeaders());
compositionCollection.on('add', this.addObject);
unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
compositionCollection.on('remove', this.removeObject);
unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
});
},
destroyed() {
tableConfiguration.destroy();
unlisteners.forEach((unlisten) => unlisten());
}
});
}
});

View File

@ -22,11 +22,16 @@
define([
'../../api/objects/object-utils',
'./TableConfigurationComponent'
'./components/table-configuration.vue',
'./TelemetryTableConfiguration',
'vue'
], function (
objectUtils,
TableConfigurationComponent
TableConfigurationComponent,
TelemetryTableConfiguration,
Vue
) {
function TableConfigurationViewProvider(openmct) {
let instantiateService;
@ -51,27 +56,38 @@ define([
return instantiateService(model, id);
}
return {
key: 'table-configuration',
name: 'Telemetry Table Configuration',
canView: function (selection) {
if (selection.length === 0) {
return false;
}
let object = selection[0].context.item;
return selection.length > 0 &&
object.type === 'table' &&
return object.type === 'table' &&
isBeingEdited(object);
},
view: function (selection) {
let component;
let domainObject = selection[0].context.item;
const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
return {
show: function (element) {
component = TableConfigurationComponent(domainObject, openmct);
element.appendChild(component.$mount().$el);
},
component = new Vue({
provide: {
openmct,
tableConfiguration
},
components: {
TableConfiguration: TableConfigurationComponent.default
},
template: '<table-configuration></table-configuration>',
el: element
});
},
destroy: function (element) {
component.$destroy();
element.removeChild(component.$el);
component = undefined;
}
}
@ -82,4 +98,4 @@ define([
}
}
return TableConfigurationViewProvider;
});
});

View File

@ -36,12 +36,12 @@ define([
TelemetryTableConfiguration
) {
class TelemetryTable extends EventEmitter {
constructor(domainObject, rowCount, openmct) {
constructor(domainObject, openmct) {
super();
this.domainObject = domainObject;
this.openmct = openmct;
this.rowCount = rowCount;
this.rowCount = 100;
this.subscriptions = {};
this.tableComposition = undefined;
this.telemetryObjects = [];
@ -85,10 +85,10 @@ define([
this.configuration.addColumnsForAllObjects(composition);
composition.forEach(this.addTelemetryObject);
this.tableComposition.on('add', this.addTelemetryObject);
this.tableComposition.on('remove', this.removeTelemetryObject);
});
});
}
}
@ -158,7 +158,7 @@ define([
getColumnMapForObject(objectKeyString) {
let columns = this.configuration.getColumns();
return columns[objectKeyString].reduce((map, column) => {
map[column.getKey()] = column;
return map;
@ -189,7 +189,7 @@ define([
this.filteredRows.destroy();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.openmct.time.off('bounds', this.refreshData);
if (this.tableComposition !== undefined) {
this.tableComposition.off('add', this.addTelemetryObject);
this.tableComposition.off('remove', this.removeTelemetryObject);
@ -198,4 +198,4 @@ define([
}
return TelemetryTable;
});
});

View File

@ -48,7 +48,12 @@ define(function () {
}
getFormattedValue(telemetryDatum) {
return this.formatter.format(telemetryDatum);
let formattedValue = this.formatter.format(telemetryDatum);
if (typeof formattedValue !== 'string') {
return formattedValue.toString();
} else {
return formattedValue;
}
}
};

View File

@ -1,315 +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([
'lodash',
'vue',
'./telemetry-table.html',
'./TelemetryTable',
'./TelemetryTableRowComponent',
'../../exporters/CSVExporter'
],function (
_,
Vue,
TelemetryTableTemplate,
TelemetryTable,
TelemetryTableRowComponent,
CSVExporter
) {
const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
return function TelemetryTableComponent(domainObject, openmct) {
const csvExporter = new CSVExporter();
const table = new TelemetryTable(domainObject, VISIBLE_ROW_COUNT, openmct);
let processingScroll = false;
let updatingView = false;
return new Vue({
template: TelemetryTableTemplate,
components: {
'telemetry-table-row': TelemetryTableRowComponent
},
data() {
return {
headers: {},
configuration: table.configuration.getConfiguration(),
headersCount: 0,
visibleRows: [],
columnWidths: [],
sizingRows: {},
rowHeight: ROW_HEIGHT,
scrollOffset: 0,
totalHeight: 0,
totalWidth: 0,
rowOffset: 0,
autoScroll: true,
sortOptions: {},
filters: {},
loading: false,
scrollable: undefined,
tableEl: undefined,
headersHolderEl: undefined,
calcTableWidth: '100%'
}
},
methods: {
updateVisibleRows() {
let start = 0;
let end = VISIBLE_ROW_COUNT;
let filteredRows = table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength;
} else {
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
let totalVisible = lastVisible - firstVisible;
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
} else if (end >= filteredRowsLength) {
end = filteredRowsLength;
start = end - VISIBLE_ROW_COUNT + 1;
}
}
this.rowOffset = start;
this.visibleRows = filteredRows.slice(start, end);
},
calculateFirstVisibleRow() {
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
},
calculateLastVisibleRow() {
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.floor(bottomScroll / this.rowHeight);
},
updateHeaders() {
let headers = table.configuration.getVisibleHeaders();
this.headers = headers;
this.headersCount = Object.values(headers).length;
Vue.nextTick().then(this.calculateColumnWidths);
},
setSizingTableWidth() {
let scrollW = this.scrollable.offsetWidth - this.scrollable.clientWidth;
if (scrollW && scrollW > 0) {
this.calcTableWidth = 'calc(100% - ' + scrollW + 'px)';
}
},
calculateColumnWidths() {
let columnWidths = [];
let totalWidth = 0;
let sizingRowEl = this.sizingTable.children[0];
let sizingCells = Array.from(sizingRowEl.children);
sizingCells.forEach((cell) => {
let columnWidth = cell.offsetWidth;
columnWidths.push(columnWidth + 'px');
totalWidth += columnWidth;
});
this.columnWidths = columnWidths;
this.totalWidth = totalWidth;
},
sortBy(columnKey) {
// If sorting by the same column, flip the sort direction.
if (this.sortOptions.key === columnKey) {
if (this.sortOptions.direction === 'asc') {
this.sortOptions.direction = 'desc';
} else {
this.sortOptions.direction = 'asc';
}
} else {
this.sortOptions = {
key: columnKey,
direction: 'asc'
}
}
table.filteredRows.sortBy(this.sortOptions);
},
scroll() {
if (!processingScroll) {
processingScroll = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
this.synchronizeScrollX();
if (this.shouldSnapToBottom()) {
this.autoScroll = true;
} else {
// If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = false;
}
processingScroll = false;
});
}
},
shouldSnapToBottom() {
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
},
scrollToBottom() {
this.scrollable.scrollTop = this.scrollable.scrollHeight;
},
synchronizeScrollX() {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
},
filterChanged(columnKey) {
table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
},
clearFilter(columnKey) {
this.filters[columnKey] = '';
table.filteredRows.setColumnFilter(columnKey, '');
},
rowsAdded(rows) {
let sizingRow;
if (Array.isArray(rows)) {
sizingRow = rows[0];
} else {
sizingRow = rows;
}
if (!this.sizingRows[sizingRow.objectKeyString]) {
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
Vue.nextTick().then(this.calculateColumnWidths);
}
if (!updatingView) {
updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
if (this.autoScroll) {
Vue.nextTick().then(this.scrollToBottom);
}
updatingView = false;
});
}
},
rowsRemoved(rows) {
if (!updatingView) {
updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
updatingView = false;
});
}
},
exportAsCSV() {
const justTheData = table.filteredRows.getRows()
.map(row => row.getFormattedDatum());
const headers = Object.keys(this.headers);
csvExporter.export(justTheData, {
filename: table.domainObject.name + '.csv',
headers: headers
});
},
outstandingRequests(loading) {
this.loading = loading;
},
calculateTableSize() {
this.setSizingTableWidth();
Vue.nextTick().then(this.calculateColumnWidths);
},
pollForResize() {
let el = this.$el;
let width = el.clientWidth;
let height = el.clientHeight;
this.resizePollHandle = setInterval(() => {
if (el.clientWidth !== width || el.clientHeight !== height) {
this.calculateTableSize();
width = el.clientWidth;
height = el.clientHeight;
}
}, RESIZE_POLL_INTERVAL);
},
updateConfiguration(configuration) {
this.configuration = configuration;
this.updateHeaders();
},
addObject() {
this.updateHeaders();
},
removeObject(objectIdentifier) {
let objectKeyString = openmct.objects.makeKeyString(objectIdentifier);
delete this.sizingRows[objectKeyString];
this.updateHeaders();
}
},
created() {
this.filterChanged = _.debounce(this.filterChanged, 500);
},
mounted() {
table.on('object-added', this.addObject);
table.on('object-removed', this.removeObject);
table.on('outstanding-requests', this.outstandingRequests);
table.filteredRows.on('add', this.rowsAdded);
table.filteredRows.on('remove', this.rowsRemoved);
table.filteredRows.on('sort', this.updateVisibleRows);
table.filteredRows.on('filter', this.updateVisibleRows);
//Default sort
this.sortOptions = table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.t-scrolling');
this.sizingTable = this.$el.querySelector('.js-sizing-table');
this.headersHolderEl = this.$el.querySelector('.mct-table-headers-w');
table.configuration.on('change', this.updateConfiguration);
this.calculateTableSize();
this.pollForResize();
table.initialize();
},
destroyed() {
table.off('object-added', this.addObject);
table.off('object-removed', this.removeObject);
table.off('outstanding-requests', this.outstandingRequests);
table.filteredRows.off('add', this.rowsAdded);
table.filteredRows.off('remove', this.rowsRemoved);
table.filteredRows.off('sort', this.updateVisibleRows);
table.filteredRows.off('filter', this.updateVisibleRows);
table.configuration.off('change', this.updateConfiguration);
clearInterval(this.resizePollHandle);
table.configuration.destroy();
table.destroy();
}
});
}
});

View File

@ -30,17 +30,16 @@ define([], function () {
this.objectKeyString = objectKeyString;
}
getFormattedDatum() {
return Object.values(this.columns)
.reduce((formattedDatum, column) => {
formattedDatum[column.getKey()] = this.getFormattedValue(column.getKey());
return formattedDatum;
}, {});
getFormattedDatum(headers) {
return Object.keys(headers).reduce((formattedDatum, columnKey) => {
formattedDatum[columnKey] = this.getFormattedValue(columnKey);
return formattedDatum;
}, {});
}
getFormattedValue(key) {
let column = this.columns[key];
return column.getFormattedValue(this.datum[key]);
return column && column.getFormattedValue(this.datum[key]);
}
getRowLimitClass() {

View File

@ -1,90 +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([
'./telemetry-table-row.html',
],function (
TelemetryTableRowTemplate
) {
return {
template: TelemetryTableRowTemplate,
data: function () {
return {
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
formattedRow: this.row.getFormattedDatum(),
rowLimitClass: this.row.getRowLimitClass(),
cellLimitClasses: this.row.getCellLimitClasses()
}
},
props: {
headers: {
type: Object,
required: true
},
row: {
type: Object,
required: true
},
columnWidths: {
type: Array,
required: false,
default: [],
},
rowIndex: {
type: Number,
required: false,
default: undefined
},
rowOffset: {
type: Number,
required: false,
default: 0
},
rowHeight: {
type: Number,
required: false,
default: 0
},
configuration: {
type: Object,
required: true
}
},
methods: {
calculateRowTop: function (rowOffset) {
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
},
formatRow: function (row) {
this.formattedRow = row.getFormattedDatum();
this.rowLimitClass = row.getRowLimitClass();
this.cellLimitClasses = row.getCellLimitClasses();
}
},
watch: {
rowOffset: 'calculateRowTop',
row: {
handler: 'formatRow',
deep: false
}
}
};
});

View File

@ -20,25 +20,49 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./TelemetryTableComponent'], function (TelemetryTableComponent) {
define([
'./components/table.vue',
'../../exporters/CSVExporter',
'./TelemetryTable',
'vue'
], function (
TableComponent,
CSVExporter,
TelemetryTable,
Vue
) {
function TelemetryTableViewProvider(openmct) {
return {
key: 'table',
name: 'Telemetry Table',
editable: true,
cssClass: 'icon-tabular-realtime',
editable: function(domainObject) {
return domainObject.type === 'table';
},
canView: function (domainObject) {
return domainObject.type === 'table' || domainObject.hasOwnProperty('telemetry');
},
view: function (domainObject) {
let csvExporter = new CSVExporter.default();
let table = new TelemetryTable(domainObject, openmct);
let component;
return {
show: function (element) {
component = new TelemetryTableComponent(domainObject, openmct);
element.appendChild(component.$mount().$el);
},
component = new Vue({
components: {
TableComponent: TableComponent.default,
},
provide: {
openmct,
csvExporter,
table
},
el: element,
template: '<table-component></table-component>'
});
},
destroy: function (element) {
component.$destroy();
element.removeChild(component.$el);
component = undefined;
}
}
@ -49,4 +73,4 @@ define(['./TelemetryTableComponent'], function (TelemetryTableComponent) {
}
}
return TelemetryTableViewProvider;
});
});

View File

@ -82,8 +82,7 @@ define(
// Going to check for duplicates. Bound the search problem to
// items around the given time. Use sortedIndex because it
// employs a binary search which is O(log n). Can use binary search
// based on time stamp because the array is guaranteed ordered due
// to sorted insertion.
// because the array is guaranteed ordered due to sorted insertion.
let startIx = this.sortedIndex(this.rows, row);
let endIx = undefined;
@ -113,26 +112,49 @@ define(
* @private
*/
sortedIndex(rows, testRow, lodashFunction) {
if (this.rows.length === 0) {
return 0;
}
const sortOptionsKey = this.sortOptions.key;
const testRowValue = testRow.datum[sortOptionsKey];
const firstValue = this.rows[0].datum[sortOptionsKey];
const lastValue = this.rows[this.rows.length - 1].datum[sortOptionsKey];
lodashFunction = lodashFunction || _.sortedIndex;
if (this.sortOptions.direction === 'asc') {
return lodashFunction(rows, testRow, (thisRow) => {
return thisRow.datum[sortOptionsKey];
});
if (testRowValue > lastValue) {
return this.rows.length;
} else if (testRowValue === lastValue) {
return this.rows.length - 1;
} else if (testRowValue <= firstValue) {
return 0;
} else {
return lodashFunction(rows, testRow, (thisRow) => {
return thisRow.datum[sortOptionsKey];
});
}
} else {
const testRowValue = testRow.datum[this.sortOptions.key];
// Use a custom comparison function to support descending sort.
return lodashFunction(rows, testRow, (thisRow) => {
const thisRowValue = thisRow.datum[sortOptionsKey];
if (testRowValue === thisRowValue) {
return EQUAL;
} else if (testRowValue < thisRowValue) {
return LESS_THAN;
} else {
return GREATER_THAN;
}
});
if (testRowValue >= firstValue) {
return 0;
} else if (testRowValue < lastValue) {
return this.rows.length;
} else if (testRowValue === lastValue) {
return this.rows.length - 1;
} else {
// Use a custom comparison function to support descending sort.
return lodashFunction(rows, testRow, (thisRow) => {
const thisRowValue = thisRow.datum[sortOptionsKey];
if (testRowValue === thisRowValue) {
return EQUAL;
} else if (testRowValue < thisRowValue) {
return LESS_THAN;
} else {
return GREATER_THAN;
}
});
}
}
}

View File

@ -0,0 +1,68 @@
<template>
<div class="grid-properties">
<!--form class="form" -->
<ul class="l-inspector-part">
<h2>Table Columns</h2>
<li class="grid-row" v-for="(title, key) in headers">
<div class="grid-cell label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
<div class="grid-cell value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
</li>
</ul>
<!--/form -->
</div>
</template>
<style>
</style>
<script>
export default {
inject: ['tableConfiguration', 'openmct'],
data() {
return {
headers: {},
configuration: this.tableConfiguration.getConfiguration()
}
},
methods: {
updateHeaders(headers) {
this.headers = headers;
},
toggleColumn(key) {
let isHidden = this.configuration.hiddenColumns[key] === true;
this.configuration.hiddenColumns[key] = !isHidden;
this.tableConfiguration.updateConfiguration(this.configuration);
},
addObject(domainObject) {
this.tableConfiguration.addColumnsForObject(domainObject, true);
this.updateHeaders(this.tableConfiguration.getAllHeaders());
},
removeObject(objectIdentifier) {
this.tableConfiguration.removeColumnsForObject(objectIdentifier, true);
this.updateHeaders(this.tableConfiguration.getAllHeaders());
}
},
mounted() {
this.unlisteners = [];
let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject);
compositionCollection.load()
.then((composition) => {
this.tableConfiguration.addColumnsForAllObjects(composition);
this.updateHeaders(this.tableConfiguration.getAllHeaders());
compositionCollection.on('add', this.addObject);
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
compositionCollection.on('remove', this.removeObject);
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
});
},
destroyed() {
this.tableConfiguration.destroy();
this.unlisteners.forEach((unlisten) => unlisten());
}
}
</script>

View File

@ -0,0 +1,74 @@
<template>
<tr :style="{ top: rowTop }" :class="rowLimitClass">
<td v-for="(title, key, headerIndex) in headers"
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}"
:title="formattedRow[key]"
:class="cellLimitClasses[key]">{{formattedRow[key]}}</td>
</tr>
</template>
<style>
</style>
<script>
export default {
data: function () {
return {
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
formattedRow: this.row.getFormattedDatum(this.headers),
rowLimitClass: this.row.getRowLimitClass(),
cellLimitClasses: this.row.getCellLimitClasses()
}
},
props: {
headers: {
type: Object,
required: true
},
row: {
type: Object,
required: true
},
columnWidths: {
type: Array,
required: false,
default() {
return [];
},
},
rowIndex: {
type: Number,
required: false,
default: undefined
},
rowOffset: {
type: Number,
required: false,
default: 0
},
rowHeight: {
type: Number,
required: false,
default: 0
}
},
methods: {
calculateRowTop: function (rowOffset) {
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
},
formatRow: function (row) {
this.formattedRow = row.getFormattedDatum(this.headers);
this.rowLimitClass = row.getRowLimitClass();
this.cellLimitClasses = row.getCellLimitClasses();
}
},
// TODO: use computed properties
watch: {
rowOffset: 'calculateRowTop',
row: {
handler: 'formatRow',
deep: false
}
}
}
</script>

View File

@ -0,0 +1,454 @@
<template>
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
:class="{'loading': loading}">
<div class="c-table__control-bar c-control-bar">
<a class="s-button t-export icon-download labeled"
v-on:click="exportAsCSV()"
title="Export This View's Data">
Export As CSV
</a>
</div>
<!-- Headers table -->
<div class="c-telemetry-table__headers-w js-table__headers-w">
<table class="c-table__headers c-telemetry-table__headers"
:style="{ 'max-width': totalWidth + 'px'}">
<thead>
<tr>
<th v-for="(title, key, headerIndex) in headers"
v-on:click="sortBy(key)"
:class="['is-sortable', sortOptions.key === key ? 'is-sorting' : '', sortOptions.direction].join(' ')"
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}">{{title}}</th>
</tr>
<tr>
<th v-for="(title, key, headerIndex) in headers"
:style="{
width: columnWidths[headerIndex],
'max-width': columnWidths[headerIndex],
}">
<search class="c-table__search"
v-model="filters[key]"
v-on:input="filterChanged(key)"
v-on:clear="clearFilter(key)" />
</th>
</tr>
</thead>
</table>
</div>
<!-- Content table -->
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll">
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth }"></div>
<table class="c-table__body c-telemetry-table__body"
:style="{ height: totalHeight + 'px', 'max-width': totalWidth + 'px'}">
<tbody>
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
:headers="headers"
:columnWidths="columnWidths"
:rowIndex="rowIndex"
:rowOffset="rowOffset"
:rowHeight="rowHeight"
:row="row"
>
</telemetry-table-row>
</tbody>
</table>
</div>
<!-- Sizing table -->
<table class="c-telemetry-table__sizing js-telemetry-table__sizing"
:style="{width: calcTableWidth}">
<tr>
<th v-for="(title, key, headerIndex) in headers">{{title}}</th>
</tr>
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
:headers="headers"
:row="sizingRowData">
</telemetry-table-row>
</table>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
@import "~styles/table";
.c-telemetry-table {
// Table that displays telemetry in a scrolling body area
overflow: hidden;
th, td {
display: block;
flex: 1 0 auto;
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default
}
/******************************* WRAPPERS */
&__headers-w {
// Wraps __headers table
flex: 0 0 auto;
overflow: hidden;
}
/******************************* TABLES */
&__headers,
&__body {
tr {
display: flex;
align-items: stretch;
}
}
&__headers {
// A table
thead {
display: block;
}
th {
&:not(:first-child) {
border-left: 1px solid $colorTabHeaderBorder;
}
}
}
/******************************* ELEMENTS */
&__scroll-forcer {
// Force horz scroll when needed; width set via JS
font-size: 0;
height: 1px; // Height 0 won't force scroll properly
position: relative;
}
/******************************* WRAPPERS */
&__body-w {
// Wraps __body table provides scrolling
flex: 1 1 100%;
overflow-x: auto;
overflow-y: scroll;
}
/******************************* TABLES */
&__body {
// A table
flex: 1 1 100%;
overflow-x: auto;
tr {
display: flex; // flex-flow defaults to row nowrap (which is what we want) so no need to define
align-items: stretch;
position: absolute;
height: 18px; // Needed when a row has empty values in its cells
}
td {
overflow: hidden;
text-overflow: ellipsis;
}
}
&__sizing {
// A table
display: table;
z-index: -1;
visibility: hidden;
pointer-events: none;
position: absolute;
//Add some padding to allow for decorations such as limits indicator
tr {
display: table-row;
}
th, td {
display: table-cell;
padding-right: 10px;
padding-left: 10px;
white-space: nowrap;
}
}
}
/******************************* LEGACY */
.s-status-taking-snapshot,
.overlay.snapshot {
// Handle overflow-y issues with tables and html2canvas
// Replaces .l-sticky-headers .l-tabular-body { overflow: auto; }
.c-table__body-w { overflow: auto; }
}
</style>
<script>
import TelemetryTableRow from './table-row.vue';
import search from '../../../ui/components/controls/search.vue';
import _ from 'lodash';
const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
export default {
components: {
TelemetryTableRow,
search
},
inject: ['table', 'openmct', 'csvExporter'],
props: ['configuration'],
data() {
return {
headers: {},
visibleRows: [],
columnWidths: [],
sizingRows: {},
rowHeight: ROW_HEIGHT,
scrollOffset: 0,
totalHeight: 0,
totalWidth: 0,
rowOffset: 0,
autoScroll: true,
sortOptions: {},
filters: {},
loading: false,
scrollable: undefined,
tableEl: undefined,
headersHolderEl: undefined,
calcTableWidth: '100%',
processingScroll: false,
updatingView: false
}
},
methods: {
updateVisibleRows() {
let start = 0;
let end = VISIBLE_ROW_COUNT;
let filteredRows = this.table.filteredRows.getRows();
let filteredRowsLength = filteredRows.length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
end = filteredRowsLength;
} else {
let firstVisible = this.calculateFirstVisibleRow();
let lastVisible = this.calculateLastVisibleRow();
let totalVisible = lastVisible - firstVisible;
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
} else if (end >= filteredRowsLength) {
end = filteredRowsLength;
start = end - VISIBLE_ROW_COUNT + 1;
}
}
this.rowOffset = start;
this.visibleRows = filteredRows.slice(start, end);
},
calculateFirstVisibleRow() {
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
},
calculateLastVisibleRow() {
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
return Math.floor(bottomScroll / this.rowHeight);
},
updateHeaders() {
let headers = this.table.configuration.getVisibleHeaders();
this.headers = headers;
this.$nextTick().then(this.calculateColumnWidths);
},
setSizingTableWidth() {
let scrollW = this.scrollable.offsetWidth - this.scrollable.clientWidth;
if (scrollW && scrollW > 0) {
this.calcTableWidth = 'calc(100% - ' + scrollW + 'px)';
}
},
calculateColumnWidths() {
let columnWidths = [];
let totalWidth = 0;
let sizingRowEl = this.sizingTable.children[0];
let sizingCells = Array.from(sizingRowEl.children);
sizingCells.forEach((cell) => {
let columnWidth = cell.offsetWidth;
columnWidths.push(columnWidth + 'px');
totalWidth += columnWidth;
});
this.columnWidths = columnWidths;
this.totalWidth = totalWidth;
},
sortBy(columnKey) {
// If sorting by the same column, flip the sort direction.
if (this.sortOptions.key === columnKey) {
if (this.sortOptions.direction === 'asc') {
this.sortOptions.direction = 'desc';
} else {
this.sortOptions.direction = 'asc';
}
} else {
this.sortOptions = {
key: columnKey,
direction: 'asc'
}
}
this.table.filteredRows.sortBy(this.sortOptions);
},
scroll() {
if (!this.processingScroll) {
this.processingScroll = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
this.synchronizeScrollX();
if (this.shouldSnapToBottom()) {
this.autoScroll = true;
} else {
// If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again.
this.autoScroll = false;
}
this.processingScroll = false;
});
}
},
shouldSnapToBottom() {
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
},
scrollToBottom() {
this.scrollable.scrollTop = this.scrollable.scrollHeight;
},
synchronizeScrollX() {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
},
filterChanged(columnKey) {
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
},
clearFilter(columnKey) {
this.filters[columnKey] = '';
this.table.filteredRows.setColumnFilter(columnKey, '');
},
rowsAdded(rows) {
let sizingRow;
if (Array.isArray(rows)) {
sizingRow = rows[0];
} else {
sizingRow = rows;
}
if (!this.sizingRows[sizingRow.objectKeyString]) {
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
this.$nextTick().then(this.calculateColumnWidths);
}
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
if (this.autoScroll) {
this.$nextTick().then(this.scrollToBottom);
}
this.updatingView = false;
});
}
},
rowsRemoved(rows) {
if (!this.updatingView) {
this.updatingView = true;
requestAnimationFrame(()=> {
this.updateVisibleRows();
this.updatingView = false;
});
}
},
exportAsCSV() {
const headerKeys = Object.keys(this.headers);
const justTheData = this.table.filteredRows.getRows()
.map(row => row.getFormattedDatum(this.headers));
this.csvExporter.export(justTheData, {
filename: this.table.domainObject.name + '.csv',
headers: headerKeys
});
},
outstandingRequests(loading) {
this.loading = loading;
},
calculateTableSize() {
this.setSizingTableWidth();
this.$nextTick().then(this.calculateColumnWidths);
},
pollForResize() {
let el = this.$el;
let width = el.clientWidth;
let height = el.clientHeight;
this.resizePollHandle = setInterval(() => {
if (el.clientWidth !== width || el.clientHeight !== height) {
this.calculateTableSize();
width = el.clientWidth;
height = el.clientHeight;
}
}, RESIZE_POLL_INTERVAL);
},
updateConfiguration(configuration) {
this.configuration = configuration;
this.updateHeaders();
},
addObject() {
this.updateHeaders();
},
removeObject(objectIdentifier) {
let objectKeyString = this.openmct.objects.makeKeyString(objectIdentifier);
delete this.sizingRows[objectKeyString];
this.updateHeaders();
}
},
created() {
this.filterChanged = _.debounce(this.filterChanged, 500);
},
mounted() {
this.table.on('object-added', this.addObject);
this.table.on('object-removed', this.removeObject);
this.table.on('outstanding-requests', this.outstandingRequests);
this.table.filteredRows.on('add', this.rowsAdded);
this.table.filteredRows.on('remove', this.rowsRemoved);
this.table.filteredRows.on('sort', this.updateVisibleRows);
this.table.filteredRows.on('filter', this.updateVisibleRows);
//Default sort
this.sortOptions = this.table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
this.table.configuration.on('change', this.updateConfiguration);
this.calculateTableSize();
this.pollForResize();
this.table.initialize();
},
destroyed() {
this.table.off('object-added', this.addObject);
this.table.off('object-removed', this.removeObject);
this.table.off('outstanding-requests', this.outstandingRequests);
this.table.filteredRows.off('add', this.rowsAdded);
this.table.filteredRows.off('remove', this.rowsRemoved);
this.table.filteredRows.off('sort', this.updateVisibleRows);
this.table.filteredRows.off('filter', this.updateVisibleRows);
this.table.configuration.off('change', this.updateConfiguration);
clearInterval(this.resizePollHandle);
this.table.configuration.destroy();
this.table.destroy();
}
}
</script>

Some files were not shown because too many files have changed in this diff Show More