Compare commits

...

13 Commits

Author SHA1 Message Date
d2e064dc52 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:38:15 -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
66 changed files with 2380 additions and 1728 deletions

21
copyright-notice.html Normal file
View File

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

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.
*
@ -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.
*****************************************************************************/
/**
* 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

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

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

@ -35,8 +35,10 @@ define([
'./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 (
@ -54,8 +56,10 @@ define([
InspectorViewRegistry,
ToolbarRegistry,
ApplicationRouter,
Browse,
Main,
coreStyles,
NotebookStyles,
Layout,
Vue
) {
@ -273,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();
@ -280,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');
@ -324,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: {
@ -334,7 +317,8 @@ define([
});
domElement.appendChild(appLayout.$mount().$el);
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,93 @@
define([
], function (
) {
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();
}
}
}
};
};
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

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

@ -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,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">
<a class="c-icon-button icon-trash"
title="Delete this entry"
v-on:click="deleteEntry"></a>
</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,14 @@ 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'
], function (
_,
UTCTimeSystem,
@ -42,19 +42,18 @@ define([
AutoflowPlugin,
TimeConductorPlugin,
ExampleImagery,
Notebook,
ImportExport,
SummaryWidget,
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
TelemetryTablePlugin,
StaticRootPlugin
StaticRootPlugin,
Notebook
) {
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) {
@ -155,10 +154,11 @@ define([
plugins.ExampleImagery = ExampleImagery;
plugins.Plot = PlotPlugin;
plugins.TelemetryTable = TelemetryTablePlugin;
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin;
plugins.Notebook = Notebook;
return plugins;
});

View File

@ -15,7 +15,8 @@ define([
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';
},

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

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

@ -35,12 +35,15 @@ define([
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();
let csvExporter = new CSVExporter.default();
let table = new TelemetryTable(domainObject, openmct);
let component;
return {

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

@ -15,7 +15,7 @@ export default {
data: function () {
return {
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
formattedRow: this.row.getFormattedDatum(),
formattedRow: this.row.getFormattedDatum(this.headers),
rowLimitClass: this.row.getRowLimitClass(),
cellLimitClasses: this.row.getCellLimitClasses()
}
@ -32,7 +32,9 @@ export default {
columnWidths: {
type: Array,
required: false,
default: [],
default() {
return [];
},
},
rowIndex: {
type: Number,
@ -48,10 +50,6 @@ export default {
type: Number,
required: false,
default: 0
},
configuration: {
type: Object,
required: true
}
},
methods: {
@ -59,7 +57,7 @@ export default {
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
},
formatRow: function (row) {
this.formattedRow = row.getFormattedDatum();
this.formattedRow = row.getFormattedDatum(this.headers);
this.rowLimitClass = row.getRowLimitClass();
this.cellLimitClasses = row.getCellLimitClasses();
}

View File

@ -19,16 +19,16 @@
:class="['is-sortable', sortOptions.key === key ? 'is-sorting' : '', sortOptions.direction].join(' ')"
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}">{{title}}</th>
</tr>
<tr class="s-filters">
<tr>
<th v-for="(title, key, headerIndex) in headers"
:style="{
width: columnWidths[headerIndex],
'max-width': columnWidths[headerIndex],
}">
<div class="holder l-filter flex-elem grows" :class="{active: filters[key]}">
<input type="text" v-model="filters[key]" v-on:input="filterChanged(key)" />
<a class="clear-icon clear-input icon-x-in-circle" :class="{show: filters[key]}" @click="clearFilter(key)"></a>
</div>
<search class="c-table__search"
v-model="filters[key]"
v-on:input="filterChanged(key)"
v-on:clear="clearFilter(key)" />
</th>
</tr>
</thead>
@ -266,6 +266,7 @@
<script>
import TelemetryTableRow from './table-row.vue';
import search from '../../../ui/components/controls/search.vue';
import _ from 'lodash';
const VISIBLE_ROW_COUNT = 100;
@ -275,14 +276,14 @@ const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
export default {
components: {
TelemetryTableRow
TelemetryTableRow,
search
},
inject: ['table', 'openmct', 'csvExporter'],
props: ['configuration'],
data() {
return {
headers: {},
headersCount: 0,
visibleRows: [],
columnWidths: [],
sizingRows: {},
@ -346,7 +347,6 @@ export default {
let headers = this.table.configuration.getVisibleHeaders();
this.headers = headers;
this.headersCount = Object.values(headers).length;
this.$nextTick().then(this.calculateColumnWidths);
},
setSizingTableWidth() {
@ -454,12 +454,12 @@ export default {
}
},
exportAsCSV() {
const headerKeys = Object.keys(this.headers);
const justTheData = this.table.filteredRows.getRows()
.map(row => row.getFormattedDatum());
const headers = Object.keys(this.headers);
.map(row => row.getFormattedDatum(this.headers));
this.csvExporter.export(justTheData, {
filename: this.table.domainObject.name + '.csv',
headers: headers
headers: headerKeys
});
},
outstandingRequests(loading) {

View File

@ -1,6 +1,6 @@
@import "constants";
// Mixins
// Functions
@function pullForward($c: $colorBodyBg, $p: 20%) {
// For dark interfaces, lighter things come forward - opposite for light interfaces
@return darken($c, $p);
@ -12,7 +12,7 @@
}
// Global
$fontBaseSize: 13px;
$fontBaseSize: 12px;
$colorBodyBg: #fcfcfc;
$colorBodyFg: #666;
$colorGenBg: #fff;
@ -71,6 +71,7 @@ $sliderKnobR: 2px;
$timeControllerToiLineColor: $colorBodyFg;
$timeControllerToiLineColorHov: #0052b5;
$colorTransLucBg: #666; // Used as a visual blocking element over variable backgrounds, like imagery
$createBtnTextTransform: uppercase;
// Foundation Colors
$colorAlt1: #776ba2;
@ -293,3 +294,16 @@ $colorLoadingBg: rgba($colorLoadingFg, 0.1);
// Transitions
$transIn: all 50ms ease-in;
$transOut: all 250ms ease-out;
// Discrete items, like Notebook entries, Widget rules
@mixin discreteItem() {
background: rgba($colorBodyFg,0.1);
border: 1px solid $colorInteriorBorder;
border-radius: $controlCr;
.c-input-inline:hover {
background: $colorBodyBg;
}
}
@mixin discreteItemInnerElem() {
border: 1px solid $colorBodyBg;
border-radius: $controlCr; }

View File

@ -1,8 +1,10 @@
/**************************** BUTTONS */
/******************************************************** BUTTONS */
%c-control {
@include userSelectNone();
$fs: 1em;
display: inline-flex;
align-items: center;
font-size: $fs;
justify-content: start;
cursor: pointer;
@ -13,12 +15,13 @@
flex: 0 0 auto;
}
&:before {
&:after {
font-size: 0.8em;
}
&:after {
font-size: 0.6em;
[class*="__label"] {
line-height: $fs; // Remove effect on top and bottom padding
font-size: $fs;
}
}
@ -47,8 +50,9 @@
}
/********* Buttons */
// Optionally can include icon in :before
.c-button {
// Optionally can include icon in :before via markup
.c-button,
button {
@extend %c-button;
}
@ -56,10 +60,14 @@
.c-icon-button {
// A clickable element that just includes the icon, no background
// Padding is included to facilitate a bigger hit area
// Make the icon bigger relative to its container
@extend %c-control;
$pLR: 3px;
$pTB: 3px;
border-radius: $controlCr;
color: $colorKey;
padding: nth($btnPad, 1) floor(nth($btnPad, 2) * 0.8);
font-size: $fontBaseSize * 1.2;
padding: $pTB $pLR ;
&:hover {
background: rgba($colorKey, 0.2);
@ -77,7 +85,7 @@
// c-icon-buttons get grouped more closely together
// When one set is adjacent to another, provides a divider between
display: flex;
display: inline-flex;
> * {
// Assume buttons are immediate descendants
@ -118,6 +126,8 @@
/********* Menu Buttons */
// Always includes :after dropdown arrow
// Optionally can include icon in :before
// Default menu position is down and to the right
// Apply c-menu-button--menus-up and c-menu-button--menus-left to --w wrapper element to modify menu position
.c-menu-button {
$m: $interiorMarginSm;
@extend %c-button;
@ -132,9 +142,106 @@
margin-left: $m;
opacity: 0.5;
}
&--w {
// Wraps c-menu-button, contains button and menu
.c-menu {
// Default position
top: 100%; left: 0;
}
&.c-menu-button--menus-up {
.c-menu {
top: auto; bottom: 100%;
}
}
&.c-menu-button--menus-left {
.c-menu {
left: auto; right: 0;
}
}
}
}
/**************************** MENUS */
/******************************************************** DISCLOSURE CONTROLS */
/********* Disclosure Button */
// Provides a downward arrow icon that when clicked displays a context menu
// Always placed AFTER an element
.c-disclosure-button {
@extend .c-icon-button;
margin-left: $interiorMarginSm;
&:before {
content: $glyph-icon-arrow-down;
font-family: symbolsfont;
font-size: 0.7em;
}
}
/********* Disclosure Triangle */
// Provides an arrow icon that when clicked expands an element to reveal its contents.
// Used in tree items. Always placed BEFORE an element.
.c-disclosure-triangle {
$d: 12px;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: $d;
position: relative;
&.is-enabled:before {
$s: .65;
content: $glyph-icon-arrow-right-equilateral;
display: block;
font-family: symbolsfont;
font-size: 1rem * $s;
position: absolute;
transform-origin: floor(($d / 2) * $s); // This is slightly better than 'center'
transition: transform 100ms ease-in-out;
}
&--expanded {
&:before {
transform: rotate(90deg);
}
}
}
/******************************************************** FORM ELEMENTS */
/********* Inline inputs */
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
.c-input-inline {
@include input-base();
border: 1px solid transparent;
display: block !important;
min-width: 0;
padding-left: 0;
padding-right: 0;
overflow: hidden;
transition: all 250ms ease;
white-space: nowrap;
&:not(:focus) {
text-overflow: ellipsis;
}
&:hover,
&:focus {
padding-left: $inputTextPLeftRight;
padding-right: $inputTextPLeftRight;
}
&:hover {
border-color: rgba($colorBodyFg, 0.2);
}
&:focus {
@include nice-input($shdw: rgba(0, 0, 0, 0.6) 0 1px 3px);
border-color: transparent;
}
}
/******************************************************** MENUS */
@mixin menuOuter() {
border-radius: $basicCr;
background: $colorMenuBg;
@ -171,8 +278,6 @@
}
}
.c-menu {
@include menuOuter();
@include menuInner();

View File

@ -43,6 +43,15 @@ div {
display: contents;
}
.u-space {
// Provides a separator space between elements
&--right {
+ [class*="__"] {
margin-left: $interiorMarginLg !important;
}
}
}
/******************************* BROWSER ELEMENTS */
body.desktop {
::-webkit-scrollbar {
@ -160,6 +169,48 @@ li {
padding: 0;
}
/******************************************************** HAS */
// Local Controls: Controls placed in proximity to or overlaid on components and views
body.desktop .has-local-controls {
// Provides hover ability to show local controls
&:hover [class*="local-controls--hidden"] {
transition: opacity 50ms ease-in-out;
opacity: 1;
pointer-events: inherit;
}
[class*="local-controls--hidden"] {
transition: opacity 500ms ease-in-out;
opacity: 0;
pointer-events: none;
}
}
//[class*="local-controls"] {
// // An explicit outer holder for controls. Typically placed in upper right.
// //font-size: 0.7rem;
// display: flex;
// align-items: center;
// justify-content: flex-end;
//
//
// &.h-local-controls-overlay-content {
// // Imagery controls
// $p: $interiorMargin;
// position: absolute;
// top: $p; right: $p;
// z-index: 2;
// }
//.l-btn-set,
//.s-button {
// &:not(:first-child) {
// margin-left: $interiorMargin;
// }
//}
//}
/************************** LEGACY */
mct-container {

View File

@ -110,13 +110,10 @@
}
/************************** CONTROLS, BUTTONS */
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0px 2px) {
@mixin input-base() {
appearance: none;
background: $bg;
border: none;
color: $fg;
border-radius: $controlCr;
box-shadow: inset $shdw;
outline: none;
&:focus {
@ -129,6 +126,15 @@
}
}
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0px 2px) {
@include input-base();
background: $bg;
color: $fg;
box-shadow: inset $shdw;
}
@mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) {
background: $bg;
color: $fg;

View File

@ -64,7 +64,7 @@
@import "../styles/items/item";
@import "../styles/mobile/item";
@import "../styles/table";
@import "../styles/notebook/notebook";
//@import "../styles/notebook/notebook";
//
//!********************************* TO BE MOVED *!
@import "../styles/autoflow";
@ -75,4 +75,5 @@
//!********************************* APP STARTUP *!
//@import "../styles/app-start";
@import "../styles/conductor/time-conductor-snow";
@import "../styles/conductor/time-conductor-snow";
//@import "../styles/notebook/notebook-snow";

View File

@ -0,0 +1,385 @@
/*********************************************** NOTEBOOK */
@import "sass-base.scss";
.c-notebook {
//@include test(orange);
display: flex;
flex-direction: column;
overflow: hidden;
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
> [class*="__"] + [class*="__"] {
margin-top: $interiorMargin;
}
&__head,
&__controls,
&__drag-area,
&__entries {
display: flex;
flex-wrap: nowrap;
}
&__head,
&__drag-area,
&__controls {
flex: 0 0 auto;
}
&__head {
[class*="__"] + [class*="__"] {
margin-left: $interiorMargin;
}
}
&__search {
flex: 1 1 auto;
}
&__drag-area {
background: rgba($colorKey, 0.1);
border: 1px dashed rgba($colorKey, 0.7);
border-radius: $controlCr;
color: rgba($colorBodyFg, 0.7);
padding: 10px;
cursor: pointer;
&:before {
margin-right: 7px !important;
}
[class*="__label"] {
font-style: italic;
@include ellipsize();
}
&:hover {
background: rgba($colorKey, 0.2);
color: $colorBodyFg;
}
&.drag-active,
&.is-active {
// Not currently working
border-color: $colorKey;
}
body.mobile & {
display: none;
}
}
&__entries {
flex-direction: column;
flex: 1 1 auto;
padding-right: $interiorMarginSm;
overflow-x: hidden;
overflow-y: scroll;
[class*="__entry"] + [class*="__entry"] {
margin-top: $interiorMarginSm;
}
}
}
/****************************** ENTRIES */
.c-ne {
// A Notebook entry
$p: $interiorMarginSm;
@include discreteItem();
display: flex;
//flex-wrap: wrap;
padding: $interiorMarginSm $interiorMarginSm $interiorMarginSm $interiorMargin;
&__time,
&__text,
&__local-controls {
padding-top: $p;
padding-bottom: $p;
}
&__time,
&__embed__time {
opacity: 0.7;
}
&__time-and-content {
display: flex;
flex: 1 1 auto;
flex-wrap: wrap;
}
&__time {
flex: 0 1 auto;
min-width: 130px;
margin-right: $interiorMarginLg;
* {
white-space: nowrap;
}
}
&__content {
flex: 1 1 auto;
> [class*="__"] + [class*="__"] {
margin-top: $interiorMarginSm;
}
}
&__text {
min-height: 24px; // Needed in Firefox when field is blank
white-space: pre-wrap;
&.is-blank-notebook-entry {
&:not(:focus):before {
content: 'Blank entry';
font-style: italic;
opacity: 0.5;
}
}
}
&__embeds {
flex-wrap: wrap;
> [class*="__embed"] {
margin: 0 $interiorMarginSm $interiorMarginSm 0;
}
}
}
/****************************** EMBEDS */
@mixin snapThumb() {
// LEGACY: TODO: refactor when .snap-thumb in New Entry dialog is refactored
$d: 50px;
border: 1px solid $colorInteriorBorder;
cursor: pointer;
width: $d;
height: $d;
border-radius: 5px;
overflow: hidden;
img {
height: 100%;
width: 100%;
}
}
.snap-thumb {
// LEGACY,
@include snapThumb();
}
.c-ne__embed {
@include discreteItemInnerElem();
display: inline-flex;
flex: 0 0 auto;
padding: $interiorMargin;
[class*="__"] + [class*="__"] {
margin-left: $interiorMargin;
}
&__info {
display: flex;
flex-direction: column;
a {
color: $colorKey;
}
}
&__name,
&__link {
// Holds __link and __context-available
display: flex;
align-items: center;
}
&__link {
&:before {
display: block;
font-size: 0.85em;
margin-right: $interiorMarginSm;
}
}
&__context-available {
font-size: 0.7em;
margin-left: $interiorMarginSm;
}
&__snap-thumb {
@include snapThumb();
}
}
/****************************** SNAPSHOTTING */
// LEGACY: TODO: refactor these names
.t-contents,
.snap-annotation {
overflow: hidden;
}
.s-status-taking-snapshot,
.overlay.snapshot {
// Handle overflow-y issues with tables and html2canvas
.l-sticky-headers .l-tabular-body { overflow: auto; }
}
/****************************** PAINTERRO OVERRIDES */
.annotation-dialog .abs.editor {
border-radius: 0;
}
#snap-annotation {
$m: $interiorMargin;
display: flex;
flex-direction: column;
position: absolute;
top: $m; right: 0; bottom: $m; left: 0; // LEGACY, deal with .editor border-radius clipping stuff
}
#snap-annotation-wrapper,
#snap-annotation-bar {
position: relative;
top: auto; right: auto; bottom: auto; left: auto;
}
#snap-annotation-wrapper {
background: rgba($colorBodyFg, 0.1);
border: 1px solid $colorInteriorBorder;
order: 2;
flex: 10 0 auto;
}
#snap-annotation-bar {
// Holds tool buttons, color selectors, etc.
$h: 22px;
$fs: 0.8rem;
order: 1;
flex: 0 0 auto;
height: auto;
background-color: transparent !important;
margin-bottom: $interiorMargin;
> div > span {
display: flex;
align-items: center;
font-size: $fs;
}
> div,
> div > span,
.ptro-icon-btn,
.ptro-named-btn,
.ptro-color-btn,
.ptro-bordered-btn,
.ptro-tool-ctl-name,
.ptro-color-btn,
.tool-controls,
.ptro-input {
// Lot of resets for crappy CSS in Painterro
&:first-child {
margin-left: 0 !important;
}
display: inline-block;
font-family: inherit;
font-size: $fs !important;
height: $h !important;
margin: 0 0 0 5px;
position: relative;
width: auto !important;
line-height: $h !important;
top: auto;
right: auto;
bottom: auto;
left: auto;
vertical-align: top;
}
.ptro-tool-ctl-name {
border-radius: 0;
background: none;
top: auto;
font-family: inherit;
padding: 0;
}
.ptro-color-btn {
width: $h !important;
}
.ptro-color-control,
.ptro-icon-btn,
.ptro-named-btn {
// Buttons in toolbar. Why the f* they're named like this is a mystery
background-color: $colorBtnBg;
color: $colorBtnFg;
padding: 0 $interiorMargin;
&:hover {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
i {
display: contents;
font-size: $fs * 1.2;
line-height: inherit;
}
}
.ptro-color-active-control {
background: $colorBtnMajorBg !important;
color: $colorBtnMajorFg !important;
}
.ptro-info,
.ptro-btn-color-checkers-bar,
*[title="Font name"],
*[title="Stroke color"],
*[title="Stroke width"],
*[data-id="fontName"],
*[data-id="fontStrokeSize"],
*[data-id="stroke"] {
display: none;
}
}
/****************************** MOBILE */
body.mobile {
.c-notebook__drag-area { display: none; }
.c-notebook__entry {
[class*="local-controls"] {
display: none;
}
}
&.phone.portrait {
.c-notebook__head,
.c-notebook__entry {
flex-direction: column;
> [class*="__"] + [class*="__"] {
margin-left: 0;
margin-top: $interiorMargin;
}
}
.c-notebook__entry {
[class*="text"] {
min-height: 0;
padding: 0;
pointer-events: none;
}
}
}
}

View File

@ -2,29 +2,25 @@
<div class="c-create-button--w">
<div class="c-create-button c-menu-button c-button--major icon-plus"
@click="toggleCreateMenu">
Create
<span class="c-button__label">Create</span>
</div>
<div class="c-create-menu c-super-menu"
v-if="showCreateMenu">
<div class="c-super-menu__menu">
<ul>
<li v-for="item in createMenuItems"
<li v-for="(item, index) in items"
:key="index"
:class="item.class"
:title="item.title"
@mouseover="showItemDescription">
@mouseover="showItemDescription(item)">
{{ item.name }}
</li>
</ul>
</div>
<div class="c-super-menu__item-description">
<div class="l-item-description__icon bg-icon-plot-stacked"></div>
<div class="l-item-description__name">Name</div>
<div class="l-item-description__description">
A timer that counts up or down to a datetime.
Timers can be started, stopped and reset whenever needed,
and support a variety of display formats.
Each Timer displays the same value to all users.
Timers can be added to Display Layouts.</div>
<div :class="['l-item-description__icon', 'bg-' + selectedMenuItem.class]"></div>
<div class="l-item-description__name">{{selectedMenuItem.name}}</div>
<div class="l-item-description__description">{{selectedMenuItem.title}}</div>
</div>
</div>
</div>
@ -43,6 +39,10 @@
font-size: 1.1em;
}
.c-create-button .c-button__label {
text-transform: $createBtnTextTransform;
}
.c-create-menu {
max-height: 80vh;
max-width: 500px;
@ -61,6 +61,7 @@
<script>
export default {
inject: ['openmct'],
props: {
showCreateMenu: {
type: Boolean,
@ -71,27 +72,30 @@
toggleCreateMenu: function () {
this.showCreateMenu = !this.showCreateMenu;
},
showItemDescription: function (menuItem) {
this.selectedMenuItem = menuItem;
}
},
data: function() {
let items = [];
this.openmct.types.listKeys().forEach(key => {
let menuItem = openmct.types.get(key).definition;
if (menuItem.creatable) {
let menuItemTemplate = {
name: menuItem.name,
class: menuItem.cssClass,
title: menuItem.description
};
items.push(menuItemTemplate);
}
});
return {
createMenuItems: [
{ name: 'Folder', class: 'icon-folder', title: 'Details will go here' },
{ name: 'Display Layout', class: 'icon-layout', title: 'Details will go here' },
{ name: 'Fixed Position Display', class: 'icon-box-with-dashed-lines', title: 'Details will go here' },
{ name: 'Overlay Plot', class: 'icon-plot-overlay', title: 'Details will go here' },
{ name: 'Stacked Plot', class: 'icon-plot-stacked', title: 'Details will go here' },
{ name: 'Telemetry Table', class: 'icon-tabular-realtime', title: 'Details will go here' },
{ name: 'Clock', class: 'icon-clock', title: 'Details will go here' },
{ name: 'Timer', class: 'icon-timer', title: 'Details will go here' },
{ name: 'Web Page', class: 'icon-page', title: 'Details will go here' },
{ name: 'Event Message Generator', class: 'icon-folder-new', title: 'Details will go here' },
{ name: 'Hyperlink', class: 'icon-chain-links', title: 'Details will go here' },
{ name: 'Notebook', class: 'icon-notebook', title: 'Details will go here' },
{ name: 'State Generator', class: 'icon-telemetry', title: 'Details will go here' },
{ name: 'Sine Wave Generator', class: 'icon-telemetry', title: 'Details will go here' },
{ name: 'Example Imagery', class: 'icon-image', title: 'Details will go here' },
{ name: 'Summary Widget', class: 'icon-summary-widget', title: 'Details will go here' }
]
items: items,
selectedMenuItem: {}
}
}
}

View File

@ -152,7 +152,7 @@
&__collapse-button {
$m: 2px;
$h: 12px; //nth($splitterBtnD, 1) - ($m * 2);
$h: 12px;
color: $splitterBtnColorFg;
flex: 0 0 nth($splitterBtnD, 1);
font-size: $h * .9;
@ -192,6 +192,7 @@
// Name of the pane
@include ellipsize();
display: block;
padding-right: nth($splitterBtnD, 2) + $interiorMargin; // Force label to ellipsis
text-transform: uppercase;
transform-origin: top left;
flex: 1 0 90%;

View File

@ -1,9 +1,12 @@
<template>
<div class="c-search"
:class="{ 'is-active': active === true }">
<input class="c-search__input" type="search"
v-bind:value="searchInput"
@input="handleInput($event)"/>
<input class="c-search__input"
tabindex="10000"
type="search"
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"/>
<a class="c-search__clear-input icon-x-in-circle"
v-on:click="clearInput"></a>
</div>
@ -47,7 +50,7 @@
&__input {
background: none !important;
box-shadow: none !important; // !important needed to override default for [input]
flex: 0 1 100%;
flex: 1 1 auto;
padding-left: 2px !important;
padding-right: 2px !important;
min-width: 10px; // Must be set to allow input to collapse below browser min
@ -71,26 +74,36 @@
</style>
<script>
/* Emits input and clear events */
export default {
inheritAttrs: false,
props: {
value: String
},
computed: {
inputListeners: function () {
let vm = this;
return Object.assign({},
this.$listeners,
{
input: function (event) {
vm.$emit('input', event.target.value);
vm.active = (event.target.value.length > 0);
}
}
)
}
},
data: function() {
return {
searchInput: '',
active: false
}
},
methods: {
handleInput(e) {
// Grab input as the user types it
// and set 'active' based on input length > 0
this.searchInput = e.target.value;
this.active = (this.searchInput.length > 0);
},
clearInput() {
// Clear the user's input and set 'active' to false
this.searchInput = '';
this.value = '';
this.$emit('clear','');
this.active = false;
}
}

View File

@ -1,133 +0,0 @@
<template>
<div class="c-splitter" :class="{
'c-splitter-vertical' : align === 'vertical',
'c-splitter-horizontal' : align === 'horizontal',
'c-splitter-collapse-left' : collapse === 'to-left',
'c-splitter-collapse-right' : collapse === 'to-right',
}">
<a class="c-splitter__btn"></a>
</div>
</template>
<style lang="scss">
@import "~styles/constants";
@import "~styles/constants-snow";
@import "~styles/mixins";
@import "~styles/glyphs";
$c: #06f;
$size: $splitterHandleD;
$margin: 0px;
$hitMargin: 4px;
.c-splitter {
background: $colorSplitterBg;
transition: $transOut;
&:before {
// Bigger hit area
//@include test();
content: '';
display: block;
position: absolute;
z-index: 1;
}
&:active, &:hover {
transition: $transIn;
}
&:active {
background: $colorSplitterActive;
}
&:hover {
background: $colorSplitterHover;
}
&-vertical {
cursor: col-resize;
width: $size;
margin: 0 $margin;
&:before {
top: 0;
right: $hitMargin * -1;
bottom: 0;
left: $hitMargin * -1;
}
}
&-horizontal {
cursor: row-resize;
height: $size;
margin: $margin 0;
&:before {
top: $hitMargin * -1;
right: 0;
bottom: $hitMargin * -1;
left: 0;
}
}
}
.c-splitter__btn {
// Collapse button
background: $colorSplitterBg;
display: none; // Only display if splitter is collapsible, see below
width: $splitterD;
height: 40px;
transition: $transOut;
&:active, &:hover {
transition: $transIn;
}
&:hover {
background: $colorSplitterHover;
}
&:active {
background: $colorSplitterActive;
}
[class*="collapse"] & {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
z-index: 3;
&:before {
color: $colorSplitterFg;
display: block;
font-size: 0.8em;
font-family: symbolsfont;
}
}
[class*="collapse-left"] & {
border-bottom-left-radius: $controlCr;
right: 0;
&:before {
content: $glyph-icon-arrow-left;
}
}
[class*="collapse-right"] & {
border-bottom-right-radius: $controlCr;
left: 0;
&:before {
content: $glyph-icon-arrow-right;
}
}
}
</style>
<script>
export default {
props: {
align: String,
collapse: String
}
}
</script>

View File

@ -1,43 +1,12 @@
<template>
<span class="c-view-control"
<span class="c-disclosure-triangle"
:class="{
'c-view-control--expanded' : expanded,
'c-disclosure-triangle--expanded' : expanded,
'is-enabled' : enabled
}"
@click="toggle"></span>
</template>
<style lang="scss">
@import "~styles/sass-base";;
.c-view-control {
$d: 12px;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: $d;
position: relative;
&.is-enabled:before {
$s: .65;
content: $glyph-icon-arrow-right-equilateral;
display: block;
font-family: symbolsfont;
font-size: 1rem * $s;
position: absolute;
transform-origin: floor(($d / 2) * $s); // This is slightly better than 'center'
transition: transform 100ms ease-in-out;
}
&--expanded {
&:before {
transform: rotate(90deg);
}
}
}
</style>
<script>
export default {
props: {

View File

@ -0,0 +1,177 @@
<template>
<div class="l-browse-bar">
<div class="l-browse-bar__start">
<a class="l-browse-bar__nav-to-parent-button c-icon-button icon-pointer-left"></a>
<div class="l-browse-bar__object-name--w"
:class="type.cssClass">
<span
class="l-browse-bar__object-name c-input-inline"
v-on:blur="updateName"
contenteditable>
{{ domainObject.name }}
</span>
</div>
<div class="l-browse-bar__context-actions c-disclosure-button"></div>
</div>
<div class="l-browse-bar__end">
<div class="l-browse-bar__view-switcher c-menu-button--w c-menu-button--menus-left"
v-if="views.length > 1">
<div class="c-menu-button"
:class="currentView.cssClass"
title="Switch view type"
@click="toggleViewMenu">
<span class="c-button__label">
{{ currentView.name }}
</span>
</div>
<div class="c-menu" v-show="showViewMenu">
<ul>
<li v-for="(view, index) in views"
@click="setView(view)"
:key="index"
:class="view.cssClass"
:title="view.name">
{{ view.name }}
</li>
</ul>
</div>
</div>
<!-- Action buttons -->
<div class="l-browse-bar__actions">
<div class="l-browse-bar__action c-button icon-eye-open" title="Preview"></div>
<div class="l-browse-bar__action c-button icon-notebook" title="New Notebook entry"></div>
<div class="l-browse-bar__action c-button c-button--major icon-pencil" title="Edit"></div>
</div>
</div>
</div>
</template>
<script>
export default {
inject: ['openmct'],
methods: {
toggleViewMenu: function (event) {
event.stopPropagation();
this.showViewMenu = !this.showViewMenu;
},
updateName: function (event) {
// TODO: handle isssues with contenteditable text escaping.
if (event.target.innerText !== this.domainObject.name) {
this.openmct.objects.mutate(this.domainObject, 'name', event.target.innerText);
}
},
setView: function (view) {
this.viewKey = view.key;
this.openmct.router.updateParams({
view: this.viewKey
});
}
},
data: function () {
return {
showViewMenu: false,
domainObject: {},
viewKey: undefined
}
},
computed: {
currentView() {
return this.views.filter(v => v.key === this.viewKey)[0] || {};
},
views() {
return this
.openmct
.objectViews
.get(this.domainObject)
.map((p) => {
return {
key: p.key,
cssClass: p.cssClass,
name: p.name
};
});
},
type() {
let objectType = this.openmct.types.get(this.domainObject.type);
if (!objectType) {
return {}
}
return objectType.definition;
}
},
mounted: function () {
document.addEventListener('click', () => {
if (this.showViewMenu) {
this.showViewMenu = false;
}
});
}
}
</script>
<style lang="scss">
@import "~styles/sass-base";
/******************************* START */
.l-browse-bar {
display: flex;
align-items: center;
justify-content: space-between;
[class*="__"] {
// Removes extraneous horizontal white space
display: inline-flex;
}
&__start {
display: flex;
align-items: center;
flex: 1 1 auto;
font-size: 1.4em;
margin-right: $interiorMargin;
min-width: 0; // Forces interior to compress when pushed on
}
&__end {
display: flex;
align-items: center;
flex: 0 0 auto;
[class*="__"] + [class*="__"] {
margin-left: $interiorMarginSm;
}
}
&__nav-to-parent-button,
&__disclosure-button {
flex: 0 0 auto;
}
&__nav-to-parent-button {
// This is an icon-button
$p: $interiorMarginLg;
margin-right: $interiorMargin;
padding-left: $p;
padding-right: $p;
}
&__object-name--w {
align-items: center;
display: flex;
flex: 0 1 auto;
min-width: 0;
&:before {
// Icon
opacity: 0.5;
margin-right: $interiorMargin;
}
}
&__object-name {
flex: 0 1 auto;
}
}
</style>

View File

@ -1,159 +0,0 @@
<template>
<div></div>
</template>
<style lang="scss">
</style>
<script>
// 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("/");
}
export default {
inject: ["openmct"],
mounted() {
let openmct = this.openmct;
let $injector = openmct.$injector;
let angular = openmct.$angular;
this.objectService = $injector.get('objectService');
this.templateLinker = $injector.get('templateLinker');
this.navigationService = $injector.get('navigationService');
this.$timeout = $injector.get('$timeout');
this.templateMap = {};
$injector.get('templates[]').forEach((t) => {
this.templateMap[t.key] = this.templateMap[t.key] || t;
});
this.$rootScope = $injector.get('$rootScope');
this.$scope = this.$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));
},
destroyed() {
},
methods: {
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,
this.openmct.$angular.element(this.$el),
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];
});
}
}
}
</script>

View File

@ -3,10 +3,8 @@
<div class="l-shell__head">
<CreateButton class="l-shell__create-button"></CreateButton>
<div class="l-shell__controls">
<div class="l-controls__elem c-button-set">
<a class="c-icon-button icon-new-window" title="Open in a new browser tab"></a>
<a class="c-icon-button icon-fullscreen-collapse" title="Enable full screen mode"></a>
</div>
<a class="c-icon-button icon-new-window" title="Open in a new browser tab"></a>
<a class="c-icon-button icon-fullscreen-collapse" title="Enable full screen mode"></a>
</div>
<div class="l-shell__app-logo">[ App Logo ]</div>
</div>
@ -24,8 +22,12 @@
</div>
</pane>
<pane class="l-shell__pane-main">
<browse-object class="l-shell__main-container">
</browse-object>
<browse-bar class="l-shell__main-view-browse-bar"
ref="browseBar">
</browse-bar>
<object-view class="l-shell__main-container"
ref="browseObject">
</object-view>
<mct-template template-key="conductor"
class="l-shell__time-conductor">
</mct-template>
@ -82,7 +84,8 @@
}
}
&__pane-main {
&__pane-main,
&__pane-tree {
> .l-pane__contents {
display: flex;
flex-flow: column nowrap;
@ -117,11 +120,15 @@
&__head,
&__status {
flex: 0 1 auto;
flex: 0 0 auto;
display: flex;
}
/******************************* HEAD */
&__main-view-browse-bar {
flex: 0 0 auto;
}
&__head {
align-items: center;
justify-content: space-between;
@ -148,8 +155,9 @@
/********** MAIN AREA */
&__main-container {
// Wrapper for main views
flex: 1 1 100%;
font-size: 16px; // TEMP FOR LEGACY STYLING
flex: 1 1 auto;
overflow: auto;
//font-size: 16px; // TEMP FOR LEGACY STYLING
}
&__tree {
@ -186,26 +194,28 @@
import Inspector from '../inspector/Inspector.vue';
import MctStatus from './MctStatus.vue';
import MctTree from './mct-tree.vue';
import BrowseObject from './BrowseObject.vue';
import ObjectView from './ObjectView.vue';
import MctTemplate from '../legacy/mct-template.vue';
import ContextMenu from '../controls/ContextMenu.vue';
import CreateButton from '../controls/CreateButton.vue';
import search from '../controls/search.vue';
import multipane from '../controls/multipane.vue';
import pane from '../controls/pane.vue';
import BrowseBar from './BrowseBar.vue';
export default {
components: {
Inspector,
MctStatus,
MctTree,
BrowseObject,
ObjectView,
'mct-template': MctTemplate,
ContextMenu,
CreateButton,
search,
multipane,
pane
pane,
BrowseBar
}
}
</script>

View File

@ -0,0 +1,74 @@
<template>
</template>
<style lang="scss">
.l-object-view {
display: contents;
}
</style>
<script>
import _ from "lodash"
export default {
inject: ["openmct"],
props: {
view: String,
object: Object
},
destroyed() {
this.clear();
},
watch: {
view(newView, oldView) {
this.viewKey = newView;
this.debounceUpdateView();
},
object(newObject, oldObject) {
this.currentObject = newObject;
this.debounceUpdateView();
}
},
created() {
this.debounceUpdateView = _.debounce(this.updateView, 10);
},
mounted() {
this.updateView();
},
methods: {
clear() {
if (this.currentView) {
this.currentView.destroy();
this.$el.innerHTML = '';
}
delete this.viewContainer;
delete this.currentView;
},
updateView() {
this.clear();
if (!this.currentObject) {
return;
}
this.viewContainer = document.createElement('div');
this.viewContainer.classList.add('l-object-view');
this.$el.append(this.viewContainer);
let provider = this.openmct.objectViews.getByProviderKey(this.viewKey);
if (!provider) {
provider = this.openmct.objectViews.get(this.currentObject)[0];
if (!provider) {
return;
}
}
this.currentView = provider.view(this.currentObject);
this.currentView.show(this.viewContainer);
},
show(object, viewKey) {
this.currentObject = object;
this.viewKey = viewKey;
this.updateView();
}
}
}
</script>

View File

@ -69,7 +69,8 @@
&:before {
// Type icon
display: inline-block;
display: block;
flex: 0 0 auto;
font-size: 1.3em;
margin-right: $interiorMarginSm;
}

View File

@ -22,8 +22,6 @@ export default {
let $rootScope = $injector.get('$rootScope');
this.$scope = $rootScope.$new();
console.log('mounting', this.templateKey);
console.log('template:', templateMap[this.templateKey]);
templateLinker.link(
this.$scope,

View File

@ -101,7 +101,7 @@ class ApplicationRouter extends EventEmitter {
doPathChange(newPath, oldPath, newLocation) {
let route = this.routes.filter(r => r.matcher.test(newPath))[0];
if (route) {
route.callback(newPath, route.matcher.exec(newPath));
route.callback(newPath, route.matcher.exec(newPath), this.currentLocation.params);
}
this.emit('change:path', newPath, oldPath);
}

80
src/ui/router/Browse.js Normal file
View File

@ -0,0 +1,80 @@
define([
], function (
) {
return function install(openmct) {
let navigateCall = 0;
let browseObject;
function viewObject(object, viewProvider) {
openmct.layout.$refs.browseObject.show(object, viewProvider.key);
openmct.layout.$refs.browseBar.domainObject = object;
openmct.layout.$refs.browseBar.viewKey = viewProvider.key;
};
function navigateToPath(path, currentViewKey) {
navigateCall++;
let currentNavigation = navigateCall;
if (!Array.isArray(path)) {
path = path.split('/');
}
let keyString = path[path.length - 1];
// TODO: retain complete path in navigation.
return openmct.objects.get(keyString)
.then((object) => {
if (currentNavigation !== navigateCall) {
return; // Prevent race.
}
openmct.layout.$refs.browseBar.domainObject = object;
browseObject = object;
if (!object) {
openmct.layout.$refs.browseObject.clear();
return;
}
let currentProvider = openmct
.objectViews
.getByProviderKey(currentViewKey)
if (currentProvider && currentProvider.canView(object)) {
viewObject(object, currentProvider);
return;
}
let defaultProvider = openmct.objectViews.get(object)[0];
if (defaultProvider) {
openmct.router.updateParams({
view: defaultProvider.key
});
} else {
openmct.router.updateParams({
view: undefined
});
openmct.layout.$refs.browseObject.clear();
}
});
}
openmct.router.route(/^\/browse\/(.*)$/, (path, results, params) => {
let navigatePath = results[1];
if (!navigatePath) {
navigatePath = 'mine';
}
navigateToPath(navigatePath, params.view);
});
openmct.router.on('change:params', function (newParams, oldParams, changed) {
if (changed.view && browseObject) {
let provider = openmct
.objectViews
.getByProviderKey(changed.view);
viewObject(browseObject, provider);
}
});
}
});