Compare commits

...

5 Commits

9 changed files with 364 additions and 60 deletions

View File

@ -1,7 +1,7 @@
<div class="c-ne__embed"> <div class="c-ne__embed">
<div class="c-ne__embed__snap-thumb" <div class="c-ne__embed__snap-thumb"
v-if="embed.snapshot" v-if="embed.snapshot"
v-on:click="openSnapshot"> v-on:click="openSnapshot(domainObject, entry, embed)">
<img v-bind:src="embed.snapshot.src"> <img v-bind:src="embed.snapshot.src">
</div> </div>
<div class="c-ne__embed__info"> <div class="c-ne__embed__info">

View File

@ -15,7 +15,7 @@
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime"> <div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}} SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
</div> </div>
<a class="s-button icon-pencil" title="Annotate"> <a class="s-button icon-pencil" title="Annotate" v-on:click="annotateSnapshot">
<span class="title-label">Annotate</span> <span class="title-label">Annotate</span>
</a> </a>
</div> </div>

View File

@ -23,16 +23,16 @@
define([ define([
'moment', 'moment',
'zepto', 'zepto',
'../utils/SnapshotOverlay',
'../../res/templates/snapshotTemplate.html', '../../res/templates/snapshotTemplate.html',
'vue' 'vue',
'painterro'
], ],
function ( function (
Moment, Moment,
$, $,
SnapshotOverlay,
SnapshotTemplate, SnapshotTemplate,
Vue Vue,
Painterro
) { ) {
function EmbedController (openmct, domainObject) { function EmbedController (openmct, domainObject) {
this.openmct = openmct; this.openmct = openmct;
@ -52,11 +52,102 @@ function (
EmbedController.prototype.navigate = function (embedType) { EmbedController.prototype.navigate = function (embedType) {
this.objectService.getObjects([embedType]).then(function (objects) { this.objectService.getObjects([embedType]).then(function (objects) {
this.navigationService.setNavigation(objects[embedType]); this.navigationService.setNavigation(objects[embedType]);
}.bind(this)); }.bind(this));
}; };
EmbedController.prototype.openSnapshot = function () { EmbedController.prototype.openSnapshot = function (domainObject, entry, embed) {
function annotateSnapshot(openmct) {
return function () {
var save = false,
painterroInstance = {},
annotateOverlay = new Vue({
template: '<div id="snap-annotation"></div>'
}),
self = this;
var options = {
cssClass: 'l-large-view',
onDestroy: function () {
annotateOverlay.$destroy(true);
},
buttons: [
{
label: 'Cancel',
callback: function () {
save = false;
painterroInstance.save();
}
},
{
label: 'Save',
callback: function () {
save = true;
painterroInstance.save();
}
}
]
};
openmct.OverlayService.show(annotateOverlay.$mount().$el, options);
painterroInstance = Painterro({
id: 'snap-annotation',
activeColor: '#ff0000',
activeColorAlpha: 1.0,
activeFillColor: '#fff',
activeFillColorAlpha: 0.0,
backgroundFillColor: '#000',
backgroundFillColorAlpha: 0.0,
defaultFontSize: 16,
defaultLineWidth: 2,
defaultTool: 'ellipse',
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
translation: {
name: 'en',
strings: {
lineColor: 'Line',
fillColor: 'Fill',
lineWidth: 'Size',
textColor: 'Color',
fontSize: 'Size',
fontStyle: 'Style'
}
},
saveHandler: function (image, done) {
if (save) {
var entryPos = self.findInArray(domainObject.entries, entry.id),
embedPos = self.findInArray(entry.embeds, embed.id);
if (entryPos !== -1 && embedPos !== -1) {
var url = image.asBlob(),
reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
var snapshot = reader.result,
snapshotObject = {
src: snapshot,
type: url.type,
size: url.size,
modified: Date.now()
},
dirString = 'entries[' + entryPos + '].embeds[' + embedPos + '].snapshot';
openmct.objects.mutate(domainObject, dirString, snapshotObject);
};
}
} else {
console.log('You cancelled the annotation!!!');
}
done(true);
}
}).show(embed.snapshot.src);
};
}
var self = this, var self = this,
snapshot = new Vue({ snapshot = new Vue({
template: SnapshotTemplate, template: SnapshotTemplate,
@ -66,15 +157,22 @@ function (
}; };
}, },
methods: { methods: {
formatTime: self.formatTime formatTime: self.formatTime,
annotateSnapshot: annotateSnapshot(self.openmct),
findInArray: self.findInArray
} }
}); });
function onDestroyCallback() { function onDestroyCallback() {
snapshot.$destroy(true); snapshot.$destroy(true);
} }
var options = {
onDestroy: onDestroyCallback,
cssClass: 'l-large-view'
};
this.openmct.OverlayService.show(snapshot.$mount().$el, {onDestroy: onDestroyCallback, cssClass: 'l-large-view'});
this.openmct.OverlayService.show(snapshot.$mount().$el, options);
}; };
EmbedController.prototype.formatTime = function (unixTime, timeFormat) { EmbedController.prototype.formatTime = function (unixTime, timeFormat) {
@ -125,23 +223,20 @@ function (
var entryPosition = self.findInArray(self.domainObject.entries, entry.id), var entryPosition = self.findInArray(self.domainObject.entries, entry.id),
embedPosition = self.findInArray(entry.embeds, embed.id); embedPosition = self.findInArray(entry.embeds, embed.id);
var warningDialog = self.dialogService.showBlockingMessage({ self.openmct.OverlayService.showBlockingMessage({
severity: "error", severity: "error",
title: "This action will permanently delete this embed. Do you wish to continue?", actionText: 'This Action will permanently delete this embed. Do you wish to continue?',
options: [{ buttons: [{
label: "OK", label: "No",
callback: function () {}
},
{
label: "Yes",
callback: function () { callback: function () {
entry.embeds.splice(embedPosition, 1); entry.embeds.splice(embedPosition, 1);
var dirString = 'entries[' + entryPosition + '].embeds'; var dirString = 'entries[' + entryPosition + '].embeds';
self.openmct.objects.mutate(self.domainObject, dirString, entry.embeds); self.openmct.objects.mutate(self.domainObject, dirString, entry.embeds);
warningDialog.dismiss();
}
},{
label: "Cancel",
callback: function () {
warningDialog.dismiss();
} }
}] }]
}); });
@ -207,7 +302,8 @@ function (
openSnapshot: self.openSnapshot, openSnapshot: self.openSnapshot,
formatTime: self.formatTime, formatTime: self.formatTime,
toggleActionMenu: self.toggleActionMenu, toggleActionMenu: self.toggleActionMenu,
actionToMenuDecorator: self.actionToMenuDecorator actionToMenuDecorator: self.actionToMenuDecorator,
findInArray: self.findInArray
}; };
}; };

View File

@ -81,21 +81,18 @@ function (
if (entryPos !== -1) { if (entryPos !== -1) {
var errorDialog = this.dialogService.showBlockingMessage({ this.openmct.OverlayService.showBlockingMessage({
severity: "error", severity: "error",
title: "This action will permanently delete this Notebook entry. Do you wish to continue?", actionText: "This action will permanently delete this Notebook entry. Do you wish to continue?",
options: [{ buttons: [{
label: "OK", label: "No",
callback: function () {}
},
{
label: "Yes",
callback: function () { callback: function () {
domainObject.entries.splice(entryPos, 1); domainObject.entries.splice(entryPos, 1);
openmct.objects.mutate(domainObject, 'entries', domainObject.entries); openmct.objects.mutate(domainObject, 'entries', domainObject.entries);
errorDialog.dismiss();
}
},{
label: "Cancel",
callback: function () {
errorDialog.dismiss();
} }
}] }]
}); });

View File

@ -60,7 +60,7 @@ function (
this.container = container; this.container = container;
var notebookEmbed = { var notebookEmbed = {
inject:['openmct'], inject:['openmct', 'domainObject'],
props:['embed', 'entry'], props:['embed', 'entry'],
template: EmbedTemplate, template: EmbedTemplate,
data: embedController.exposedData, data: embedController.exposedData,
@ -81,7 +81,7 @@ function (
var notebookVue = Vue.extend({ var notebookVue = Vue.extend({
template: NotebookTemplate, template: NotebookTemplate,
provide: {openmct: self.openmct}, provide: {openmct: self.openmct, domainObject: self.domainObject},
components: { components: {
'notebook-entry': entryComponent, 'notebook-entry': entryComponent,
'search': search.default 'search': search.default

View File

@ -40,7 +40,8 @@ $inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
$menuLineH: 1.5rem; $menuLineH: 1.5rem;
$treeItemIndent: 16px; $treeItemIndent: 16px;
$treeTypeIconW: 18px; $treeTypeIconW: 18px;
$overlayOuterMargin: 5%; $overlayOuterMarginLg: 5%;
$overlayOuterMarginDialog: 20%;
$overlayInnerMargin: 25px; $overlayInnerMargin: 25px;
/*************** Items */ /*************** Items */

View File

@ -0,0 +1,91 @@
<template>
<div class="c-message"
v-bind:class="">
<!-- This element is displayed within the overlay service as well as in the list of messages
Uses flex-row -->
<div class="c-message__icon"
:class="['message-severity-' + model.severity]"></div>
<div class="c-message__text">
<!-- Uses flex-column -->
<div class="c-message__title"
v-if="model.title">
{{model.title}}
</div>
<div class="c-message__hint"
v-if="model.hint">
{{model.hint}}
<span v-if="model.timestamp">[{{model.timestamp}}]</span>
</div>
<div class="c-message__action-text"
v-if="model.actionText">
{{model.actionText}}
</div>
<div class="c-message__actions"
v-if="model.primaryOption">
<a class="c-button c-button--major"
@click="model.primaryOption.callback()">
{{model.primaryOption.label}}
</a>
</div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-message {
display: flex;
padding: $interiorMarginLg;
> * + * {
@include test();
margin-left: $interiorMarginLg;
}
&__icon {
$s: 50px;
flex: 0 0 auto;
min-width: $s;
min-height: $s;
&.message-severity {
// TEMP: TODO: replace with SVG background assets
&-alert {
background: $colorAlert;
}
&-error {
background: $colorFormError;
}
}
}
&__text {
display: flex;
flex-direction: column;
flex: 1 1 auto;
> * + * {
@include test();
margin-top: $interiorMargin;
}
}
// __text elements
&__title,
&__action-text {
font-size: 1.2em; // TEMP
}
}
</style>
<script>
export default {
inject:['model']
}
</script>

View File

@ -8,9 +8,19 @@
v-on:click="destroy"> v-on:click="destroy">
</button> </button>
<div class="c-overlay__contents" ref="element"></div> <div class="c-overlay__contents" ref="element"></div>
<div class="c-overlay__button-bar"> <div class="c-overlay__button-bar" v-if="!buttons">
<button class="c-button c-button--major" <button class="c-button c-button--major"
v-on:click="destroy">Done</button> v-on:click="destroy">
Done
</button>
</div>
<div class="c-overlay__button-bar" v-if="buttons">
<button class="c-button c-button--major"
v-for="(button, index) in buttons"
:key="index"
@click="buttonClickHandler(button.callback)">
{{button.label}}
</button>
</div> </div>
</div> </div>
</div> </div>
@ -19,6 +29,13 @@
<style lang="scss"> <style lang="scss">
@import "~styles/sass-base"; @import "~styles/sass-base";
@mixin overlaySizing($marginTB: 5%, $marginLR: $marginTB, $width: auto, $height: auto) {
position: absolute;
top: $marginTB; right: $marginLR; bottom: $marginTB; left: $marginLR;
width: $width;
height: $height;
}
.l-overlay-wrapper { .l-overlay-wrapper {
// Created by overlayService.js, contains this template. // Created by overlayService.js, contains this template.
// Acts as an anchor for one or more overlays. // Acts as an anchor for one or more overlays.
@ -62,7 +79,26 @@
margin-top: $interiorMargin; margin-top: $interiorMargin;
} }
body.desktop & {
// Overlay types, styling independent of platform.
.l-large-view & {
// Default
}
.l-dialog & {
//
}
.l-message & {
&__outer {
// background: orange;
}
}
}
body.desktop {
.c-overlay {
&__blocker { &__blocker {
@include abs(); @include abs();
background: $colorOvrBlocker; background: $colorOvrBlocker;
@ -71,21 +107,49 @@
} }
&__outer { &__outer {
$m: $overlayOuterMargin;
top: $m; right: $m; bottom: $m; left: $m;
border-radius: $overlayCr; border-radius: $overlayCr;
box-shadow: rgba(black, 0.5) 0 2px 25px; box-shadow: rgba(black, 0.5) 0 2px 25px;
// Defaults to l-large-view
@include overlaySizing($overlayOuterMarginLg);
} }
} }
// Overlay types, styling for desktop.
.l-large-view {
// Default
}
.l-dialog {
.c-overlay__outer {
@include overlaySizing($overlayOuterMarginDialog);
}
}
.l-message {
.c-overlay__outer {
@include overlaySizing(auto);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
} }
</style> </style>
<script> <script>
export default { export default {
inject: ['destroy', 'element'], inject: ['destroy', 'element', 'buttons'],
mounted() { mounted() {
this.$refs.element.appendChild(this.element); this.$refs.element.appendChild(this.element);
},
methods: {
buttonClickHandler: function (method) {
method();
this.destroy();
}
} }
} }
</script> </script>

View File

@ -22,15 +22,19 @@
define([ define([
'./overlay.vue', './overlay.vue',
'./blockingMessage.vue',
'vue' 'vue'
], function ( ], function (
OverlayComponent, OverlayComponent,
BlockingMessage,
Vue Vue
) { ) {
function OverlayService() { function OverlayService() {
this.activeOverlays = []; this.activeOverlays = [];
this.overlayId = 0; this.overlayId = 0;
this.showBlockingMessage = this.showBlockingMessage.bind(this);
} }
OverlayService.prototype.show = function (element, options) { OverlayService.prototype.show = function (element, options) {
@ -43,45 +47,96 @@ define([
component = new Vue({ component = new Vue({
provide: { provide: {
destroy: this.destroy.bind(this), destroy: this.destroy.bind(this),
element: element element: element,
buttons: options.buttons
}, },
components: { components: {
OverlayComponent: OverlayComponent.default OverlayComponent: OverlayComponent.default
}, },
template: '<overlay-component></overlay-component>' template: '<overlay-component></overlay-component>'
}); }),
dialog = {};
overlay.classList.add('l-overlay-wrapper', overlayTypeCssClass); overlay.classList.add('l-overlay-wrapper', overlayTypeCssClass);
document.body.appendChild(overlay); document.body.appendChild(overlay);
overlay.appendChild(component.$mount().$el); overlay.appendChild(component.$mount().$el);
this.activeOverlays.push({ var overlayObject = {
overlay: overlay, overlay: overlay,
component: component, component: component,
onDestroy: options.onDestroy, onDestroy: options.onDestroy,
id: this.overlayId id: this.overlayId,
}); dialog: dialog
};
dialog.dismiss = function () {
let pos = findInArray(overlayObject.id, this.activeOverlays);
if (pos !== -1) {
if (overlayObject.onDestroy && typeof overlayObject.onDestroy === 'function') {
overlayObject.onDestroy();
}
overlayObject.component.$destroy(true);
document.body.removeChild(overlayObject.overlay);
this.activeOverlays.splice(pos, 1);
if (this.activeOverlays.length) {
this.activeOverlays[this.activeOverlays.length - 1].overlay.classList.remove('invisible');
}
}
}.bind(this);
this.activeOverlays.push(overlayObject);
this.overlayId++; this.overlayId++;
return dialog;
}; };
OverlayService.prototype.destroy = function () { OverlayService.prototype.destroy = function () {
var lastActiveOverlayObject = this.activeOverlays.pop(), var lastActiveOverlayObject = this.activeOverlays[this.activeOverlays.length - 1];
lastActiveOverlay = lastActiveOverlayObject.overlay,
lastActiveComponent = lastActiveOverlayObject.component;
if (lastActiveOverlayObject.onDestroy && typeof lastActiveOverlayObject.onDestroy === 'function') { lastActiveOverlayObject.dialog.dismiss(lastActiveOverlayObject.id);
lastActiveOverlayObject.onDestroy();
}
lastActiveComponent.$destroy(true);
document.body.removeChild(lastActiveOverlay);
if (this.activeOverlays.length) {
this.activeOverlays[this.activeOverlays.length - 1].overlay.classList.remove('invisible');
}
}; };
OverlayService.prototype.showBlockingMessage = function (model) {
let component = new Vue({
provide: {
model: model
},
components: {
BlockingMessage: BlockingMessage.default
},
template: '<blocking-message></blocking-message>'
});
function destroy() {
component.$destroy(true);
}
let options = {
cssClass: 'l-message',
onDestroy: destroy,
buttons: model.buttons
};
return this.show(component.$mount().$el, options);
};
function findInArray(id, array) {
var found = -1;
array.forEach(function (o,i) {
if (o.id === id) {
found = i;
return;
}
});
return found;
}
return OverlayService; return OverlayService;
}); });