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
This commit is contained in:
Deep Tailor 2018-09-12 15:27:19 -07:00 committed by Pete Richards
parent c883bbe6c2
commit 3a28caac06
32 changed files with 1108 additions and 1251 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,132 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* Module defining ViewSnapshot
*/
var OVERLAY_TEMPLATE = '' +
' <div class="abs blocker"></div>' +
' <div class="abs outer-holder">' +
' <a class="close icon-x-in-circle"></a>' +
' <div class="abs inner-holder l-flex-col">' +
' <div class="t-contents flex-elem holder grows"></div>' +
' <div class="bottom-bar flex-elem holder">' +
' <a class="t-done s-button major">Done</a>' +
' </div>' +
' </div>' +
' </div>';
define([
'zepto',
"../../res/templates/snapshotHeader.html"
],
function ($, headerTemplate) {
var toggleOverlay,
overlay,
closeButton,
doneButton,
blocker,
overlayContainer,
img,
annotateButton,
annotateImg;
function ViewSnapshot($compile) {
this.$compile = $compile;
}
function openOverlay(url, header) {
overlay = document.createElement('div');
$(overlay).addClass('abs overlay l-large-view');
overlay.innerHTML = OVERLAY_TEMPLATE;
overlayContainer = overlay.querySelector('.t-contents');
closeButton = overlay.querySelector('a.close');
closeButton.addEventListener('click', toggleOverlay);
doneButton = overlay.querySelector('a.t-done');
doneButton.addEventListener('click', toggleOverlay);
blocker = overlay.querySelector('.abs.blocker');
blocker.addEventListener('click', toggleOverlay);
annotateButton = header.querySelector('a.icon-pencil');
annotateButton.addEventListener('click', annotateImg);
document.body.appendChild(overlay);
img = document.createElement('div');
$(img).addClass('abs object-holder t-image-holder s-image-holder');
img.innerHTML = '<div class="image-main s-image-main" style="background-image: url(' + url + ');"></div>';
overlayContainer.appendChild(header);
overlayContainer.appendChild(img);
}
function closeOverlay() {
overlayContainer.removeChild(img);
document.body.removeChild(overlay);
closeButton.removeEventListener('click', toggleOverlay);
closeButton = undefined;
doneButton.removeEventListener('click', toggleOverlay);
doneButton = undefined;
blocker.removeEventListener('click', toggleOverlay);
blocker = undefined;
overlayContainer = undefined;
overlay = undefined;
img = undefined;
}
ViewSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId, $scope, embed) {
var isOpen = false;
// onclick for menu items in overlay header context menu
$scope.menuPerform = function (menu) {
menu.perform();
closeOverlay();
};
// Create the overlay element and add it to the document's body
$scope.cssClass = embed.cssClass;
$scope.embedType = embed.type;
$scope.entryName = embed.name;
$scope.snapDate = +embedId;
var element = this.$compile(headerTemplate)($scope);
var annotateAction = $scope.action.getActions({category: 'embed'})[1];
toggleOverlay = function () {
if (!isOpen) {
openOverlay(snapshot, element[0]);
isOpen = true;
} else {
closeOverlay();
isOpen = false;
}
};
annotateImg = function () {
closeOverlay();
annotateAction.perform($event, snapshot, embedId, entryId, $scope);
};
toggleOverlay();
};
return ViewSnapshot;
}
);

View File

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

View File

@ -1,367 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*-- main controller file, here is the core functionality of the notebook plugin --*/
define(
['zepto'],
function ($) {
function NotebookController(
$scope,
dialogService,
popupService,
agentService,
objectService,
navigationService,
now,
actionService,
$timeout,
$element,
$sce
) {
$scope.entriesEl = $(document.body).find('.t-entries-list');
$scope.sortEntries = $scope.domainObject.getModel().defaultSort;
$scope.showTime = "0";
$scope.editEntry = false;
$scope.entrySearch = '';
$scope.entryTypes = [];
$scope.embedActions = [];
$scope.currentEntryValue = '';
var SECONDS_IN_AN_HOUR = 60 * 60 * 1000;
this.scope = $scope;
$scope.hoursFilter = function (hours,entryTime) {
if (+hours) {
return entryTime > (Date.now() - SECONDS_IN_AN_HOUR * (+hours));
}else {
return true;
}
};
$scope.scrollToTop = function () {
var entriesContainer = $scope.entriesEl.parent();
entriesContainer[0].scrollTop = 0;
};
$scope.findEntryEl = function (entryId) {
var element = $($scope.entriesEl).find('#entry_' + entryId);
if (element[0]) {
return element.find("[contenteditable='true']");
} else {
var entries = $scope.entriesEl.children().children(),
lastOrFirst = $scope.sortEntries === "-createdOn" ? 0 : (entries.length - 1);
return $(entries[lastOrFirst]).find("[contenteditable='true']");
}
};
$scope.findEntryPositionById = function (id) {
var foundId = -1;
$scope.domainObject.model.entries.forEach(function (element, index) {
if (element.id === id) {
foundId = index;
return;
}
});
return foundId;
};
$scope.newEntry = function ($event) {
$scope.scrollToTop();
var entries = $scope.domainObject.model.entries,
lastEntry = entries[entries.length - 1],
id = Date.now();
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
var createdEntry = {'id': id, 'createdOn': id};
$scope.domainObject.useCapability('mutation', function (model) {
model.entries.push(createdEntry);
});
} else {
$scope.findEntryEl(lastEntry.id).focus();
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entries.length - 1].createdOn = id;
});
}
$scope.entrySearch = '';
};
$scope.deleteEntry = function ($event) {
var delId = +$event.currentTarget.id;
var errorDialog = dialogService.showBlockingMessage({
severity: "error",
title: "This action will permanently delete this Notebook entry. Do you want to continue?",
minimized: true, // want the notification to be minimized initially (don't show banner)
options: [{
label: "OK",
callback: function () {
errorDialog.dismiss();
var elementPos = $scope.findEntryPositionById(delId);
if (elementPos !== -1) {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries.splice(elementPos, 1);
});
} else {
window.console.log('delete error');
}
}
},{
label: "Cancel",
callback: function () {
errorDialog.dismiss();
}
}]
});
};
$scope.textFocus = function ($event, entryId) {
if ($event.srcElement) {
$scope.currentEntryValue = $event.srcElement.innerText;
} else {
$event.target.innerText = '';
}
};
//On text blur(when focus is removed)
$scope.textBlur = function ($event, entryId) {
// entryId is the unique numeric based on the original createdOn
if ($event.target) {
var elementPos = $scope.findEntryPositionById(+entryId);
// If the text of an entry has been changed, then update the text and the modifiedOn numeric
// Otherwise, don't do anything
if ($scope.currentEntryValue !== $event.target.innerText) {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[elementPos].text = $event.target.innerText;
model.entries[elementPos].modified = Date.now();
});
}
}
};
$scope.finished = function (model) {
var lastEntry = model[model.length - 1];
if (!lastEntry.text) {
$scope.findEntryEl(lastEntry.id).focus();
}
};
$scope.handleActive = function () {
var newEntry = $scope.entriesEl.find('.active');
if (newEntry) {
newEntry.removeClass('active');
}
};
$scope.clearSearch = function () {
$scope.entrySearch = '';
};
$scope.viewSnapshot = function ($event,snapshot,embedId,entryId,$innerScope,domainObject) {
var viewAction = $scope.action.getActions({category: 'embed'})[0];
viewAction.perform($event, snapshot, embedId, entryId, $innerScope, domainObject);
};
$scope.renderImage = function (img) {
return URL.createObjectURL(img);
};
$scope.getDomainObj = function (id) {
return objectService.getObjects([id]);
};
function refreshComp(change) {
if (change && change.length) {
change[0].getCapability('action').getActions({key: 'remove'})[0].perform();
}
}
$scope.actionToMenuOption = function (action) {
return {
key: action.getMetadata().key,
name: action.getMetadata().name,
cssClass: action.getMetadata().cssClass,
perform: action.perform
};
};
// Maintain all "conclude-editing" and "save" actions in the
// present context.
function updateActions() {
$scope.menuEmbed = $scope.action ?
$scope.action.getActions({category: 'embed'}) :
[];
$scope.menuEmbedNoSnap = $scope.action ?
$scope.action.getActions({category: 'embed-no-snap'}) :
[];
$scope.menuActions = $scope.action ?
$scope.action.getActions({key: 'window'}) :
[];
}
// Update set of actions whenever the action capability
// changes or becomes available.
$scope.$watch("action", updateActions);
$scope.navigate = function ($event,embedType) {
if ($event) {
$event.preventDefault();
}
$scope.getDomainObj(embedType).then(function (resp) {
navigationService.setNavigation(resp[embedType]);
});
};
$scope.saveSnap = function (url,embedPos,entryPos) {
var snapshot = false;
if (url) {
if (embedPos !== -1 && entryPos !== -1) {
var reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
snapshot = reader.result;
$scope.domainObject.useCapability('mutation', function (model) {
if (model.entries[entryPos]) {
model.entries[entryPos].embeds[embedPos].snapshot = {
'src': snapshot,
'type': url.type,
'size': url.size,
'modified': Date.now()
};
model.entries[entryPos].embeds[embedPos].id = Date.now();
}
});
};
}
}else {
$scope.domainObject.useCapability('mutation', function (model) {
model.entries[entryPos].embeds[embedPos].snapshot = snapshot;
});
}
};
/*---popups menu embeds----*/
function getEmbedActions(embedType) {
if (!$scope.embedActions.length) {
$scope.getDomainObj(embedType).then(function (resp) {
$scope.embedActions = [];
$scope.embedActions.push($scope.actionToMenuOption(
$scope.action.getActions({key: 'mct-preview-action', selectedObject: resp[embedType]})[0]
));
$scope.embedActions.push($scope.actionToMenuOption(
$scope.action.getActions({key: 'window', selectedObject: resp[embedType]})[0]
));
$scope.embedActions.push({
key: 'navigate',
name: 'Go to Original',
cssClass: '',
perform: function () {
$scope.navigate('', embedType);
}
});
});
}
}
$scope.openMenu = function ($event,embedType) {
$event.preventDefault();
getEmbedActions(embedType);
var body = $(document).find('body'),
initiatingEvent = agentService.isMobile() ?
'touchstart' : 'mousedown',
dismissExistingMenu,
menu;
var container = $($event.currentTarget).parent().parent();
menu = container.find('.menu-element');
// Remove the context menu
function dismiss() {
container.find('.hide-menu').append(menu);
body.off("mousedown", dismiss);
dismissExistingMenu = undefined;
$scope.embedActions = [];
}
// Dismiss any menu which was already showing
if (dismissExistingMenu) {
dismissExistingMenu();
}
// ...and record the presence of this menu.
dismissExistingMenu = dismiss;
popupService.display(menu, [$event.pageX,$event.pageY], {
marginX: 0,
marginY: -50
});
// Stop propagation so that clicks or touches on the menu do not close the menu
menu.on(initiatingEvent, function (event) {
event.stopPropagation();
$timeout(dismiss, 300);
});
// Dismiss the menu when body is clicked/touched elsewhere
// ('mousedown' because 'click' breaks left-click context menus)
// ('touchstart' because 'touch' breaks context menus up)
body.on(initiatingEvent, dismiss);
};
$scope.$watchCollection("composition", refreshComp);
$scope.$watch('domainObject.getModel().defaultSort', function (newDefaultSort, oldDefaultSort) {
if (newDefaultSort !== oldDefaultSort) {
$scope.sortEntries = newDefaultSort;
}
});
$scope.$on('$destroy', function () {});
}
return NotebookController;
});

View File

@ -1,44 +0,0 @@
/******************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* This bundle implements "containment" rules, which determine which objects
* can be contained within a notebook.
*/
define(
[],
function () {
function CompositionPolicy() {
}
CompositionPolicy.prototype.allow = function (parent, child) {
var parentDef = parent.getCapability('type').getName();
if (parentDef === 'Notebook' && child.getCapability('status').list().length) {
return false;
}
return true;
};
return CompositionPolicy;
}
);

View File

@ -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,36 @@
<div
class="l-flex-row l-entry-embed"
v-bind:class="[embed.cssClass]">
<div
class="snap-thumb"
v-if="embed.snapshot"
v-on:click="openSnapshot">
<img v-bind:src="embed.snapshot.src">
</div>
<div class="embed-info l-flex-col">
<div class="embed-title object-header">
<a v-on:click="navigate(embed.type)">{{embed.name}}</a>
<a class='context-available' 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="embed-date" v-if="embed.snapshot">
{{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
<li class="l-flex-row has-local-controls l-notebook-entry s-notebook-entry"
v-on:drop="dropOnEntry(entry.id)"
v-on:dragover="dragoverOnEntry"
>
<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>{{formatTime(entry.createdOn, 'YYYY-MM-DD')}}</span>
<span>{{formatTime(entry.createdOn, 'HH:mm:ss')}}</span>
</div>
<div class="holder flex-elem l-flex-col grows l-notebook-entry-content">
<div class="flex-elem s-input-inline t-notebook-entry-input s-notebook-entry-text"
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="flex-elem entry-embeds l-flex-row">
<notebook-embed
v-for="(embed, index) in entry.embeds"
v-bind:embed="embed"
v-bind:entry="entry"
></notebook-embed>
</div>
</div>
<div class="holder flex-elem local-control local-controls-hidden notebook-entry-delete">
<a class="s-icon-button icon-trash" title="Delete Entry" v-on:click="deleteEntry"></a>
</div>
</div>
</li>

View File

@ -0,0 +1,46 @@
<div 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"
v-model="entrySearch"
v-on:keyup="search($event)"/>
<a class="c-search__clear-input clear-icon icon-x-in-circle"
v-bind:class="[entrySearch ? 'show': '']"
v-on:click="entrySearch = ''; search($event)"></a>
</div>
<div class="notebook-view-controls l-flex-row flex-elem holder">
<div class="select notebook-view-controls__filter-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 notebook-view-controls__sort">
<select v-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" v-on: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>
<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",

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,174 @@
/*****************************************************************************
* 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'
],
function (
Vue,
EntryController,
EmbedController,
NotebookTemplate,
EntryTemplate,
EmbedTemplate
) {
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
},
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

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