A bunch of fixes for TCR (#2250)

* fix add links by drag and drop

* fix dialog component logging errors

* fix search component errors

* fix sort

* remove unused entrydnd file

* remove unnecessary console logs

* fix preview action in embed dropdown, which was throwing a type error

* invoke PreviewAction once in NotebookController and pass into git add -a

* add navigation capability to embeds, and a bunch of cleanup

* code cleanup and avoid calling dismiss twice on overlay destroy, which was throwing a DOM error. Calling code should dismiss the overlay

* only show elements pool if domainObject has composition

* fix error in inspector when no selection is present

* wire up object view expand button

* listen to composition#remove in TabsView

* make reviewer requested changes

* make reviewer requested changes, and fix for an edge case where removed tab is at the end of the array and currentTab is not set properly

* remove array wrapping dynamic classes in embed.html

* add title='View Large' to expand button

* change model variable to domainObject in tabs.vue

* dismiss top level overlay when esc is pressed (only if overlay is dismissable)

* fix link navigation from embeds

* use positive flag dismissable instead of negative notDismissable for overlays

* make overlays dismissable by default, unless set to false
This commit is contained in:
Deep Tailor 2019-01-24 16:23:50 -08:00 committed by Andrew Henry
parent dd31de6935
commit ac11f898d4
15 changed files with 221 additions and 152 deletions

View File

@ -29,13 +29,21 @@ define(
function SnapshotPreviewController($scope, openmct) {
$scope.previewImage = function (imageUrl) {
var image = document.createElement('img');
let image = document.createElement('img');
image.src = imageUrl;
openmct.overlays.overlay(
let previewImageOverlay = openmct.overlays.overlay(
{
element: image,
size: 'large'
size: 'large',
buttons: [
{
label: 'Done',
callback: function () {
previewImageOverlay.dismiss();
}
}
]
}
);
};
@ -43,13 +51,13 @@ define(
$scope.annotateImage = function (ngModel, field, imageUrl) {
$scope.imageUrl = imageUrl;
var div = document.createElement('div'),
let div = document.createElement('div'),
painterroInstance = {},
save = false;
div.id = 'snap-annotation';
openmct.overlays.overlay(
let annotateImageOverlay = openmct.overlays.overlay(
{
element: div,
size: 'large',
@ -59,6 +67,7 @@ define(
callback: function () {
save = false;
painterroInstance.save();
annotateImageOverlay.dismiss();
}
},
{
@ -66,6 +75,7 @@ define(
callback: function () {
save = true;
painterroInstance.save();
annotateImageOverlay.dismiss();
}
}
]
@ -97,7 +107,7 @@ define(
},
saveHandler: function (image, done) {
if (save) {
var url = image.asBlob(),
let url = image.asBlob(),
reader = new window.FileReader();
reader.readAsDataURL(url);

View File

@ -22,7 +22,7 @@ class Dialog extends Overlay {
super({
element: component.$el,
size: 'fit',
notDismissable: true,
dismissable: false,
...options
});

View File

@ -12,6 +12,7 @@ class Overlay extends EventEmitter {
constructor(options) {
super();
this.dismissable = options.dismissable !== false ? true : false;
this.container = document.createElement('div');
this.container.classList.add('l-overlay-wrapper', cssClasses[options.size]);
@ -20,7 +21,7 @@ class Overlay extends EventEmitter {
dismiss: this.dismiss.bind(this),
element: options.element,
buttons: options.buttons,
notDismissable: options.notDismissable ? true : false
dismissable: this.dismissable
},
components: {
OverlayComponent: OverlayComponent
@ -35,8 +36,8 @@ class Overlay extends EventEmitter {
dismiss() {
this.emit('destroy');
this.component.$destroy();
document.body.removeChild(this.container);
this.component.$destroy();
}
/**

View File

@ -14,6 +14,14 @@ import ProgressDialog from './ProgressDialog';
class OverlayAPI {
constructor() {
this.activeOverlays = [];
this.dismissLastOverlay = this.dismissLastOverlay.bind(this);
document.addEventListener('keyup', (event) => {
if (event.key === 'Escape') {
this.dismissLastOverlay();
}
});
}
/**
@ -38,14 +46,24 @@ class OverlayAPI {
}
/**
] * A description of option properties that can be passed into the overlay
* @typedef options
* private
*/
dismissLastOverlay() {
let lastOverlay = this.activeOverlays[this.activeOverlays.length - 1];
if (lastOverlay && lastOverlay.dismissable) {
lastOverlay.dismiss();
}
}
/**
* A description of option properties that can be passed into the overlay
* @typedef options
* @property {object} element DOMElement that is to be inserted/shown on the overlay
* @property {string} size prefered size of the overlay (large, small, fit)
* @property {array} buttons optional button objects with label and callback properties
* @property {function} onDestroy callback to be called when overlay is destroyed
* @property {boolean} notDismissable to prevent user from dismissing the overlay, calling code
* will need to explicitly dismiss the overlay.
* @property {boolean} dismissable allow user to dismiss overlay by using esc, and clicking away
* from overlay. Unless set to false, all overlays will be dismissable by default.
*/
overlay(options) {
let overlay = new Overlay(options);

View File

@ -25,7 +25,7 @@ class ProgressDialog extends Overlay {
super({
element: component.$el,
size: 'fit',
notDismissable: true,
dismissable: false,
...options
});

View File

@ -5,7 +5,7 @@
</div>
<div class="c-overlay__outer">
<button class="c-click-icon c-overlay__close-button icon-x-in-circle"
v-if="!notDismissable"
v-if="dismissable"
@click="destroy">
</button>
<div class="c-overlay__contents" ref="element"></div>
@ -129,19 +129,19 @@
<script>
export default {
inject: ['dismiss', 'element', 'buttons', 'notDismissable'],
inject: ['dismiss', 'element', 'buttons', 'dismissable'],
mounted() {
this.$refs.element.appendChild(this.element);
},
methods: {
destroy: function () {
if (!this.notDismissable) {
if (this.dismissable) {
this.dismiss();
}
},
buttonClickHandler: function (method) {
method();
this.destroy();
this.$emit('destroy');
}
}
}

View File

@ -7,18 +7,19 @@
<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>
:href="objectLink"
:class="embed.cssClass">{{embed.name}}</a>
<a class="c-ne__embed__context-available icon-arrow-down"
v-on:click="toggleActionMenu"></a>
@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)">
<li v-for="action in actions"
:key="action.name"
:class="action.cssClass"
@click="action.perform(embed, entry)">
{{ action.name }}
</li>
</ul>

View File

@ -1,7 +1,5 @@
<li class="c-notebook__entry c-ne has-local-controls"
v-on:drop="dropOnEntry(entry.id, $event)"
v-on:dragover="dragoverOnEntry"
>
@drop.prevent="dropOnEntry(entry.id, $event)">
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<span>{{formatTime(entry.createdOn, 'YYYY-MM-DD')}}</span>
@ -11,18 +9,18 @@
<div class="c-ne__text c-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"
@blur="textBlur($event, entry.id)"
@focus="textFocus($event, entry.id)"
v-html="entry.text">
</div>
<div class="c-ne__embeds">
<notebook-embed
v-for="(embed, index) in entry.embeds"
:key="index"
v-for="embed in entry.embeds"
:key="embed.id"
:embed="embed"
:entry="entry"
></notebook-embed>
:objectPath="embed.objectPath"
:entry="entry">
</notebook-embed>
</div>
</div>
</div>
@ -30,6 +28,7 @@
<div class="c-ne__local-controls--hidden">
<button class="c-click-icon c-click-icon--major icon-trash"
title="Delete this entry"
v-on:click="deleteEntry"></button>
@click="deleteEntry">
</button>
</div>
</li>

View File

@ -24,11 +24,10 @@
</div>
<div class="c-notebook__drag-area icon-plus"
@click="newEntry($event)"
@drop="newEntry($event)"
id="newEntry">
@drop="newEntry($event)">
<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()">
<div class="c-notebook__entries">
<ul>
<notebook-entry
v-for="(entry,index) in filteredAndSortedEntries"

View File

@ -34,28 +34,17 @@ function (
Vue,
Painterro
) {
function EmbedController (openmct, domainObject) {
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 (domainObject, entry, embed) {
function annotateSnapshot(openmct) {
@ -63,20 +52,22 @@ function (
var save = false,
painterroInstance = {},
annotateOverlay = new Vue({
annotateVue = new Vue({
template: '<div id="snap-annotation"></div>'
}),
self = this;
openmct.overlays.overlay({
element: annotateOverlay.$mount().$el,
let annotateOverlay = openmct.overlays.overlay({
element: annotateVue.$mount().$el,
size: 'large',
dismissable: false,
buttons: [
{
label: 'Cancel',
callback: function () {
save = false;
painterroInstance.save();
annotateOverlay.dismiss();
}
},
{
@ -84,11 +75,12 @@ function (
callback: function () {
save = true;
painterroInstance.save();
annotateOverlay.dismiss();
}
}
],
onDestroy: function () {
annotateOverlay.$destroy(true);
annotateVue.$destroy(true);
}
});
@ -162,14 +154,11 @@ function (
}
});
function onDestroyCallback() {
snapshot.$destroy(true);
}
var snapshotOverlay = this.openmct.overlays.overlay({
element: snapshot.$mount().$el,
onDestroy: onDestroyCallback,
onDestroy: () => {snapshot.$destroy(true)},
size: 'large',
dismissable: true,
buttons: [
{
label: 'Done',
@ -199,23 +188,20 @@ function (
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) {
EmbedController.prototype.populateActionMenu = function (openmct, actions) {
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));
openmct.objects.get(self.embed.type).then(function (domainObject) {
actions.forEach((action) => {
self.actions.push({
cssClass: action.cssClass,
name: action.name,
perform: () => {
action.invoke([domainObject].concat(openmct.router.path));
}
});
});
});
};
};
@ -308,11 +294,9 @@ function (
var self = this;
return {
navigate: self.navigate,
openSnapshot: self.openSnapshot,
formatTime: self.formatTime,
toggleActionMenu: self.toggleActionMenu,
actionToMenuDecorator: self.actionToMenuDecorator,
findInArray: self.findInArray
};
};

View File

@ -30,8 +30,6 @@ function (
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 = '';
@ -106,37 +104,35 @@ function (
};
EntryController.prototype.dropOnEntry = function (entryid, event) {
var data = event.dataTransfer.getData('openmct/domain-object-path');
if (data) {
var selectedObject = JSON.parse(data)[0],
selectedObjectId = selectedObject.identifier.key,
cssClass = this.openmct.types.get(selectedObject.type),
var objectPath = JSON.parse(data),
domainObject = objectPath[0],
domainObjectKey = domainObject.identifier.key,
domainObjectType = this.openmct.types.get(domainObject.type),
cssClass = domainObjectType && domainObjectType.definition ?
domainObjectType.definition.cssClass : 'icon-object-unknown',
entryPos = this.entryPosById(entryid),
currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
newEmbed = {
type: selectedObjectId,
id: '' + Date.now(),
domainObject: domainObject,
objectPath: objectPath,
type: domainObjectKey,
cssClass: cssClass,
name: selectedObject.name,
name: domainObject.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,
dialogService: this.dialogService,
currentEntryValue: this.currentEntryValue
};
};
@ -148,8 +144,7 @@ function (
textBlur: this.textBlur,
formatTime: this.formatTime,
deleteEntry: this.deleteEntry,
dropOnEntry: this.dropOnEntry,
dragoverOnEntry: this.dragoverOnEntry
dropOnEntry: this.dropOnEntry
};
};
return EntryController;

View File

@ -27,7 +27,9 @@ define([
'../../res/templates/notebook.html',
'../../res/templates/entry.html',
'../../res/templates/embed.html',
'../../../../ui/components/search.vue'
'../../../../ui/components/search.vue',
'../../../../ui/preview/PreviewAction',
'../../../../ui/mixins/object-link'
],
function (
Vue,
@ -36,15 +38,16 @@ function (
NotebookTemplate,
EntryTemplate,
EmbedTemplate,
search
search,
PreviewAction,
objectLinkMixin
) {
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.previewAction = new PreviewAction.default(openmct);
this.show = this.show.bind(this);
this.destroy = this.destroy.bind(this);
@ -61,11 +64,12 @@ function (
var notebookEmbed = {
inject:['openmct', 'domainObject'],
mixins:[objectLinkMixin.default],
props:['embed', 'entry'],
template: EmbedTemplate,
data: embedController.exposedData,
methods: embedController.exposedMethods(),
beforeMount: embedController.populateActionMenu(self.objectService, self.actionService)
beforeMount: embedController.populateActionMenu(self.openmct, [self.previewAction])
};
var entryComponent = {

View File

@ -15,25 +15,25 @@
v-for="(tab,index) in tabsList"
:key="index"
:class="[
{'is-current': tab=== currentTab},
{'is-current': isCurrent(tab)},
tab.type.definition.cssClass
]"
@click="showTab(tab)">
<span class="c-button__label">{{tab.model.name}}</span>
<span class="c-button__label">{{tab.domainObject.name}}</span>
</button>
</div>
<div class="c-tabs-view__object-holder"
v-for="(object, index) in tabsList"
v-for="(tab, index) in tabsList"
:key="index"
:class="{'invisible': object !== currentTab}">
:class="{'invisible': !isCurrent(tab)}">
<div class="c-tabs-view__object-name l-browse-bar__object-name--w"
:class="currentTab.type.definition.cssClass">
<div class="l-browse-bar__object-name">
{{currentTab.model.name}}
{{currentTab.domainObject.name}}
</div>
</div>
<object-view class="c-tabs-view__object"
:object="object.model">
:object="tab.domainObject">
</object-view>
</div>
</div>
@ -87,6 +87,7 @@
<script>
import ObjectView from '../../../ui/components/ObjectView.vue';
import _ from 'lodash';
var unknownObjectType = {
definition: {
@ -100,9 +101,71 @@ export default {
components: {
ObjectView
},
data: function () {
return {
currentTab: {},
tabsList: [],
setCurrentTab: true,
isDragging: false,
allowDrop: false
};
},
methods:{
showTab(tab) {
this.currentTab = tab;
},
addItem(domainObject) {
let type = this.openmct.types.get(domainObject.type) || unknownObjectType,
tabItem = {
domainObject,
type: type
};
this.tabsList.push(tabItem);
if (this.setCurrentTab) {
this.currentTab = tabItem;
this.setCurrentTab = false;
}
},
removeItem(identifier) {
let pos = this.tabsList.findIndex(tab =>
tab.domainObject.identifier.namespace === identifier.namespace && tab.domainObject.identifier.key === identifier.key
),
tabToBeRemoved = this.tabsList[pos];
this.tabsList.splice(pos, 1);
if (this.isCurrent(tabToBeRemoved)) {
this.showTab(this.tabsList[this.tabsList.length - 1]);
}
},
onDrop(e) {
this.setCurrentTab = true;
},
dragstart (e) {
if (e.dataTransfer.types.includes('openmct/domain-object-path')) {
this.isDragging = true;
}
},
dragend(e) {
this.isDragging = false;
this.allowDrop = false;
},
dragenter() {
this.allowDrop = true;
},
dragleave() {
this.allowDrop = false;
},
isCurrent(tab) {
return _.isEqual(this.currentTab, tab)
}
},
mounted () {
if (this.composition) {
this.composition.on('add', this.addItem, this);
this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem);
this.composition.load();
}
@ -116,54 +179,9 @@ export default {
dropHint.addEventListener('dragleave', this.dragleave);
}
},
data: function () {
return {
currentTab: {},
tabsList: [],
setCurrentTab: true,
isDragging: false,
allowDrop: false
};
},
methods:{
showTab (tab) {
this.currentTab = tab;
},
addItem (model) {
let type = this.openmct.types.get(model.type) || unknownObjectType,
tabItem = {
model,
type: type
};
this.tabsList.push(tabItem);
if (this.setCurrentTab) {
this.currentTab = tabItem;
this.setCurrentTab = false;
}
},
onDrop (e) {
this.setCurrentTab = true;
},
dragstart (e) {
if (e.dataTransfer.types.includes('openmct/domain-object-path')) {
this.isDragging = true;
}
},
dragend (e) {
this.isDragging = false;
this.allowDrop = false;
},
dragenter () {
this.allowDrop = true;
},
dragleave() {
this.allowDrop = false;
}
},
destroyed() {
this.composition.off('add', this.addItem, this);
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);

View File

@ -36,10 +36,14 @@
</context-menu-drop-down>
</div>
<div class="c-so-view__header__end">
<div class="c-button icon-expand local-controls--hidden"></div>
<div class="c-button icon-expand local-controls--hidden"
title="View Large"
@click="expand">
</div>
</div>
</div>
<object-view class="c-so-view__object-view"
ref="objectView"
:object="domainObject"></object-view>
</div>
</template>
@ -125,6 +129,21 @@
objectPath: Array,
hasFrame: Boolean,
},
methods: {
expand() {
let objectView = this.$refs.objectView,
parentElement = objectView.$el,
childElement = parentElement.children[0];
this.openmct.overlays.overlay({
element: childElement,
size: 'large',
onDestroy() {
parentElement.append(childElement);
}
});
}
},
components: {
ObjectView,
ContextMenuDropDown,

View File

@ -8,7 +8,7 @@
</pane>
<pane class="c-inspector__elements"
handle="before"
label="Elements" v-if="isEditing">
label="Elements" v-if="isEditing && hasComposition">
<elements></elements>
</pane>
</multipane>
@ -224,6 +224,27 @@
Properties,
Location,
InspectorView
},
data() {
return {
hasComposition: false
}
},
methods: {
refreshComposition(selection) {
if (selection[0]) {
let parentObject = selection[0].context.item;
this.hasComposition = !!this.openmct.composition.get(parentObject);
}
}
},
mounted() {
this.openmct.selection.on('change', this.refreshComposition);
this.refreshComposition(this.openmct.selection.get());
},
destroyed() {
this.openmct.selection.off('change', this.refreshComposition);
}
}
</script>