Feature/eslint plugin vue (#2548)

* Use eslint-plugin-vue to lint vue files
This commit is contained in:
David Tsay 2019-12-04 12:39:09 -08:00 committed by Andrew Henry
parent 14ce5e159b
commit 14a0f84c1b
99 changed files with 5818 additions and 4877 deletions

View File

@ -5,9 +5,16 @@ module.exports = {
"jasmine": true, "jasmine": true,
"amd": true "amd": true
}, },
"extends": "eslint:recommended", "globals": {
"parser": "babel-eslint", "_": "readonly"
},
"extends": [
"eslint:recommended",
"plugin:vue/recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": { "parserOptions": {
"parser": "babel-eslint",
"allowImportExportEverywhere": true, "allowImportExportEverywhere": true,
"ecmaVersion": 2015, "ecmaVersion": 2015,
"ecmaFeatures": { "ecmaFeatures": {
@ -58,7 +65,38 @@ module.exports = {
} }
], ],
"dot-notation": "error", "dot-notation": "error",
"indent": ["error", 4] "indent": ["error", 4],
"vue/html-indent": [
"error",
4,
{
"attribute": 1,
"baseIndent": 0,
"closeBracket": 0,
"alignAttributesVertically": true,
"ignores": []
}
],
"vue/html-self-closing": ["error",
{
"html": {
"void": "never",
"normal": "never",
"component": "always"
},
"svg": "always",
"math": "always"
}
],
"vue/max-attributes-per-line": ["error", {
"singleline": 1,
"multiline": {
"max": 1,
"allowFirstLine": true
}
}],
"vue/multiline-html-element-content-newline": "off",
"vue/singleline-html-element-content-newline": "off"
}, },
"overrides": [ "overrides": [
{ {

View File

@ -23,6 +23,7 @@
"d3-time": "1.0.x", "d3-time": "1.0.x",
"d3-time-format": "2.1.x", "d3-time-format": "2.1.x",
"eslint": "5.2.0", "eslint": "5.2.0",
"eslint-plugin-vue": "^6.0.0",
"eventemitter3": "^1.2.0", "eventemitter3": "^1.2.0",
"exports-loader": "^0.7.0", "exports-loader": "^0.7.0",
"express": "^4.13.1", "express": "^4.13.1",
@ -74,8 +75,8 @@
}, },
"scripts": { "scripts": {
"start": "node app.js", "start": "node app.js",
"lint": "eslint platform example src openmct.js", "lint": "eslint platform example src/**/*.{js,vue} openmct.js",
"lint:fix": "eslint platform example src openmct.js --fix", "lint:fix": "eslint platform example src/**/*.{js,vue} openmct.js --fix",
"build:prod": "NODE_ENV=production webpack", "build:prod": "NODE_ENV=production webpack",
"build:dev": "webpack", "build:dev": "webpack",
"build:watch": "webpack --watch", "build:watch": "webpack --watch",

View File

@ -1,20 +1,24 @@
<template> <template>
<div class="c-menu"> <div class="c-menu">
<ul> <ul>
<li v-for="action in actions" <li
:key="action.name" v-for="action in actions"
:class="action.cssClass" :key="action.name"
:title="action.description" :class="action.cssClass"
@click="action.invoke(objectPath)"> :title="action.description"
{{ action.name }} @click="action.invoke(objectPath)"
</li> >
<li v-if="actions.length === 0">No actions defined.</li> {{ action.name }}
</ul> </li>
</div> <li v-if="actions.length === 0">
No actions defined.
</li>
</ul>
</div>
</template> </template>
<script> <script>
export default { export default {
inject: ['actions', 'objectPath'] inject: ['actions', 'objectPath']
} }
</script> </script>

View File

@ -1,28 +1,36 @@
<template> <template>
<div class="c-message"> <div class="c-message">
<!--Uses flex-row --> <!--Uses flex-row -->
<div class="c-message__icon" <div
:class="['u-icon-bg-color-' + iconClass]"></div> class="c-message__icon"
<div class="c-message__text"> :class="['u-icon-bg-color-' + iconClass]"
<!-- Uses flex-column --> ></div>
<div class="c-message__title" <div class="c-message__text">
v-if="title"> <!-- Uses flex-column -->
{{title}} <div
</div> v-if="title"
class="c-message__title"
<div class="c-message__hint" >
v-if="hint"> {{ title }}
{{hint}}
<span v-if="timestamp">[{{timestamp}}]</span>
</div>
<div class="c-message__action-text"
v-if="message">
{{message}}
</div>
<slot></slot>
</div> </div>
<div
v-if="hint"
class="c-message__hint"
>
{{ hint }}
<span v-if="timestamp">[{{ timestamp }}]</span>
</div>
<div
v-if="message"
class="c-message__action-text"
>
{{ message }}
</div>
<slot></slot>
</div> </div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">

View File

@ -1,28 +1,57 @@
<template> <template>
<div class="c-overlay"> <div class="c-overlay">
<div class="c-overlay__blocker" <div
@click="destroy"> class="c-overlay__blocker"
</div> @click="destroy"
<div class="c-overlay__outer"> ></div>
<button class="c-click-icon c-overlay__close-button icon-x-in-circle" <div class="c-overlay__outer">
v-if="dismissable" <button
@click="destroy"> v-if="dismissable"
class="c-click-icon c-overlay__close-button icon-x-in-circle"
@click="destroy"
></button>
<div
ref="element"
class="c-overlay__contents"
></div>
<div
v-if="buttons"
class="c-overlay__button-bar"
>
<button
v-for="(button, index) in buttons"
:key="index"
class="c-button"
:class="{'c-button--major': button.emphasis}"
@click="buttonClickHandler(button.callback)"
>
{{ button.label }}
</button> </button>
<div class="c-overlay__contents" ref="element" tabindex="0"></div> <div
<div class="c-overlay__button-bar" v-if="buttons"> ref="element"
<button class="c-button" class="c-overlay__contents"
tabindex="0" tabindex="0"
ref="buttons" ></div>
v-for="(button, index) in buttons" <div
:key="index" v-if="buttons"
@focus="focusIndex=index" class="c-overlay__button-bar"
:class="{'c-button--major': focusIndex===index}" >
@click="buttonClickHandler(button.callback)"> <button
{{button.label}} v-for="(button, index) in buttons"
ref="buttons"
:key="index"
class="c-button"
tabindex="0"
:class="{'c-button--major': focusIndex===index}"
@focus="focusIndex=index"
@click="buttonClickHandler(button.callback)"
>
{{ button.label }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -174,50 +203,50 @@
</style> </style>
<script> <script>
export default { export default {
data: function () { data: function () {
return { return {
focusIndex: -1 focusIndex: -1
}; };
}, },
inject: ['dismiss', 'element', 'buttons', 'dismissable'], inject: ['dismiss', 'element', 'buttons', 'dismissable'],
mounted() { mounted() {
const element = this.$refs.element; const element = this.$refs.element;
element.appendChild(this.element); element.appendChild(this.element);
const elementForFocus = this.getElementForFocus() || element; const elementForFocus = this.getElementForFocus() || element;
this.$nextTick(() => { this.$nextTick(() => {
elementForFocus.focus(); elementForFocus.focus();
}); });
}, },
methods: { methods: {
destroy: function () { destroy: function () {
if (this.dismissable) { if (this.dismissable) {
this.dismiss(); this.dismiss();
}
},
buttonClickHandler: function (method) {
method();
this.$emit('destroy');
},
getElementForFocus: function () {
const defaultElement = this.$refs.element;;
if (!this.$refs.buttons) {
return defaultElement;
}
const focusButton = this.$refs.buttons.filter((button, index) => {
if (this.buttons[index].emphasis) {
this.focusIndex = index;
}
return this.buttons[index].emphasis;
});
if (!focusButton.length) {
return defaultElement;
}
return focusButton[0];
} }
},
buttonClickHandler: function (method) {
method();
this.$emit('destroy');
},
getElementForFocus: function () {
const defaultElement = this.$refs.element;
if (!this.$refs.buttons) {
return defaultElement;
}
const focusButton = this.$refs.buttons.filter((button, index) => {
if (this.buttons[index].emphasis) {
this.focusIndex = index;
}
return this.buttons[index].emphasis;
});
if (!focusButton.length) {
return defaultElement;
}
return focusButton[0];
} }
} }
}
</script> </script>

View File

@ -1,7 +1,7 @@
<template> <template>
<dialog-component> <dialog-component>
<progress-component :model="model"></progress-component> <progress-component :model="model" />
</dialog-component> </dialog-component>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -14,9 +14,14 @@ import DialogComponent from './DialogComponent.vue';
export default { export default {
components: { components: {
DialogComponent: DialogComponent, DialogComponent: DialogComponent,
ProgressComponent: ProgressComponent, ProgressComponent: ProgressComponent
}, },
inject:['iconClass', 'title', 'hint', 'timestamp', 'message'], inject:['iconClass', 'title', 'hint', 'timestamp', 'message'],
props:['model'] props: {
model: {
type: Object,
required: true
}
}
} }
</script> </script>

View File

@ -44,6 +44,7 @@ define([
return { return {
show: function (element) { show: function (element) {
component = new Vue({ component = new Vue({
el: element,
components: { components: {
LadTableSet: LadTableSet.default LadTableSet: LadTableSet.default
}, },
@ -52,7 +53,6 @@ define([
domainObject, domainObject,
objectPath objectPath
}, },
el: element,
template: '<lad-table-set></lad-table-set>' template: '<lad-table-set></lad-table-set>'
}); });
}, },

View File

@ -44,6 +44,7 @@ define([
return { return {
show: function (element) { show: function (element) {
component = new Vue({ component = new Vue({
el: element,
components: { components: {
LadTableComponent: LadTableComponent.default LadTableComponent: LadTableComponent.default
}, },
@ -52,7 +53,6 @@ define([
domainObject, domainObject,
objectPath objectPath
}, },
el: element,
template: '<lad-table-component></lad-table-component>' template: '<lad-table-component></lad-table-component>'
}); });
}, },

View File

@ -22,13 +22,13 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<tr @contextmenu.prevent="showContextMenu"> <tr @contextmenu.prevent="showContextMenu">
<td>{{name}}</td> <td>{{ name }}</td>
<td>{{timestamp}}</td> <td>{{ timestamp }}</td>
<td :class="valueClass"> <td :class="valueClass">
{{value}} {{ value }}
</td> </td>
</tr> </tr>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -44,7 +44,12 @@ const CONTEXT_MENU_ACTIONS = [
export default { export default {
inject: ['openmct', 'objectPath'], inject: ['openmct', 'objectPath'],
props: ['domainObject'], props: {
domainObject: {
type: Object,
required: true
}
},
data() { data() {
let currentObjectPath = this.objectPath.slice(); let currentObjectPath = this.objectPath.slice();
currentObjectPath.unshift(this.domainObject); currentObjectPath.unshift(this.domainObject);
@ -57,48 +62,16 @@ export default {
currentObjectPath currentObjectPath
} }
}, },
methods: {
updateValues(datum) {
this.timestamp = this.formats[this.timestampKey].format(datum);
this.value = this.formats[this.valueKey].format(datum);
var limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
}
},
updateName(name){
this.name = name;
},
updateTimeSystem(timeSystem) {
this.value = '---';
this.timestamp = '---';
this.valueClass = '';
this.timestampKey = timeSystem.key;
this.openmct
.telemetry
.request(this.domainObject, {strategy: 'latest'})
.then((array) => this.updateValues(array[array.length - 1]));
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
}
},
mounted() { mounted() {
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata); this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.limitEvaluator = openmct this.limitEvaluator = this.openmct
.telemetry .telemetry
.limitEvaluator(this.domainObject); .limitEvaluator(this.domainObject);
this.stopWatchingMutation = openmct this.stopWatchingMutation = this.openmct
.objects .objects
.observe( .observe(
this.domainObject, this.domainObject,
@ -129,6 +102,38 @@ export default {
this.stopWatchingMutation(); this.stopWatchingMutation();
this.unsubscribe(); this.unsubscribe();
this.openmct.off('timeSystem', this.updateTimeSystem); this.openmct.off('timeSystem', this.updateTimeSystem);
},
methods: {
updateValues(datum) {
this.timestamp = this.formats[this.timestampKey].format(datum);
this.value = this.formats[this.valueKey].format(datum);
var limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
}
},
updateName(name) {
this.name = name;
},
updateTimeSystem(timeSystem) {
this.value = '---';
this.timestamp = '---';
this.valueClass = '';
this.timestampKey = timeSystem.key;
this.openmct
.telemetry
.request(this.domainObject, {strategy: 'latest'})
.then((array) => this.updateValues(array[array.length - 1]));
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
}
} }
} }
</script> </script>

View File

@ -33,14 +33,13 @@
<lad-row <lad-row
v-for="item in items" v-for="item in items"
:key="item.key" :key="item.key"
:domainObject="item.domainObject"> :domain-object="item.domainObject"
</lad-row> />
</tbody> </tbody>
</table> </table>
</template> </template>
<script> <script>
import lodash from 'lodash';
import LadRow from './LADRow.vue'; import LadRow from './LADRow.vue';
export default { export default {
@ -53,6 +52,18 @@ export default {
items: [] items: []
} }
}, },
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.reorder);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.reorder);
},
methods: { methods: {
addItem(domainObject) { addItem(domainObject) {
let item = {}; let item = {};
@ -72,18 +83,6 @@ export default {
this.$set(this.items, reorderEvent.newIndex, oldItems[reorderEvent.oldIndex]); this.$set(this.items, reorderEvent.newIndex, oldItems[reorderEvent.oldIndex]);
}); });
} }
},
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.reorder);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.reorder);
} }
} }
</script> </script>

View File

@ -21,29 +21,34 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<table class="c-table c-lad-table"> <table class="c-table c-lad-table">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Timestamp</th> <th>Timestamp</th>
<th>Value</th> <th>Value</th>
</tr>
</thead>
<tbody>
<template
v-for="primary in primaryTelemetryObjects"
>
<tr
:key="primary.key"
class="c-table__group-header"
>
<td colspan="10">
{{ primary.domainObject.name }}
</td>
</tr> </tr>
</thead> <lad-row
<tbody> v-for="secondary in secondaryTelemetryObjects[primary.key]"
<template :key="secondary.key"
v-for="primary in primaryTelemetryObjects"> :domain-object="secondary.domainObject"
<tr class="c-table__group-header" />
:key="primary.key"> </template>
<td colspan="10">{{primary.domainObject.name}}</td> </tbody>
</tr> </table>
<lad-row
v-for="secondary in secondaryTelemetryObjects[primary.key]"
:key="secondary.key"
:domainObject="secondary.domainObject">
</lad-row>
</template>
</tbody>
</table>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -51,10 +56,9 @@
</style> </style>
<script> <script>
import lodash from 'lodash'; import LadRow from './LADRow.vue';
import LadRow from './LADRow.vue';
export default { export default {
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject'],
components: { components: {
LadRow LadRow
@ -66,6 +70,22 @@
compositions: [] compositions: []
} }
}, },
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addPrimary);
this.composition.on('remove', this.removePrimary);
this.composition.on('reorder', this.reorderPrimary);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.addPrimary);
this.composition.off('remove', this.removePrimary);
this.composition.off('reorder', this.reorderPrimary);
this.compositions.forEach(c => {
c.composition.off('add', c.addCallback);
c.composition.off('remove', c.removeCallback);
});
},
methods: { methods: {
addPrimary(domainObject) { addPrimary(domainObject) {
let primary = {}; let primary = {};
@ -75,7 +95,7 @@
this.$set(this.secondaryTelemetryObjects, primary.key, []); this.$set(this.secondaryTelemetryObjects, primary.key, []);
this.primaryTelemetryObjects.push(primary); this.primaryTelemetryObjects.push(primary);
let composition = openmct.composition.get(primary.domainObject), let composition = this.openmct.composition.get(primary.domainObject),
addCallback = this.addSecondary(primary), addCallback = this.addSecondary(primary),
removeCallback = this.removeSecondary(primary); removeCallback = this.removeSecondary(primary);
@ -121,23 +141,6 @@
this.$set(this.secondaryTelemetryObjects, primary.key, array); this.$set(this.secondaryTelemetryObjects, primary.key, array);
} }
} }
},
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addPrimary);
this.composition.on('remove', this.removePrimary);
this.composition.on('reorder', this.reorderPrimary);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.addPrimary);
this.composition.off('remove', this.removePrimary);
this.composition.off('reorder', this.reorderPrimary);
this.compositions.forEach(c => {
c.composition.off('add', c.addCallback);
c.composition.off('remove', c.removeCallback);
});
} }
} }
</script> </script>

View File

@ -1,9 +1,9 @@
<template> <template>
<div class="c-indicator c-indicator--clickable icon-clear-data s-status-caution"> <div class="c-indicator c-indicator--clickable icon-clear-data s-status-caution">
<span class="label c-indicator__label"> <span class="label c-indicator__label">
<button @click="globalClearEmit">Clear Data</button> <button @click="globalClearEmit">Clear Data</button>
</span> </span>
</div> </div>
</template> </template>
<script> <script>

View File

@ -55,11 +55,11 @@ define([
openmct, openmct,
objectPath objectPath
}, },
el: element,
components: { components: {
AlphanumericFormatView: AlphanumericFormatView.default AlphanumericFormatView: AlphanumericFormatView.default
}, },
template: '<alphanumeric-format-view></alphanumeric-format-view>', template: '<alphanumeric-format-view></alphanumeric-format-view>'
el: element
}); });
}, },
destroy: function () { destroy: function () {

View File

@ -21,70 +21,78 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-properties" v-if="isEditing"> <div
<div class="c-properties__header">Alphanumeric Format</div> v-if="isEditing"
<ul class="c-properties__section"> class="c-properties"
<li class="c-properties__row"> >
<div class="c-properties__label" title="Printf formatting for the selected telemetry"> <div class="c-properties__header">
<label for="telemetryPrintfFormat">Format</label> Alphanumeric Format
</div>
<div class="c-properties__value">
<input id="telemetryPrintfFormat"
type="text"
@change="formatTelemetry"
:value="telemetryFormat"
:placeholder="nonMixedFormat ? '' : 'Mixed'"
>
</div>
</li>
</ul>
</div> </div>
<ul class="c-properties__section">
<li class="c-properties__row">
<div
class="c-properties__label"
title="Printf formatting for the selected telemetry"
>
<label for="telemetryPrintfFormat">Format</label>
</div>
<div class="c-properties__value">
<input
id="telemetryPrintfFormat"
type="text"
:value="telemetryFormat"
:placeholder="nonMixedFormat ? '' : 'Mixed'"
@change="formatTelemetry"
>
</div>
</li>
</ul>
</div>
</template> </template>
<script> <script>
export default { export default {
inject: ['openmct'], inject: ['openmct'],
data() { data() {
let selectionPath = this.openmct.selection.get()[0]; return {
return { isEditing: this.openmct.editor.isEditing(),
isEditing: this.openmct.editor.isEditing(), telemetryFormat: undefined,
telemetryFormat: undefined, nonMixedFormat: false
nonMixedFormat: false }
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleEdit);
this.openmct.selection.on('change', this.handleSelection);
this.handleSelection(this.openmct.selection.get());
},
destroyed() {
this.openmct.editor.off('isEditing', this.toggleEdit);
this.openmct.selection.off('change', this.handleSelection);
},
methods: {
toggleEdit(isEditing) {
this.isEditing = isEditing;
},
formatTelemetry(event) {
let newFormat = event.currentTarget.value;
this.openmct.selection.get().forEach(selectionPath => {
selectionPath[0].context.updateTelemetryFormat(newFormat);
});
this.telemetryFormat = newFormat;
},
handleSelection(selection) {
if (selection.length === 0 || selection[0].length < 2) {
return;
} }
},
methods: {
toggleEdit(isEditing) {
this.isEditing = isEditing;
},
formatTelemetry(event) {
let newFormat = event.currentTarget.value;
this.openmct.selection.get().forEach(selectionPath => {
selectionPath[0].context.updateTelemetryFormat(newFormat);
});
this.telemetryFormat = newFormat;
},
handleSelection(selection) {
if (selection.length === 0 || selection[0].length < 2) {
return;
}
let format = selection[0][0].context.layoutItem.format; let format = selection[0][0].context.layoutItem.format;
this.nonMixedFormat = selection.every(selectionPath => { this.nonMixedFormat = selection.every(selectionPath => {
return selectionPath[0].context.layoutItem.format === format; return selectionPath[0].context.layoutItem.format === format;
}); });
this.telemetryFormat = this.nonMixedFormat ? format : ''; this.telemetryFormat = this.nonMixedFormat ? format : '';
}
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleEdit);
this.openmct.selection.on('change', this.handleSelection);
this.handleSelection(this.openmct.selection.get());
},
destroyed() {
this.openmct.editor.off('isEditing', this.toggleEdit);
this.openmct.selection.off('change', this.handleSelection);
} }
} }
}
</script> </script>

View File

@ -21,15 +21,18 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<layout-frame :item="item" <layout-frame
:grid-size="gridSize" :item="item"
@move="(gridDelta) => $emit('move', gridDelta)" :grid-size="gridSize"
@endMove="() => $emit('endMove')"> @move="(gridDelta) => $emit('move', gridDelta)"
<div class="c-box-view" @endMove="() => $emit('endMove')"
:style="style"> >
</div> <div
</layout-frame> class="c-box-view"
</template> :style="style"
></div>
</layout-frame>
</template>
<style lang="scss"> <style lang="scss">
@import '~styles/sass-base'; @import '~styles/sass-base';
@ -44,60 +47,70 @@
} }
</style> </style>
<script> <script>
import LayoutFrame from './LayoutFrame.vue' import LayoutFrame from './LayoutFrame.vue'
export default { export default {
makeDefinition() { makeDefinition() {
return {
fill: '#717171',
stroke: 'transparent',
x: 1,
y: 1,
width: 10,
height: 5
};
},
inject: ['openmct'],
components: {
LayoutFrame
},
props: {
item: {
type: Object,
required: true
},
gridSize: {
type: Array,
required: true,
validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number')
},
index: {
type: Number,
required: true
},
initSelect: Boolean
},
computed: {
style() {
return { return {
fill: '#717171', backgroundColor: this.item.fill,
stroke: 'transparent', border: '1px solid ' + this.item.stroke
x: 1,
y: 1,
width: 10,
height: 5
}; };
}, }
inject: ['openmct'], },
components: { watch: {
LayoutFrame index(newIndex) {
}, if (!this.context) {
props: { return;
item: Object,
gridSize: Array,
index: Number,
initSelect: Boolean
},
computed: {
style() {
return {
backgroundColor: this.item.fill,
border: '1px solid ' + this.item.stroke
};
} }
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex; this.context.index = newIndex;
} }
}, },
mounted() { mounted() {
this.context = { this.context = {
layoutItem: this.item, layoutItem: this.item,
index: this.index index: this.index
}; };
this.removeSelectable = this.openmct.selection.selectable( this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect); this.$el, this.context, this.initSelect);
}, },
destroyed() { destroyed() {
if (this.removeSelectable) { if (this.removeSelectable) {
this.removeSelectable(); this.removeSelectable();
}
} }
} }
</script> }
</script>

View File

@ -21,42 +21,49 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="l-layout" <div
@dragover="handleDragOver" class="l-layout"
@click.capture="bypassSelection" :class="{
@drop="handleDrop" 'is-multi-selected': selectedLayoutItems.length > 1
:class="{ }"
'is-multi-selected': selectedLayoutItems.length > 1 @dragover="handleDragOver"
}"> @click.capture="bypassSelection"
<!-- Background grid --> @drop="handleDrop"
<div class="l-layout__grid-holder c-grid"> >
<div class="c-grid__x l-grid l-grid-x" <!-- Background grid -->
v-if="gridSize[0] >= 3" <div class="l-layout__grid-holder c-grid">
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]"> <div
</div> v-if="gridSize[0] >= 3"
<div class="c-grid__y l-grid l-grid-y" class="c-grid__x l-grid l-grid-x"
v-if="gridSize[1] >= 3" :style="[{ backgroundSize: gridSize[0] + 'px 100%' }]"
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div> ></div>
</div> <div
<component v-for="(item, index) in layoutItems" v-if="gridSize[1] >= 3"
:is="item.type" class="c-grid__y l-grid l-grid-y"
:item="item" :style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"
:key="item.id" ></div>
:gridSize="gridSize"
:initSelect="initSelectIndex === index"
:index="index"
:multiSelect="selectedLayoutItems.length > 1"
@move="move"
@endMove="endMove"
@endLineResize='endLineResize'
@formatChanged='updateTelemetryFormat'>
</component>
<edit-marquee v-if='showMarquee'
:gridSize="gridSize"
:selectedLayoutItems="selectedLayoutItems"
@endResize="endResize">
</edit-marquee>
</div> </div>
<component
:is="item.type"
v-for="(item, index) in layoutItems"
:key="item.id"
:item="item"
:grid-size="gridSize"
:init-select="initSelectIndex === index"
:index="index"
:multi-select="selectedLayoutItems.length > 1"
@move="move"
@endMove="endMove"
@endLineResize="endLineResize"
@formatChanged="updateTelemetryFormat"
/>
<edit-marquee
v-if="showMarquee"
:grid-size="gridSize"
:selected-layout-items="selectedLayoutItems"
@endResize="endResize"
/>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -135,452 +142,455 @@
</style> </style>
<script> <script>
import uuid from 'uuid'; import uuid from 'uuid';
import SubobjectView from './SubobjectView.vue' import SubobjectView from './SubobjectView.vue'
import TelemetryView from './TelemetryView.vue' import TelemetryView from './TelemetryView.vue'
import BoxView from './BoxView.vue' import BoxView from './BoxView.vue'
import TextView from './TextView.vue' import TextView from './TextView.vue'
import LineView from './LineView.vue' import LineView from './LineView.vue'
import ImageView from './ImageView.vue' import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue' import EditMarquee from './EditMarquee.vue'
const ITEM_TYPE_VIEW_MAP = { const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView, 'subobject-view': SubobjectView,
'telemetry-view': TelemetryView, 'telemetry-view': TelemetryView,
'box-view': BoxView, 'box-view': BoxView,
'line-view': LineView, 'line-view': LineView,
'text-view': TextView, 'text-view': TextView,
'image-view': ImageView 'image-view': ImageView
}; };
const ORDERS = { const ORDERS = {
top: Number.POSITIVE_INFINITY, top: Number.POSITIVE_INFINITY,
up: 1, up: 1,
down: -1, down: -1,
bottom: Number.NEGATIVE_INFINITY bottom: Number.NEGATIVE_INFINITY
}; };
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/'; const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
let components = ITEM_TYPE_VIEW_MAP; let components = ITEM_TYPE_VIEW_MAP;
components['edit-marquee'] = EditMarquee; components['edit-marquee'] = EditMarquee;
function getItemDefinition(itemType, ...options) { function getItemDefinition(itemType, ...options) {
let itemView = ITEM_TYPE_VIEW_MAP[itemType]; let itemView = ITEM_TYPE_VIEW_MAP[itemType];
if (!itemView) { if (!itemView) {
throw `Invalid itemType: ${itemType}`; throw `Invalid itemType: ${itemType}`;
}
return itemView.makeDefinition(...options);
} }
export default { return itemView.makeDefinition(...options);
data() { }
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
return { export default {
internalDomainObject: domainObject, components: components,
initSelectIndex: undefined, props: {
selection: [] domainObject: {
}; type: Object,
required: true
}
},
data() {
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
return {
internalDomainObject: domainObject,
initSelectIndex: undefined,
selection: []
};
},
computed: {
gridSize() {
return this.internalDomainObject.configuration.layoutGrid;
}, },
computed: { layoutItems() {
gridSize() { return this.internalDomainObject.configuration.items;
return this.internalDomainObject.configuration.layoutGrid; },
}, selectedLayoutItems() {
layoutItems() { return this.layoutItems.filter(item => {
return this.internalDomainObject.configuration.items; return this.itemIsInCurrentSelection(item);
}, });
selectedLayoutItems() { },
return this.layoutItems.filter(item => { showMarquee() {
return this.itemIsInCurrentSelection(item); let selectionPath = this.selection[0];
}); let singleSelectedLine = this.selection.length === 1 &&
},
showMarquee() {
let selectionPath = this.selection[0];
let singleSelectedLine = this.selection.length === 1 &&
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view'; selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type === 'line-view';
return selectionPath && selectionPath.length > 1 && !singleSelectedLine; return selectionPath && selectionPath.length > 1 && !singleSelectedLine;
}
},
inject: ['openmct', 'options', 'objectPath'],
mounted() {
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) {
this.internalDomainObject = JSON.parse(JSON.stringify(obj));
}.bind(this));
this.openmct.selection.on('change', this.setSelection);
this.initializeItems();
this.composition = this.openmct.composition.get(this.internalDomainObject);
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);
this.composition.load();
},
destroyed: function () {
this.openmct.selection.off('change', this.setSelection);
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
this.unlisten();
},
methods: {
addElement(itemType, element) {
this.addItem(itemType + '-view', element);
},
setSelection(selection) {
this.selection = selection;
},
itemIsInCurrentSelection(item) {
return this.selection.some(selectionPath =>
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.id === item.id);
},
bypassSelection($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopImmediatePropagation();
}
this.dragInProgress = false;
return;
} }
}, },
inject: ['openmct', 'options', 'objectPath'], endLineResize(item, updates) {
props: ['domainObject'], this.dragInProgress = true;
components: components, let index = this.layoutItems.indexOf(item);
methods: { Object.assign(item, updates);
addElement(itemType, element) { this.mutate(`configuration.items[${index}]`, item);
this.addItem(itemType + '-view', element); },
}, endResize(scaleWidth, scaleHeight, marqueeStart, marqueeOffset) {
setSelection(selection) { this.dragInProgress = true;
this.selection = selection; this.layoutItems.forEach(item => {
}, if (this.itemIsInCurrentSelection(item)) {
itemIsInCurrentSelection(item) { let itemXInMarqueeSpace = item.x - marqueeStart.x;
return this.selection.some(selectionPath => let itemXInMarqueeSpaceAfterScale = Math.round(itemXInMarqueeSpace * scaleWidth);
selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.id === item.id); item.x = itemXInMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x;
},
bypassSelection($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopImmediatePropagation();
}
this.dragInProgress = false;
return;
}
},
endLineResize(item, updates) {
this.dragInProgress = true;
let index = this.layoutItems.indexOf(item);
Object.assign(item, updates);
this.mutate(`configuration.items[${index}]`, item);
},
endResize(scaleWidth, scaleHeight, marqueeStart, marqueeOffset) {
this.dragInProgress = true;
this.layoutItems.forEach(item => {
if (this.itemIsInCurrentSelection(item)) {
let itemXInMarqueeSpace = item.x - marqueeStart.x;
let itemXInMarqueeSpaceAfterScale = Math.round(itemXInMarqueeSpace * scaleWidth);
item.x = itemXInMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x;
let itemYInMarqueeSpace = item.y - marqueeStart.y; let itemYInMarqueeSpace = item.y - marqueeStart.y;
let itemYInMarqueeSpaceAfterScale = Math.round(itemYInMarqueeSpace * scaleHeight); let itemYInMarqueeSpaceAfterScale = Math.round(itemYInMarqueeSpace * scaleHeight);
item.y = itemYInMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y; item.y = itemYInMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
if (item.x2) { if (item.x2) {
let itemX2InMarqueeSpace = item.x2 - marqueeStart.x; let itemX2InMarqueeSpace = item.x2 - marqueeStart.x;
let itemX2InMarqueeSpaceAfterScale = Math.round(itemX2InMarqueeSpace * scaleWidth); let itemX2InMarqueeSpaceAfterScale = Math.round(itemX2InMarqueeSpace * scaleWidth);
item.x2 = itemX2InMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x; item.x2 = itemX2InMarqueeSpaceAfterScale + marqueeOffset.x + marqueeStart.x;
} else {
item.width = Math.round(item.width * scaleWidth);
}
if (item.y2) {
let itemY2InMarqueeSpace = item.y2 - marqueeStart.y;
let itemY2InMarqueeSpaceAfterScale = Math.round(itemY2InMarqueeSpace * scaleHeight);
item.y2 = itemY2InMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
} else {
item.height = Math.round(item.height * scaleHeight);
}
}
});
this.mutate("configuration.items", this.layoutItems);
},
move(gridDelta) {
this.dragInProgress = true;
if (!this.initialPositions) {
this.initialPositions = {};
_.cloneDeep(this.selectedLayoutItems).forEach(selectedItem => {
if (selectedItem.type === 'line-view') {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y, selectedItem.x2, selectedItem.y2];
this.startingMinX2 = this.startingMinX2 !== undefined ? Math.min(this.startingMinX2, selectedItem.x2) : selectedItem.x2;
this.startingMinY2 = this.startingMinY2 !== undefined ? Math.min(this.startingMinY2, selectedItem.y2) : selectedItem.y2;
} else {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y];
}
this.startingMinX = this.startingMinX !== undefined ? Math.min(this.startingMinX, selectedItem.x) : selectedItem.x;
this.startingMinY = this.startingMinY !== undefined ? Math.min(this.startingMinY, selectedItem.y) : selectedItem.y;
});
}
let layoutItems = this.layoutItems.map(item => {
if (this.initialPositions[item.id]) {
this.updateItemPosition(item, gridDelta);
}
return item;
});
},
updateItemPosition(item, gridDelta) {
let startingPosition = this.initialPositions[item.id];
let [startingX, startingY, startingX2, startingY2] = startingPosition;
if (this.startingMinX + gridDelta[0] >= 0) {
if (item.x2 !== undefined) {
if (this.startingMinX2 + gridDelta[0] >= 0) {
item.x = startingX + gridDelta[0];
}
} else { } else {
item.width = Math.round(item.width * scaleWidth);
}
if (item.y2) {
let itemY2InMarqueeSpace = item.y2 - marqueeStart.y;
let itemY2InMarqueeSpaceAfterScale = Math.round(itemY2InMarqueeSpace * scaleHeight);
item.y2 = itemY2InMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
} else {
item.height = Math.round(item.height * scaleHeight);
}
}
});
this.mutate("configuration.items", this.layoutItems);
},
move(gridDelta) {
this.dragInProgress = true;
if (!this.initialPositions) {
this.initialPositions = {};
_.cloneDeep(this.selectedLayoutItems).forEach(selectedItem => {
if (selectedItem.type === 'line-view') {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y, selectedItem.x2, selectedItem.y2];
this.startingMinX2 = this.startingMinX2 !== undefined ? Math.min(this.startingMinX2, selectedItem.x2) : selectedItem.x2;
this.startingMinY2 = this.startingMinY2 !== undefined ? Math.min(this.startingMinY2, selectedItem.y2) : selectedItem.y2;
} else {
this.initialPositions[selectedItem.id] = [selectedItem.x, selectedItem.y];
}
this.startingMinX = this.startingMinX !== undefined ? Math.min(this.startingMinX, selectedItem.x) : selectedItem.x;
this.startingMinY = this.startingMinY !== undefined ? Math.min(this.startingMinY, selectedItem.y) : selectedItem.y;
});
}
this.layoutItems.forEach(item => {
if (this.initialPositions[item.id]) {
this.updateItemPosition(item, gridDelta);
}
});
},
updateItemPosition(item, gridDelta) {
let startingPosition = this.initialPositions[item.id];
let [startingX, startingY, startingX2, startingY2] = startingPosition;
if (this.startingMinX + gridDelta[0] >= 0) {
if (item.x2 !== undefined) {
if (this.startingMinX2 + gridDelta[0] >= 0) {
item.x = startingX + gridDelta[0]; item.x = startingX + gridDelta[0];
} }
} else {
item.x = startingX + gridDelta[0];
} }
}
if (this.startingMinY + gridDelta[1] >= 0) { if (this.startingMinY + gridDelta[1] >= 0) {
if (item.y2 !== undefined) { if (item.y2 !== undefined) {
if (this.startingMinY2 + gridDelta[1] >= 0) { if (this.startingMinY2 + gridDelta[1] >= 0) {
item.y = startingY + gridDelta[1];
}
} else {
item.y = startingY + gridDelta[1]; item.y = startingY + gridDelta[1];
} }
}
if (item.x2 !== undefined && this.startingMinX2 + gridDelta[0] >= 0 && this.startingMinX + gridDelta[0] >= 0) {
item.x2 = startingX2 + gridDelta[0];
}
if (item.y2 !== undefined && this.startingMinY2 + gridDelta[1] >= 0 && this.startingMinY + gridDelta[1] >= 0) {
item.y2 = startingY2 + gridDelta[1];
}
},
endMove() {
this.mutate('configuration.items', this.layoutItems);
this.initialPositions = undefined;
this.startingMinX = undefined;
this.startingMinY = undefined;
this.startingMinX2 = undefined;
this.startingMinY2 = undefined;
},
mutate(path, value) {
this.openmct.objects.mutate(this.internalDomainObject, path, value);
},
handleDrop($event) {
if (!$event.dataTransfer.types.includes('openmct/domain-object-path')) {
return;
}
$event.preventDefault();
let domainObject = JSON.parse($event.dataTransfer.getData('openmct/domain-object-path'))[0];
let elementRect = this.$el.getBoundingClientRect();
let droppedObjectPosition = [
Math.floor(($event.pageX - elementRect.left) / this.gridSize[0]),
Math.floor(($event.pageY - elementRect.top) / this.gridSize[1])
];
if (this.isTelemetry(domainObject)) {
this.addItem('telemetry-view', domainObject, droppedObjectPosition);
} else { } else {
let identifier = this.openmct.objects.makeKeyString(domainObject.identifier); item.y = startingY + gridDelta[1];
if (!this.objectViewMap[identifier]) {
this.addItem('subobject-view', domainObject, droppedObjectPosition);
} else {
let prompt = this.openmct.overlays.dialog({
iconClass: 'alert',
message: "This item is already in layout and will not be added again.",
buttons: [
{
label: 'OK',
callback: function () {
prompt.dismiss();
}
}
]
});
}
} }
}, }
containsObject(identifier) {
return _.get(this.internalDomainObject, 'composition') if (item.x2 !== undefined && this.startingMinX2 + gridDelta[0] >= 0 && this.startingMinX + gridDelta[0] >= 0) {
.some(childId => this.openmct.objects.areIdsEqual(childId, identifier)); item.x2 = startingX2 + gridDelta[0];
}, }
handleDragOver($event) {
// Get the ID of the dragged object if (item.y2 !== undefined && this.startingMinY2 + gridDelta[1] >= 0 && this.startingMinY + gridDelta[1] >= 0) {
let draggedKeyString = $event.dataTransfer.types item.y2 = startingY2 + gridDelta[1];
.filter(type => type.startsWith(DRAG_OBJECT_TRANSFER_PREFIX)) }
.map(type => type.substring(DRAG_OBJECT_TRANSFER_PREFIX.length))[0]; },
endMove() {
this.mutate('configuration.items', this.layoutItems);
this.initialPositions = undefined;
this.startingMinX = undefined;
this.startingMinY = undefined;
this.startingMinX2 = undefined;
this.startingMinY2 = undefined;
},
mutate(path, value) {
this.openmct.objects.mutate(this.internalDomainObject, path, value);
},
handleDrop($event) {
if (!$event.dataTransfer.types.includes('openmct/domain-object-path')) {
return;
}
$event.preventDefault();
let domainObject = JSON.parse($event.dataTransfer.getData('openmct/domain-object-path'))[0];
let elementRect = this.$el.getBoundingClientRect();
let droppedObjectPosition = [
Math.floor(($event.pageX - elementRect.left) / this.gridSize[0]),
Math.floor(($event.pageY - elementRect.top) / this.gridSize[1])
];
if (this.isTelemetry(domainObject)) {
this.addItem('telemetry-view', domainObject, droppedObjectPosition);
} else {
let identifier = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.objectViewMap[identifier]) {
this.addItem('subobject-view', domainObject, droppedObjectPosition);
} else {
let prompt = this.openmct.overlays.dialog({
iconClass: 'alert',
message: "This item is already in layout and will not be added again.",
buttons: [
{
label: 'OK',
callback: function () {
prompt.dismiss();
}
}
]
});
}
}
},
containsObject(identifier) {
return _.get(this.internalDomainObject, 'composition')
.some(childId => this.openmct.objects.areIdsEqual(childId, identifier));
},
handleDragOver($event) {
// Get the ID of the dragged object
let draggedKeyString = $event.dataTransfer.types
.filter(type => type.startsWith(DRAG_OBJECT_TRANSFER_PREFIX))
.map(type => type.substring(DRAG_OBJECT_TRANSFER_PREFIX.length))[0];
// If the layout already contains the given object, then shortcut the default dragover behavior and // If the layout already contains the given object, then shortcut the default dragover behavior and
// potentially allow drop. Display layouts allow drag drop of duplicate telemetry objects. // potentially allow drop. Display layouts allow drag drop of duplicate telemetry objects.
if (this.containsObject(draggedKeyString)){ if (this.containsObject(draggedKeyString)) {
$event.preventDefault(); $event.preventDefault();
}
},
isTelemetry(domainObject) {
if (this.openmct.telemetry.isTelemetryObject(domainObject) &&
!this.options.showAsView.includes(domainObject.type)) {
return true;
} else {
return false;
}
},
addItem(itemType, ...options) {
let item = getItemDefinition(itemType, this.openmct, this.gridSize, ...options);
item.type = itemType;
item.id = uuid();
this.trackItem(item);
this.layoutItems.push(item);
this.openmct.objects.mutate(this.internalDomainObject, "configuration.items", this.layoutItems);
this.initSelectIndex = this.layoutItems.length - 1;
},
trackItem(item) {
if (!item.identifier) {
return;
}
let keyString = this.openmct.objects.makeKeyString(item.identifier);
if (item.type === "telemetry-view") {
let count = this.telemetryViewMap[keyString] || 0;
this.telemetryViewMap[keyString] = ++count;
} else if (item.type === "subobject-view") {
this.objectViewMap[keyString] = true;
}
},
removeItem(selectedItems) {
let indices = [];
this.initSelectIndex = -1;
selectedItems.forEach(selectedItem => {
indices.push(selectedItem[0].context.index);
this.untrackItem(selectedItem[0].context.layoutItem);
});
_.pullAt(this.layoutItems, indices);
this.mutate("configuration.items", this.layoutItems);
this.$el.click();
},
untrackItem(item) {
if (!item.identifier) {
return;
}
let keyString = this.openmct.objects.makeKeyString(item.identifier);
if (item.type === 'telemetry-view') {
let count = --this.telemetryViewMap[keyString];
if (count === 0) {
delete this.telemetryViewMap[keyString];
this.removeFromComposition(keyString);
}
} else if (item.type === 'subobject-view') {
delete this.objectViewMap[keyString];
this.removeFromComposition(keyString);
}
},
removeFromComposition(keyString) {
let composition = _.get(this.internalDomainObject, 'composition');
composition = composition.filter(identifier => {
return this.openmct.objects.makeKeyString(identifier) !== keyString;
});
this.mutate("composition", composition);
},
initializeItems() {
this.telemetryViewMap = {};
this.objectViewMap = {};
this.layoutItems.forEach(this.trackItem);
},
addChild(child) {
let identifier = this.openmct.objects.makeKeyString(child.identifier);
if (this.isTelemetry(child)) {
if (!this.telemetryViewMap[identifier]) {
this.addItem('telemetry-view', child);
}
} else if (!this.objectViewMap[identifier]) {
this.addItem('subobject-view', child);
}
},
removeChild(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
if (this.objectViewMap[keyString]) {
delete this.objectViewMap[keyString];
this.removeFromConfiguration(keyString);
} else if (this.telemetryViewMap[keyString]) {
delete this.telemetryViewMap[keyString];
this.removeFromConfiguration(keyString);
}
},
removeFromConfiguration(keyString) {
let layoutItems = this.layoutItems.filter(item => {
if (!item.identifier) {
return true;
} else {
return this.openmct.objects.makeKeyString(item.identifier) !== keyString;
}
});
this.mutate("configuration.items", layoutItems);
this.$el.click();
},
orderItem(position, selectedItems) {
let delta = ORDERS[position];
let indices = [];
let newIndex = -1;
let items = [];
Object.assign(items, this.layoutItems);
this.selectedLayoutItems.forEach(selectedItem => {
indices.push(this.layoutItems.indexOf(selectedItem));
});
indices.sort((a, b) => a - b);
if (position === 'top' || position === 'up') {
indices.reverse();
}
if (position === 'top' || position === 'bottom') {
this.moveToTopOrBottom(position, indices, items, delta);
} else {
this.moveUpOrDown(position, indices, items, delta);
}
this.mutate('configuration.items', this.layoutItems);
},
moveUpOrDown(position, indices, items, delta) {
let previousItemIndex = -1;
let newIndex = -1;
indices.forEach((itemIndex, index) => {
let isAdjacentItemSelected = position === 'up' ?
itemIndex + 1 === previousItemIndex :
itemIndex - 1 === previousItemIndex;
if (index > 0 && isAdjacentItemSelected) {
if (position === 'up') {
newIndex -= 1;
} else {
newIndex += 1;
}
} else {
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
}
previousItemIndex = itemIndex;
this.updateItemOrder(newIndex, itemIndex, items);
});
},
moveToTopOrBottom(position, indices, items, delta) {
let newIndex = -1;
indices.forEach((itemIndex, index) => {
if (index === 0) {
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
} else {
if (position === 'top') {
newIndex -= 1;
} else {
newIndex += 1;
}
}
this.updateItemOrder(newIndex, itemIndex, items);
});
},
updateItemOrder(newIndex, itemIndex, items) {
if (newIndex !== itemIndex) {
this.layoutItems.splice(itemIndex, 1);
this.layoutItems.splice(newIndex, 0, items[itemIndex]);
}
},
updateTelemetryFormat(item, format) {
let index = _.findIndex(this.layoutItems, item);
item.format = format;
this.mutate(`configuration.items[${index}]`, item);
} }
}, },
mounted() { isTelemetry(domainObject) {
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', function (obj) { if (this.openmct.telemetry.isTelemetryObject(domainObject) &&
this.internalDomainObject = JSON.parse(JSON.stringify(obj)); !this.options.showAsView.includes(domainObject.type)) {
}.bind(this)); return true;
this.openmct.selection.on('change', this.setSelection); } else {
this.initializeItems(); return false;
this.composition = this.openmct.composition.get(this.internalDomainObject); }
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);
this.composition.load();
}, },
destroyed: function () { addItem(itemType, ...options) {
this.openmct.selection.off('change', this.setSelection); let item = getItemDefinition(itemType, this.openmct, this.gridSize, ...options);
this.composition.off('add', this.addChild); item.type = itemType;
this.composition.off('remove', this.removeChild); item.id = uuid();
this.unlisten(); this.trackItem(item);
this.layoutItems.push(item);
this.openmct.objects.mutate(this.internalDomainObject, "configuration.items", this.layoutItems);
this.initSelectIndex = this.layoutItems.length - 1;
},
trackItem(item) {
if (!item.identifier) {
return;
}
let keyString = this.openmct.objects.makeKeyString(item.identifier);
if (item.type === "telemetry-view") {
let count = this.telemetryViewMap[keyString] || 0;
this.telemetryViewMap[keyString] = ++count;
} else if (item.type === "subobject-view") {
this.objectViewMap[keyString] = true;
}
},
removeItem(selectedItems) {
let indices = [];
this.initSelectIndex = -1;
selectedItems.forEach(selectedItem => {
indices.push(selectedItem[0].context.index);
this.untrackItem(selectedItem[0].context.layoutItem);
});
_.pullAt(this.layoutItems, indices);
this.mutate("configuration.items", this.layoutItems);
this.$el.click();
},
untrackItem(item) {
if (!item.identifier) {
return;
}
let keyString = this.openmct.objects.makeKeyString(item.identifier);
if (item.type === 'telemetry-view') {
let count = --this.telemetryViewMap[keyString];
if (count === 0) {
delete this.telemetryViewMap[keyString];
this.removeFromComposition(keyString);
}
} else if (item.type === 'subobject-view') {
delete this.objectViewMap[keyString];
this.removeFromComposition(keyString);
}
},
removeFromComposition(keyString) {
let composition = _.get(this.internalDomainObject, 'composition');
composition = composition.filter(identifier => {
return this.openmct.objects.makeKeyString(identifier) !== keyString;
});
this.mutate("composition", composition);
},
initializeItems() {
this.telemetryViewMap = {};
this.objectViewMap = {};
this.layoutItems.forEach(this.trackItem);
},
addChild(child) {
let identifier = this.openmct.objects.makeKeyString(child.identifier);
if (this.isTelemetry(child)) {
if (!this.telemetryViewMap[identifier]) {
this.addItem('telemetry-view', child);
}
} else if (!this.objectViewMap[identifier]) {
this.addItem('subobject-view', child);
}
},
removeChild(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
if (this.objectViewMap[keyString]) {
delete this.objectViewMap[keyString];
this.removeFromConfiguration(keyString);
} else if (this.telemetryViewMap[keyString]) {
delete this.telemetryViewMap[keyString];
this.removeFromConfiguration(keyString);
}
},
removeFromConfiguration(keyString) {
let layoutItems = this.layoutItems.filter(item => {
if (!item.identifier) {
return true;
} else {
return this.openmct.objects.makeKeyString(item.identifier) !== keyString;
}
});
this.mutate("configuration.items", layoutItems);
this.$el.click();
},
orderItem(position, selectedItems) {
let delta = ORDERS[position];
let indices = [];
let items = [];
Object.assign(items, this.layoutItems);
this.selectedLayoutItems.forEach(selectedItem => {
indices.push(this.layoutItems.indexOf(selectedItem));
});
indices.sort((a, b) => a - b);
if (position === 'top' || position === 'up') {
indices.reverse();
}
if (position === 'top' || position === 'bottom') {
this.moveToTopOrBottom(position, indices, items, delta);
} else {
this.moveUpOrDown(position, indices, items, delta);
}
this.mutate('configuration.items', this.layoutItems);
},
moveUpOrDown(position, indices, items, delta) {
let previousItemIndex = -1;
let newIndex = -1;
indices.forEach((itemIndex, index) => {
let isAdjacentItemSelected = position === 'up' ?
itemIndex + 1 === previousItemIndex :
itemIndex - 1 === previousItemIndex;
if (index > 0 && isAdjacentItemSelected) {
if (position === 'up') {
newIndex -= 1;
} else {
newIndex += 1;
}
} else {
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
}
previousItemIndex = itemIndex;
this.updateItemOrder(newIndex, itemIndex, items);
});
},
moveToTopOrBottom(position, indices, items, delta) {
let newIndex = -1;
indices.forEach((itemIndex, index) => {
if (index === 0) {
newIndex = Math.max(Math.min(itemIndex + delta, this.layoutItems.length - 1), 0);
} else {
if (position === 'top') {
newIndex -= 1;
} else {
newIndex += 1;
}
}
this.updateItemOrder(newIndex, itemIndex, items);
});
},
updateItemOrder(newIndex, itemIndex, items) {
if (newIndex !== itemIndex) {
this.layoutItems.splice(itemIndex, 1);
this.layoutItems.splice(newIndex, 0, items[itemIndex]);
}
},
updateTelemetryFormat(item, format) {
let index = _.findIndex(this.layoutItems, item);
item.format = format;
this.mutate(`configuration.items[${index}]`, item);
} }
} }
}
</script> </script>

View File

@ -21,17 +21,28 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<!-- Resize handles --> <!-- Resize handles -->
<div class="c-frame-edit" :style="style"> <div
<div class="c-frame-edit__handle c-frame-edit__handle--nw" class="c-frame-edit"
@mousedown="startResize([1,1], [-1,-1], $event)"></div> :style="marqueeStyle"
<div class="c-frame-edit__handle c-frame-edit__handle--ne" >
@mousedown="startResize([0,1], [1,-1], $event)"></div> <div
<div class="c-frame-edit__handle c-frame-edit__handle--sw" class="c-frame-edit__handle c-frame-edit__handle--nw"
@mousedown="startResize([1,0], [-1,1], $event)"></div> @mousedown="startResize([1,1], [-1,-1], $event)"
<div class="c-frame-edit__handle c-frame-edit__handle--se" ></div>
@mousedown="startResize([0,0], [1,1], $event)"></div> <div
</div> class="c-frame-edit__handle c-frame-edit__handle--ne"
@mousedown="startResize([0,1], [1,-1], $event)"
></div>
<div
class="c-frame-edit__handle c-frame-edit__handle--sw"
@mousedown="startResize([1,0], [-1,1], $event)"
></div>
<div
class="c-frame-edit__handle c-frame-edit__handle--se"
@mousedown="startResize([0,0], [1,1], $event)"
></div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -95,139 +106,146 @@
<script> <script>
import LayoutDrag from './../LayoutDrag' import LayoutDrag from './../LayoutDrag'
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
selectedLayoutItems: Array, selectedLayoutItems: {
gridSize: Array type: Array,
default: undefined
}, },
data() { gridSize: {
return { type: Array,
dragPosition: undefined required: true,
} validator: (arr) => arr && arr.length === 2
}, && arr.every(el => typeof el === 'number')
computed: { }
style() { },
let x = Number.POSITIVE_INFINITY; data() {
let y = Number.POSITIVE_INFINITY; return {
let width = Number.NEGATIVE_INFINITY; dragPosition: undefined
let height = Number.NEGATIVE_INFINITY; }
},
computed: {
marqueePosition() {
let x = Number.POSITIVE_INFINITY;
let y = Number.POSITIVE_INFINITY;
let width = Number.NEGATIVE_INFINITY;
let height = Number.NEGATIVE_INFINITY;
this.selectedLayoutItems.forEach(item => { this.selectedLayoutItems.forEach(item => {
if (item.x2 !== undefined) { if (item.x2 !== undefined) {
let lineWidth = Math.abs(item.x - item.x2); let lineWidth = Math.abs(item.x - item.x2);
let lineMinX = Math.min(item.x, item.x2); let lineMinX = Math.min(item.x, item.x2);
x = Math.min(lineMinX, x); x = Math.min(lineMinX, x);
width = Math.max(lineWidth + lineMinX, width); width = Math.max(lineWidth + lineMinX, width);
} else {
x = Math.min(item.x, x);
width = Math.max(item.width + item.x, width);
}
if (item.y2 !== undefined) {
let lineHeight = Math.abs(item.y - item.y2);
let lineMinY = Math.min(item.y, item.y2);
y = Math.min(lineMinY, y);
height = Math.max(lineHeight + lineMinY, height);
} else {
y = Math.min(item.y, y);
height = Math.max(item.height + item.y, height);
}
});
if (this.dragPosition) {
[x, y] = this.dragPosition.position;
[width, height] = this.dragPosition.dimensions;
} else { } else {
width = width - x; x = Math.min(item.x, x);
height = height - y; width = Math.max(item.width + item.x, width);
} }
this.marqueePosition = { if (item.y2 !== undefined) {
x: x, let lineHeight = Math.abs(item.y - item.y2);
y: y, let lineMinY = Math.min(item.y, item.y2);
width: width, y = Math.min(lineMinY, y);
height: height height = Math.max(lineHeight + lineMinY, height);
} else {
y = Math.min(item.y, y);
height = Math.max(item.height + item.y, height);
} }
return this.getMarqueeStyle(x, y, width, height); });
if (this.dragPosition) {
[x, y] = this.dragPosition.position;
[width, height] = this.dragPosition.dimensions;
} else {
width = width - x;
height = height - y;
}
return {
x: x,
y: y,
width: width,
height: height
} }
}, },
methods: { marqueeStyle() {
getMarqueeStyle(x, y, width, height) { return {
return { left: (this.gridSize[0] * this.marqueePosition.x) + 'px',
left: (this.gridSize[0] * x) + 'px', top: (this.gridSize[1] * this.marqueePosition.y) + 'px',
top: (this.gridSize[1] * y) + 'px', width: (this.gridSize[0] * this.marqueePosition.width) + 'px',
width: (this.gridSize[0] * width) + 'px', height: (this.gridSize[1] * this.marqueePosition.height) + 'px'
height: (this.gridSize[1] * height) + 'px' };
}; }
}, },
updatePosition(event) { methods: {
let currentPosition = [event.pageX, event.pageY]; updatePosition(event) {
this.initialPosition = this.initialPosition || currentPosition; let currentPosition = [event.pageX, event.pageY];
this.delta = currentPosition.map(function (value, index) { this.initialPosition = this.initialPosition || currentPosition;
return value - this.initialPosition[index]; this.delta = currentPosition.map(function (value, index) {
}.bind(this)); return value - this.initialPosition[index];
}, }.bind(this));
startResize(posFactor, dimFactor, event) { },
document.body.addEventListener('mousemove', this.continueResize); startResize(posFactor, dimFactor, event) {
document.body.addEventListener('mouseup', this.endResize); document.body.addEventListener('mousemove', this.continueResize);
this.marqueeStartPosition = { document.body.addEventListener('mouseup', this.endResize);
position: [this.marqueePosition.x, this.marqueePosition.y], this.marqueeStartPosition = {
dimensions: [this.marqueePosition.width, this.marqueePosition.height] position: [this.marqueePosition.x, this.marqueePosition.y],
}; dimensions: [this.marqueePosition.width, this.marqueePosition.height]
this.updatePosition(event); };
this.activeDrag = new LayoutDrag(this.marqueeStartPosition, posFactor, dimFactor, this.gridSize); this.updatePosition(event);
event.preventDefault(); this.activeDrag = new LayoutDrag(this.marqueeStartPosition, posFactor, dimFactor, this.gridSize);
}, event.preventDefault();
continueResize(event) { },
event.preventDefault(); continueResize(event) {
this.updatePosition(event); event.preventDefault();
this.dragPosition = this.activeDrag.getAdjustedPositionAndDimensions(this.delta); this.updatePosition(event);
}, this.dragPosition = this.activeDrag.getAdjustedPositionAndDimensions(this.delta);
endResize(event) { },
document.body.removeEventListener('mousemove', this.continueResize); endResize(event) {
document.body.removeEventListener('mouseup', this.endResize); document.body.removeEventListener('mousemove', this.continueResize);
this.continueResize(event); document.body.removeEventListener('mouseup', this.endResize);
this.continueResize(event);
let marqueeStartWidth = this.marqueeStartPosition.dimensions[0]; let marqueeStartWidth = this.marqueeStartPosition.dimensions[0];
let marqueeStartHeight = this.marqueeStartPosition.dimensions[1]; let marqueeStartHeight = this.marqueeStartPosition.dimensions[1];
let marqueeStartX = this.marqueeStartPosition.position[0]; let marqueeStartX = this.marqueeStartPosition.position[0];
let marqueeStartY = this.marqueeStartPosition.position[1]; let marqueeStartY = this.marqueeStartPosition.position[1];
let marqueeEndX = this.dragPosition.position[0]; let marqueeEndX = this.dragPosition.position[0];
let marqueeEndY = this.dragPosition.position[1]; let marqueeEndY = this.dragPosition.position[1];
let marqueeEndWidth = this.dragPosition.dimensions[0]; let marqueeEndWidth = this.dragPosition.dimensions[0];
let marqueeEndHeight = this.dragPosition.dimensions[1]; let marqueeEndHeight = this.dragPosition.dimensions[1];
let scaleWidth = marqueeEndWidth / marqueeStartWidth; let scaleWidth = marqueeEndWidth / marqueeStartWidth;
let scaleHeight = marqueeEndHeight / marqueeStartHeight; let scaleHeight = marqueeEndHeight / marqueeStartHeight;
let marqueeStart = { let marqueeStart = {
x: marqueeStartX, x: marqueeStartX,
y: marqueeStartY, y: marqueeStartY,
height: marqueeStartWidth, height: marqueeStartWidth,
width: marqueeStartHeight width: marqueeStartHeight
}; };
let marqueeEnd = { let marqueeEnd = {
x: marqueeEndX, x: marqueeEndX,
y: marqueeEndY, y: marqueeEndY,
width: marqueeEndWidth, width: marqueeEndWidth,
height: marqueeEndHeight height: marqueeEndHeight
}; };
let marqueeOffset = { let marqueeOffset = {
x: marqueeEnd.x - marqueeStart.x, x: marqueeEnd.x - marqueeStart.x,
y: marqueeEnd.y - marqueeStart.y y: marqueeEnd.y - marqueeStart.y
}; };
this.$emit('endResize', scaleWidth, scaleHeight, marqueeStart, marqueeOffset); this.$emit('endResize', scaleWidth, scaleHeight, marqueeStart, marqueeOffset);
this.dragPosition = undefined; this.dragPosition = undefined;
this.initialPosition = undefined; this.initialPosition = undefined;
this.marqueeStartPosition = undefined; this.marqueeStartPosition = undefined;
this.delta = undefined; this.delta = undefined;
event.preventDefault(); event.preventDefault();
}
} }
} }
}
</script> </script>

View File

@ -21,15 +21,18 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<layout-frame :item="item" <layout-frame
:grid-size="gridSize" :item="item"
@move="(gridDelta) => $emit('move', gridDelta)" :grid-size="gridSize"
@endMove="() => $emit('endMove')"> @move="(gridDelta) => $emit('move', gridDelta)"
<div class="c-image-view" @endMove="() => $emit('endMove')"
:style="style"> >
</div> <div
</layout-frame> class="c-image-view"
</template> :style="style"
></div>
</layout-frame>
</template>
<style lang="scss"> <style lang="scss">
@import '~styles/sass-base'; @import '~styles/sass-base';
@ -46,59 +49,70 @@
} }
</style> </style>
<script> <script>
import LayoutFrame from './LayoutFrame.vue' import LayoutFrame from './LayoutFrame.vue'
export default { export default {
makeDefinition(openmct, gridSize, element) { makeDefinition(openmct, gridSize, element) {
return {
stroke: 'transparent',
x: 1,
y: 1,
width: 10,
height: 5,
url: element.url
};
},
inject: ['openmct'],
components: {
LayoutFrame
},
props: {
item: {
type: Object,
required: true
},
gridSize: {
type: Array,
required: true,
validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number')
},
index: {
type: Number,
required: true
},
initSelect: Boolean
},
computed: {
style() {
return { return {
stroke: 'transparent', backgroundImage: 'url(' + this.item.url + ')',
x: 1, border: '1px solid ' + this.item.stroke
y: 1,
width: 10,
height: 5,
url: element.url
};
},
inject: ['openmct'],
props: {
item: Object,
gridSize: Array,
index: Number,
initSelect: Boolean
},
components: {
LayoutFrame
},
computed: {
style() {
return {
backgroundImage: 'url(' + this.item.url + ')',
border: '1px solid ' + this.item.stroke
}
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
}
},
mounted() {
this.context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
} }
} }
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
}
},
mounted() {
this.context = {
layoutItem: this.item,
index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
} }
</script> }
</script>

View File

@ -21,19 +21,21 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="l-layout__frame c-frame" <div
:class="{ class="l-layout__frame c-frame"
'no-frame': !item.hasFrame, :class="{
'u-inspectable': inspectable 'no-frame': !item.hasFrame,
}" 'u-inspectable': inspectable
:style="style"> }"
:style="style"
>
<slot></slot>
<slot></slot> <div
class="c-frame-edit__move"
<div class="c-frame-edit__move" @mousedown="startMove([1,1], [0,0], $event)"
@mousedown="startMove([1,1], [0,0], $event)"> ></div>
</div> </div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -173,73 +175,81 @@
</style> </style>
<script> <script>
import LayoutDrag from './../LayoutDrag' import LayoutDrag from './../LayoutDrag'
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
item: Object, item: {
gridSize: Array type: Object,
required: true
}, },
computed: { gridSize: {
style() { type: Array,
let {x, y, width, height} = this.item; required: true,
return { validator: (arr) => arr && arr.length === 2
left: (this.gridSize[0] * x) + 'px', && arr.every(el => typeof el === 'number')
top: (this.gridSize[1] * y) + 'px', }
width: (this.gridSize[0] * width) + 'px', },
height: (this.gridSize[1] * height) + 'px', computed: {
minWidth: (this.gridSize[0] * width) + 'px', style() {
minHeight: (this.gridSize[1] * height) + 'px' let {x, y, width, height} = this.item;
}; return {
}, left: (this.gridSize[0] * x) + 'px',
inspectable() { top: (this.gridSize[1] * y) + 'px',
return this.item.type === 'subobject-view' || this.item.type === 'telemetry-view'; width: (this.gridSize[0] * width) + 'px',
height: (this.gridSize[1] * height) + 'px',
minWidth: (this.gridSize[0] * width) + 'px',
minHeight: (this.gridSize[1] * height) + 'px'
};
},
inspectable() {
return this.item.type === 'subobject-view' || this.item.type === 'telemetry-view';
}
},
methods: {
updatePosition(event) {
let currentPosition = [event.pageX, event.pageY];
this.initialPosition = this.initialPosition || currentPosition;
this.delta = currentPosition.map(function (value, index) {
return value - this.initialPosition[index];
}.bind(this));
},
startMove(posFactor, dimFactor, event) {
document.body.addEventListener('mousemove', this.continueMove);
document.body.addEventListener('mouseup', this.endMove);
this.dragPosition = {
position: [this.item.x, this.item.y]
};
this.updatePosition(event);
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
event.preventDefault();
},
continueMove(event) {
event.preventDefault();
this.updatePosition(event);
let newPosition = this.activeDrag.getAdjustedPosition(this.delta);
if (!_.isEqual(newPosition, this.dragPosition)) {
this.dragPosition = newPosition;
this.$emit('move', this.toGridDelta(this.delta));
} }
}, },
methods: { endMove(event) {
updatePosition(event) { document.body.removeEventListener('mousemove', this.continueMove);
let currentPosition = [event.pageX, event.pageY]; document.body.removeEventListener('mouseup', this.endMove);
this.initialPosition = this.initialPosition || currentPosition; this.continueMove(event);
this.delta = currentPosition.map(function (value, index) { this.$emit('endMove');
return value - this.initialPosition[index]; this.dragPosition = undefined;
}.bind(this)); this.initialPosition = undefined;
}, this.delta = undefined;
startMove(posFactor, dimFactor, event) { event.preventDefault();
document.body.addEventListener('mousemove', this.continueMove); },
document.body.addEventListener('mouseup', this.endMove); toGridDelta(pixelDelta) {
this.dragPosition = { return pixelDelta.map((v, i) => {
position: [this.item.x, this.item.y] return Math.round(v / this.gridSize[i]);
}; });
this.updatePosition(event);
this.activeDrag = new LayoutDrag(this.dragPosition, posFactor, dimFactor, this.gridSize);
event.preventDefault();
},
continueMove(event) {
event.preventDefault();
this.updatePosition(event);
let newPosition = this.activeDrag.getAdjustedPosition(this.delta);
if (!_.isEqual(newPosition, this.dragPosition)) {
this.dragPosition = newPosition;
this.$emit('move', this.toGridDelta(this.delta));
}
},
endMove(event) {
document.body.removeEventListener('mousemove', this.continueMove);
document.body.removeEventListener('mouseup', this.endMove);
this.continueMove(event);
this.$emit('endMove');
this.dragPosition = undefined;
this.initialPosition = undefined;
this.delta = undefined;
event.preventDefault();
},
toGridDelta(pixelDelta) {
return pixelDelta.map((v, i) => {
return Math.round(v / this.gridSize[i]);
});
}
} }
} }
}
</script> </script>

View File

@ -21,244 +21,253 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="l-layout__frame c-frame no-frame" <div
:style="style"> class="l-layout__frame c-frame no-frame"
<svg width="100%" height="100%"> :style="style"
<line v-bind="linePosition" >
:stroke="item.stroke" <svg
stroke-width="2"> width="100%"
</line> height="100%"
</svg> >
<line
v-bind="linePosition"
:stroke="item.stroke"
stroke-width="2"
/>
</svg>
<div class="c-frame-edit__move" <div
@mousedown="startDrag($event)"></div> class="c-frame-edit__move"
<div class="c-frame-edit" v-if="showFrameEdit"> @mousedown="startDrag($event)"
<div class="c-frame-edit__handle" ></div>
:class="startHandleClass" <div
@mousedown="startDrag($event, 'start')"></div> v-if="showFrameEdit"
<div class="c-frame-edit__handle" class="c-frame-edit"
:class="endHandleClass" >
@mousedown="startDrag($event, 'end')"></div> <div
</div> class="c-frame-edit__handle"
:class="startHandleClass"
@mousedown="startDrag($event, 'start')"
></div>
<div
class="c-frame-edit__handle"
:class="endHandleClass"
@mousedown="startDrag($event, 'end')"
></div>
</div> </div>
</div>
</template> </template>
<script> <script>
const START_HANDLE_QUADRANTS = { const START_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--sw', 1: 'c-frame-edit__handle--sw',
2: 'c-frame-edit__handle--se', 2: 'c-frame-edit__handle--se',
3: 'c-frame-edit__handle--ne', 3: 'c-frame-edit__handle--ne',
4: 'c-frame-edit__handle--nw' 4: 'c-frame-edit__handle--nw'
}; };
const END_HANDLE_QUADRANTS = { const END_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--ne', 1: 'c-frame-edit__handle--ne',
2: 'c-frame-edit__handle--nw', 2: 'c-frame-edit__handle--nw',
3: 'c-frame-edit__handle--sw', 3: 'c-frame-edit__handle--sw',
4: 'c-frame-edit__handle--se' 4: 'c-frame-edit__handle--se'
}; };
export default { export default {
makeDefinition() { makeDefinition() {
return {
x: 5,
y: 10,
x2: 10,
y2: 5,
stroke: '#717171'
};
},
inject: ['openmct'],
props: {
item: {
type: Object,
required: true
},
gridSize: {
type: Array,
required: true,
validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number')
},
initSelect: Boolean,
index: {
type: Number,
required: true
},
multiSelect: Boolean
},
data() {
return {
dragPosition: undefined,
dragging: undefined,
selection: []
};
},
computed: {
showFrameEdit() {
let layoutItem = this.selection.length > 0 && this.selection[0][0].context.layoutItem;
return !this.multiSelect && layoutItem && layoutItem.id === this.item.id;
},
position() {
let {x, y, x2, y2} = this.item;
if (this.dragging && this.dragPosition) {
({x, y, x2, y2} = this.dragPosition);
}
return {x, y, x2, y2};
},
style() {
let {x, y, x2, y2} = this.position;
let width = Math.max(this.gridSize[0] * Math.abs(x - x2), 1);
let height = Math.max(this.gridSize[1] * Math.abs(y - y2), 1);
let left = this.gridSize[0] * Math.min(x, x2);
let top = this.gridSize[1] * Math.min(y, y2);
return { return {
x: 5, left: `${left}px`,
y: 10, top: `${top}px`,
x2: 10, width: `${width}px`,
y2: 5, height: `${height}px`
stroke: '#717171'
}; };
}, },
inject: ['openmct'], startHandleClass() {
props: { return START_HANDLE_QUADRANTS[this.vectorQuadrant];
item: Object,
gridSize: Array,
initSelect: Boolean,
index: Number,
multiSelect: Boolean
}, },
data() { endHandleClass() {
return { return END_HANDLE_QUADRANTS[this.vectorQuadrant];
dragPosition: undefined,
dragging: undefined,
selection: []
};
}, },
computed: { vectorQuadrant() {
showFrameEdit() { let {x, y, x2, y2} = this.position;
let layoutItem = this.selection.length > 0 && this.selection[0][0].context.layoutItem; if (x2 > x) {
return !this.multiSelect && layoutItem && layoutItem.id === this.item.id;
},
position() {
let {x, y, x2, y2} = this.item;
if (this.dragging && this.dragPosition) {
({x, y, x2, y2} = this.dragPosition);
}
return {x, y, x2, y2};
},
style() {
let {x, y, x2, y2} = this.position;
let width = Math.max(this.gridSize[0] * Math.abs(x - x2), 1);
let height = Math.max(this.gridSize[1] * Math.abs(y - y2), 1);
let left = this.gridSize[0] * Math.min(x, x2);
let top = this.gridSize[1] * Math.min(y, y2);
return {
left: `${left}px`,
top: `${top}px`,
width: `${width}px`,
height: `${height}px`,
};
},
startHandleClass() {
return START_HANDLE_QUADRANTS[this.vectorQuadrant];
},
endHandleClass() {
return END_HANDLE_QUADRANTS[this.vectorQuadrant];
},
vectorQuadrant() {
let {x, y, x2, y2} = this.position;
if (x2 > x) {
if (y2 < y) {
return 1;
}
return 4;
}
if (y2 < y) { if (y2 < y) {
return 2; return 1;
}
return 3;
},
linePosition() {
if (this.vectorQuadrant === 1) {
return {
x1: '0%',
y1: '100%',
x2: '100%',
y2: '0%'
};
}
if (this.vectorQuadrant === 4) {
return {
x1: '0%',
y1: '0%',
x2: '100%',
y2: '100%'
};
}
if (this.vectorQuadrant === 2) {
return {
x1: '0%',
y1: '0%',
x2: '100%',
y2: '100%'
};
}
if (this.vectorQuadrant === 3) {
return {
x1: '100%',
y1: '0%',
x2: '0%',
y2: '100%'
};
} }
return 4;
} }
if (y2 < y) {
return 2;
}
return 3;
}, },
methods: { linePosition() {
startDrag(event, position) { return this.vectorQuadrant % 2 !== 0
this.dragging = position; // odd vectorQuadrant slopes up
document.body.addEventListener('mousemove', this.continueDrag); ? {
document.body.addEventListener('mouseup', this.endDrag); x1: '0%',
this.startPosition = [event.pageX, event.pageY]; y1: '100%',
this.dragPosition = { x2: '100%',
x: this.item.x, y2: '0%'
y: this.item.y, }
x2: this.item.x2, // even vectorQuadrant slopes down
y2: this.item.y2 : {
x1: '0%',
y1: '0%',
x2: '100%',
y2: '100%'
}; };
event.preventDefault(); }
}, },
continueDrag(event) { watch: {
event.preventDefault(); index(newIndex) {
let pxDeltaX = this.startPosition[0] - event.pageX; if (!this.context) {
let pxDeltaY = this.startPosition[1] - event.pageY; return;
let newPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
if (!this.dragging) {
if (!_.isEqual(newPosition, this.dragPosition)) {
let gridDelta = [event.pageX - this.startPosition[0], event.pageY - this.startPosition[1]];
this.dragPosition = newPosition;
this.$emit('move', this.toGridDelta(gridDelta));
}
} else {
this.dragPosition = newPosition;
}
},
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
let {x, y, x2, y2} = this.dragPosition;
if (!this.dragging) {
this.$emit('endMove');
} else {
this.$emit('endLineResize', this.item, {x, y, x2, y2});
}
this.dragPosition = undefined;
this.dragging = undefined;
event.preventDefault();
},
calculateDragPosition(pxDeltaX, pxDeltaY) {
let gridDeltaX = Math.round(pxDeltaX / this.gridSize[0]);
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]?
let {x, y, x2, y2} = this.item;
let dragPosition = {x, y, x2, y2};
if (this.dragging === 'start') {
dragPosition.x -= gridDeltaX;
dragPosition.y -= gridDeltaY;
} else if (this.dragging === 'end') {
dragPosition.x2 -= gridDeltaX;
dragPosition.y2 -= gridDeltaY;
} else {
// dragging entire line.
dragPosition.x -= gridDeltaX;
dragPosition.y -= gridDeltaY;
dragPosition.x2 -= gridDeltaX;
dragPosition.y2 -= gridDeltaY;
}
return dragPosition;
},
setSelection(selection) {
this.selection = selection;
},
toGridDelta(pixelDelta) {
return pixelDelta.map((v, i) => {
return Math.round(v / this.gridSize[i]);
});
} }
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex; this.context.index = newIndex;
} }
}, },
mounted() { mounted() {
this.openmct.selection.on('change', this.setSelection); this.openmct.selection.on('change', this.setSelection);
this.context = { this.context = {
layoutItem: this.item, layoutItem: this.item,
index: this.index index: this.index
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
this.openmct.selection.off('change', this.setSelection);
},
methods: {
startDrag(event, position) {
this.dragging = position;
document.body.addEventListener('mousemove', this.continueDrag);
document.body.addEventListener('mouseup', this.endDrag);
this.startPosition = [event.pageX, event.pageY];
this.dragPosition = {
x: this.item.x,
y: this.item.y,
x2: this.item.x2,
y2: this.item.y2
}; };
this.removeSelectable = this.openmct.selection.selectable( event.preventDefault();
this.$el, this.context, this.initSelect);
}, },
destroyed() { continueDrag(event) {
if (this.removeSelectable) { event.preventDefault();
this.removeSelectable(); let pxDeltaX = this.startPosition[0] - event.pageX;
let pxDeltaY = this.startPosition[1] - event.pageY;
let newPosition = this.calculateDragPosition(pxDeltaX, pxDeltaY);
if (!this.dragging) {
if (!_.isEqual(newPosition, this.dragPosition)) {
let gridDelta = [event.pageX - this.startPosition[0], event.pageY - this.startPosition[1]];
this.dragPosition = newPosition;
this.$emit('move', this.toGridDelta(gridDelta));
}
} else {
this.dragPosition = newPosition;
} }
this.openmct.selection.off('change', this.setSelection); },
endDrag(event) {
document.body.removeEventListener('mousemove', this.continueDrag);
document.body.removeEventListener('mouseup', this.endDrag);
let {x, y, x2, y2} = this.dragPosition;
if (!this.dragging) {
this.$emit('endMove');
} else {
this.$emit('endLineResize', this.item, {x, y, x2, y2});
}
this.dragPosition = undefined;
this.dragging = undefined;
event.preventDefault();
},
calculateDragPosition(pxDeltaX, pxDeltaY) {
let gridDeltaX = Math.round(pxDeltaX / this.gridSize[0]);
let gridDeltaY = Math.round(pxDeltaY / this.gridSize[0]); // TODO: should this be gridSize[1]?
let {x, y, x2, y2} = this.item;
let dragPosition = {x, y, x2, y2};
if (this.dragging === 'start') {
dragPosition.x -= gridDeltaX;
dragPosition.y -= gridDeltaY;
} else if (this.dragging === 'end') {
dragPosition.x2 -= gridDeltaX;
dragPosition.y2 -= gridDeltaY;
} else {
// dragging entire line.
dragPosition.x -= gridDeltaX;
dragPosition.y -= gridDeltaY;
dragPosition.x2 -= gridDeltaX;
dragPosition.y2 -= gridDeltaY;
}
return dragPosition;
},
setSelection(selection) {
this.selection = selection;
},
toGridDelta(pixelDelta) {
return pixelDelta.map((v, i) => {
return Math.round(v / this.gridSize[i]);
});
} }
} }
</script> }
</script>

View File

@ -20,106 +20,120 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<layout-frame :item="item" <layout-frame
:grid-size="gridSize" :item="item"
:title="domainObject && domainObject.name" :grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)" :title="domainObject && domainObject.name"
@endMove="() => $emit('endMove')"> @move="(gridDelta) => $emit('move', gridDelta)"
<object-frame v-if="domainObject" @endMove="() => $emit('endMove')"
:domain-object="domainObject" >
:object-path="currentObjectPath" <object-frame
:has-frame="item.hasFrame" v-if="domainObject"
:show-edit-view="false" ref="objectFrame"
ref="objectFrame"> :domain-object="domainObject"
</object-frame> :object-path="currentObjectPath"
</layout-frame> :has-frame="item.hasFrame"
:show-edit-view="false"
/>
</layout-frame>
</template> </template>
<script> <script>
import ObjectFrame from '../../../ui/components/ObjectFrame.vue' import ObjectFrame from '../../../ui/components/ObjectFrame.vue'
import LayoutFrame from './LayoutFrame.vue' import LayoutFrame from './LayoutFrame.vue'
const MINIMUM_FRAME_SIZE = [320, 180], const MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_DIMENSIONS = [10, 10], DEFAULT_DIMENSIONS = [10, 10],
DEFAULT_POSITION = [1, 1], DEFAULT_POSITION = [1, 1],
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget']; DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
function getDefaultDimensions(gridSize) { function getDefaultDimensions(gridSize) {
return MINIMUM_FRAME_SIZE.map((min, index) => { return MINIMUM_FRAME_SIZE.map((min, index) => {
return Math.max( return Math.max(
Math.ceil(min / gridSize[index]), Math.ceil(min / gridSize[index]),
DEFAULT_DIMENSIONS[index] DEFAULT_DIMENSIONS[index]
); );
}); });
} }
function hasFrameByDefault(type) { function hasFrameByDefault(type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1; return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
} }
export default { export default {
makeDefinition(openmct, gridSize, domainObject, position) { makeDefinition(openmct, gridSize, domainObject, position) {
let defaultDimensions = getDefaultDimensions(gridSize); let defaultDimensions = getDefaultDimensions(gridSize);
position = position || DEFAULT_POSITION; position = position || DEFAULT_POSITION;
return { return {
width: defaultDimensions[0], width: defaultDimensions[0],
height: defaultDimensions[1], height: defaultDimensions[1],
x: position[0], x: position[0],
y: position[1], y: position[1],
identifier: domainObject.identifier, identifier: domainObject.identifier,
hasFrame: hasFrameByDefault(domainObject.type) hasFrame: hasFrameByDefault(domainObject.type)
}; };
},
inject: ['openmct', 'objectPath'],
components: {
ObjectFrame,
LayoutFrame
},
props: {
item: {
type: Object,
required: true
}, },
inject: ['openmct', 'objectPath'], gridSize: {
props: { type: Array,
item: Object, required: true,
gridSize: Array, validator: (arr) => arr && arr.length === 2
initSelect: Boolean, && arr.every(el => typeof el === 'number')
index: Number
}, },
data() { initSelect: Boolean,
return { index: {
domainObject: undefined, type: Number,
currentObjectPath: [] required: true
}
},
data() {
return {
domainObject: undefined,
currentObjectPath: []
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
} }
},
components: {
ObjectFrame,
LayoutFrame
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex; this.context.index = newIndex;
} }
}, },
methods: { mounted() {
setObject(domainObject) { this.openmct.objects.get(this.item.identifier)
this.domainObject = domainObject; .then(this.setObject);
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice()); },
this.$nextTick(function () { destroyed() {
let childContext = this.$refs.objectFrame.getSelectionContext(); if (this.removeSelectable) {
childContext.item = domainObject; this.removeSelectable();
childContext.layoutItem = this.item; }
childContext.index = this.index; },
this.context = childContext; methods: {
this.removeSelectable = this.openmct.selection.selectable( setObject(domainObject) {
this.$el, this.context, this.initSelect); this.domainObject = domainObject;
}); this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
} this.$nextTick(function () {
}, let childContext = this.$refs.objectFrame.getSelectionContext();
mounted() { childContext.item = domainObject;
this.openmct.objects.get(this.item.identifier) childContext.layoutItem = this.item;
.then(this.setObject); childContext.index = this.index;
}, this.context = childContext;
destroyed() { this.removeSelectable = this.openmct.selection.selectable(
if (this.removeSelectable) { this.$el, this.context, this.initSelect);
this.removeSelectable(); });
}
} }
} }
}
</script> </script>

View File

@ -20,29 +20,41 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<layout-frame :item="item" <layout-frame
:grid-size="gridSize" :item="item"
@move="(gridDelta) => $emit('move', gridDelta)" :grid-size="gridSize"
@endMove="() => $emit('endMove')"> @move="(gridDelta) => $emit('move', gridDelta)"
<div class="c-telemetry-view" @endMove="() => $emit('endMove')"
:style="styleObject" >
v-if="domainObject" <div
@contextmenu.prevent="showContextMenu"> v-if="domainObject"
<div v-if="showLabel" class="c-telemetry-view"
class="c-telemetry-view__label"> :style="styleObject"
<div class="c-telemetry-view__label-text">{{ domainObject.name }}</div> @contextmenu.prevent="showContextMenu"
</div> >
<div
<div v-if="showValue" v-if="showLabel"
:title="fieldName" class="c-telemetry-view__label"
class="c-telemetry-view__value" >
:class="[telemetryClass]"> <div class="c-telemetry-view__label-text">
<div class="c-telemetry-view__value-text">{{ telemetryValue }}</div> {{ domainObject.name }}
</div> </div>
</div> </div>
</layout-frame>
</template> <div
v-if="showValue"
:title="fieldName"
class="c-telemetry-view__value"
:class="[telemetryClass]"
>
<div class="c-telemetry-view__value-text">
{{ telemetryValue }}
</div>
</div>
</div>
</layout-frame>
</template>
<style lang="scss"> <style lang="scss">
@import '~styles/sass-base'; @import '~styles/sass-base';
@ -78,189 +90,200 @@
} }
</style> </style>
<script> <script>
import LayoutFrame from './LayoutFrame.vue' import LayoutFrame from './LayoutFrame.vue'
import printj from 'printj' import printj from 'printj'
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5], const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
DEFAULT_POSITION = [1, 1], DEFAULT_POSITION = [1, 1],
CONTEXT_MENU_ACTIONS = ['viewHistoricalData']; CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
export default { export default {
makeDefinition(openmct, gridSize, domainObject, position) { makeDefinition(openmct, gridSize, domainObject, position) {
let metadata = openmct.telemetry.getMetadata(domainObject); let metadata = openmct.telemetry.getMetadata(domainObject);
position = position || DEFAULT_POSITION; position = position || DEFAULT_POSITION;
return {
identifier: domainObject.identifier,
x: position[0],
y: position[1],
width: DEFAULT_TELEMETRY_DIMENSIONS[0],
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
displayMode: 'all',
value: metadata.getDefaultDisplayValue(),
stroke: "transparent",
fill: "transparent",
color: "",
size: "13px"
};
},
inject: ['openmct', 'objectPath'],
components: {
LayoutFrame
},
props: {
item: {
type: Object,
required: true
},
gridSize: {
type: Array,
required: true,
validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number')
},
initSelect: Boolean,
index: {
type: Number,
required: true
}
},
data() {
return {
datum: undefined,
formats: undefined,
domainObject: undefined,
currentObjectPath: undefined
}
},
computed: {
showLabel() {
let displayMode = this.item.displayMode;
return displayMode === 'all' || displayMode === 'label';
},
showValue() {
let displayMode = this.item.displayMode;
return displayMode === 'all' || displayMode === 'value';
},
styleObject() {
return { return {
identifier: domainObject.identifier, backgroundColor: this.item.fill,
x: position[0], borderColor: this.item.stroke,
y: position[1], color: this.item.color,
width: DEFAULT_TELEMETRY_DIMENSIONS[0], fontSize: this.item.size
height: DEFAULT_TELEMETRY_DIMENSIONS[1], }
displayMode: 'all', },
value: metadata.getDefaultDisplayValue(), fieldName() {
stroke: "transparent", return this.valueMetadata && this.valueMetadata.name;
fill: "transparent", },
color: "", valueMetadata() {
size: "13px" return this.datum && this.metadata.value(this.item.value);
},
valueFormatter() {
return this.formats[this.item.value];
},
telemetryValue() {
if (!this.datum) {
return;
}
if (this.item.format) {
return printj.sprintf(this.item.format, this.datum[this.valueMetadata.key]);
}
return this.valueFormatter && this.valueFormatter.format(this.datum);
},
telemetryClass() {
if (!this.datum) {
return;
}
let alarm = this.limitEvaluator && this.limitEvaluator.evaluate(this.datum, this.valueMetadata);
return alarm && alarm.cssClass;
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
},
item(newItem) {
this.context.layoutItem = newItem;
}
},
mounted() {
this.openmct.objects.get(this.item.identifier)
.then(this.setObject);
this.openmct.time.on("bounds", this.refreshData);
},
destroyed() {
this.removeSubscription();
if (this.removeSelectable) {
this.removeSelectable();
}
this.openmct.time.off("bounds", this.refreshData);
},
methods: {
requestHistoricalData() {
let bounds = this.openmct.time.bounds();
let options = {
start: bounds.start,
end: bounds.end,
size: 1,
strategy: 'latest'
}; };
}, this.openmct.telemetry.request(this.domainObject, options)
inject: ['openmct', 'objectPath'], .then(data => {
props: { if (data.length > 0) {
item: Object, this.updateView(data[data.length - 1]);
gridSize: Array,
initSelect: Boolean,
index: Number
},
components: {
LayoutFrame
},
computed: {
showLabel() {
let displayMode = this.item.displayMode;
return displayMode === 'all' || displayMode === 'label';
},
showValue() {
let displayMode = this.item.displayMode;
return displayMode === 'all' || displayMode === 'value';
},
styleObject() {
return {
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
fontSize: this.item.size
}
},
fieldName() {
return this.valueMetadata && this.valueMetadata.name;
},
valueMetadata() {
return this.datum && this.metadata.value(this.item.value);
},
valueFormatter() {
return this.formats[this.item.value];
},
telemetryValue() {
if (!this.datum) {
return;
}
if (this.item.format) {
return printj.sprintf(this.item.format, this.datum[this.valueMetadata.key]);
}
return this.valueFormatter && this.valueFormatter.format(this.datum);
},
telemetryClass() {
if (!this.datum) {
return;
}
let alarm = this.limitEvaluator && this.limitEvaluator.evaluate(this.datum, this.valueMetadata);
return alarm && alarm.cssClass;
}
},
data() {
return {
datum: undefined,
formats: undefined,
domainObject: undefined,
currentObjectPath: undefined
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
},
item(newItem) {
this.context.layoutItem = newItem;
}
},
methods: {
requestHistoricalData() {
let bounds = this.openmct.time.bounds();
let options = {
start: bounds.start,
end: bounds.end,
size: 1,
strategy: 'latest'
};
this.openmct.telemetry.request(this.domainObject, options)
.then(data => {
if (data.length > 0) {
this.updateView(data[data.length - 1]);
}
});
},
subscribeToObject() {
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
if (this.openmct.time.clock() !== undefined) {
this.updateView(datum);
} }
}.bind(this)); });
}, },
updateView(datum) { subscribeToObject() {
this.datum = datum; this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
}, if (this.openmct.time.clock() !== undefined) {
removeSubscription() { this.updateView(datum);
if (this.subscription) {
this.subscription();
this.subscription = undefined;
} }
}, }.bind(this));
refreshData(bounds, isTick) { },
if (!isTick) { updateView(datum) {
this.datum = undefined; this.datum = datum;
this.requestHistoricalData(this.domainObject); },
} removeSubscription() {
}, if (this.subscription) {
setObject(domainObject) { this.subscription();
this.domainObject = domainObject; this.subscription = undefined;
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.requestHistoricalData();
this.subscribeToObject();
this.currentObjectPath = this.objectPath.slice();
this.currentObjectPath.unshift(this.domainObject);
this.context = {
item: domainObject,
layoutItem: this.item,
index: this.index,
updateTelemetryFormat: this.updateTelemetryFormat
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
updateTelemetryFormat(format) {
this.$emit('formatChanged', this.item, format);
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
} }
}, },
mounted() { refreshData(bounds, isTick) {
this.openmct.objects.get(this.item.identifier) if (!isTick) {
.then(this.setObject); this.datum = undefined;
this.openmct.time.on("bounds", this.refreshData); this.requestHistoricalData(this.domainObject);
},
destroyed() {
this.removeSubscription();
if (this.removeSelectable) {
this.removeSelectable();
} }
},
setObject(domainObject) {
this.domainObject = domainObject;
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.requestHistoricalData();
this.subscribeToObject();
this.openmct.time.off("bounds", this.refreshData); this.currentObjectPath = this.objectPath.slice();
this.currentObjectPath.unshift(this.domainObject);
this.context = {
item: domainObject,
layoutItem: this.item,
index: this.index,
updateTelemetryFormat: this.updateTelemetryFormat
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
},
updateTelemetryFormat(format) {
this.$emit('formatChanged', this.item, format);
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
} }
} }
}
</script> </script>

View File

@ -20,17 +20,21 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<layout-frame :item="item" <layout-frame
:grid-size="gridSize" :item="item"
@move="(gridDelta) => $emit('move', gridDelta)" :grid-size="gridSize"
@endMove="() => $emit('endMove')"> @move="(gridDelta) => $emit('move', gridDelta)"
<div class="c-text-view" @endMove="() => $emit('endMove')"
:style="style"> >
{{ item.text }} <div
</div> class="c-text-view"
</layout-frame> :style="style"
</template> >
{{ item.text }}
</div>
</layout-frame>
</template>
<style lang="scss"> <style lang="scss">
@import '~styles/sass-base'; @import '~styles/sass-base';
@ -46,65 +50,75 @@
} }
</style> </style>
<script> <script>
import LayoutFrame from './LayoutFrame.vue' import LayoutFrame from './LayoutFrame.vue'
export default { export default {
makeDefinition(openmct, gridSize, element) { makeDefinition(openmct, gridSize, element) {
return {
fill: 'transparent',
stroke: 'transparent',
size: '13px',
color: '',
x: 1,
y: 1,
width: 10,
height: 5,
text: element.text
};
},
inject: ['openmct'],
components: {
LayoutFrame
},
props: {
item: {
type: Object,
required: true
},
gridSize: {
type: Array,
required: true,
validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number')
},
index: {
type: Number,
required: true
},
initSelect: Boolean
},
computed: {
style() {
return { return {
fill: 'transparent', backgroundColor: this.item.fill,
stroke: 'transparent', borderColor: this.item.stroke,
size: '13px', color: this.item.color,
color: '', fontSize: this.item.size
x: 1,
y: 1,
width: 10,
height: 5,
text: element.text
}; };
}, }
inject: ['openmct'], },
props: { watch: {
item: Object, index(newIndex) {
gridSize: Array, if (!this.context) {
index: Number, return;
initSelect: Boolean
},
components: {
LayoutFrame
},
computed: {
style() {
return {
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
fontSize: this.item.size
};
} }
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex; this.context.index = newIndex;
} }
}, },
mounted() { mounted() {
this.context = { this.context = {
layoutItem: this.item, layoutItem: this.item,
index: this.index index: this.index
}; };
this.removeSelectable = this.openmct.selection.selectable( this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect); this.$el, this.context, this.initSelect);
}, },
destroyed() { destroyed() {
if (this.removeSelectable) { if (this.removeSelectable) {
this.removeSelectable(); this.removeSelectable();
}
} }
} }
</script> }
</script>

View File

@ -42,22 +42,22 @@ export default function DisplayLayoutPlugin(options) {
return { return {
show(container) { show(container) {
component = new Vue({ component = new Vue({
el: container,
components: { components: {
Layout Layout
}, },
template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>',
provide: { provide: {
openmct, openmct,
objectUtils, objectUtils,
options, options,
objectPath objectPath
}, },
el: container,
data() { data() {
return { return {
domainObject: domainObject domainObject: domainObject
}; };
} },
template: '<layout ref="displayLayout" :domain-object="domainObject"></layout>'
}); });
}, },
getSelectionContext() { getSelectionContext() {

View File

@ -48,11 +48,11 @@ define([
provide: { provide: {
openmct openmct
}, },
el: element,
components: { components: {
FiltersView: FiltersView.default FiltersView: FiltersView.default
}, },
template: '<filters-view></filters-view>', template: '<filters-view></filters-view>'
el: element
}); });
}, },
destroy: function () { destroy: function () {

View File

@ -1,56 +1,66 @@
<template> <template>
<div class="c-properties__section c-filter-settings"> <div class="c-properties__section c-filter-settings">
<li class="c-properties__row c-filter-settings__setting" <li
v-for="(filter, index) in filterField.filters" v-for="(filter, index) in filterField.filters"
:key="index"> :key="index"
<div class="c-properties__label label" class="c-properties__row c-filter-settings__setting"
:disabled="useGlobal"> >
{{ filterField.name }} = <div
</div> class="c-properties__label label"
<div class="c-properties__value value"> :disabled="useGlobal"
<!-- EDITING --> >
<!-- String input, editing --> {{ filterField.name }} =
<template v-if="!filter.possibleValues && isEditing"> </div>
<input class="c-input--flex" <div class="c-properties__value value">
type="text" <!-- EDITING -->
:id="`${filter}filterControl`" <!-- String input, editing -->
:disabled="useGlobal" <template v-if="!filter.possibleValues && isEditing">
:value="persistedValue(filter)" <input
@change="updateFilterValue($event, filter)"> :id="`${filter}filterControl`"
</template> class="c-input--flex"
type="text"
:disabled="useGlobal"
:value="persistedValue(filter)"
@change="updateFilterValue($event, filter)"
>
</template>
<!-- Checkbox list, editing --> <!-- Checkbox list, editing -->
<template v-if="filter.possibleValues && isEditing"> <template v-if="filter.possibleValues && isEditing">
<div class="c-checkbox-list__row" <div
v-for="option in filter.possibleValues" v-for="option in filter.possibleValues"
:key="option.value"> :key="option.value"
<input class="c-checkbox-list__input" class="c-checkbox-list__row"
type="checkbox" >
:id="`${option.value}filterControl`" <input
:disabled="useGlobal" :id="`${option.value}filterControl`"
@change="updateFilterValue($event, filter.comparator, option.value)" class="c-checkbox-list__input"
:checked="isChecked(filter.comparator, option.value)"> type="checkbox"
<span class="c-checkbox-list__value"> :disabled="useGlobal"
{{ option.label }} :checked="isChecked(filter.comparator, option.value)"
</span> @change="updateFilterValue($event, filter.comparator, option.value)"
</div> >
</template> <span class="c-checkbox-list__value">
{{ option.label }}
<!-- BROWSING -->
<!-- String input, NOT editing -->
<template v-if="!filter.possibleValues && !isEditing">
{{ persistedValue(filter) }}
</template>
<!-- Checkbox list, NOT editing -->
<template v-if="filter.possibleValues && !isEditing">
<span v-if="persistedFilters[filter.comparator]">
{{ getFilterLabels(filter) }}
</span> </span>
</template> </div>
</div> </template>
</li>
</div> <!-- BROWSING -->
<!-- String input, NOT editing -->
<template v-if="!filter.possibleValues && !isEditing">
{{ persistedValue(filter) }}
</template>
<!-- Checkbox list, NOT editing -->
<template v-if="filter.possibleValues && !isEditing">
<span v-if="persistedFilters[filter.comparator]">
{{ getFilterLabels(filter) }}
</span>
</template>
</div>
</li>
</div>
</template> </template>
<script> <script>
@ -59,7 +69,10 @@ export default {
'openmct' 'openmct'
], ],
props: { props: {
filterField: Object, filterField: {
type: Object,
required: true
},
useGlobal: Boolean, useGlobal: Boolean,
persistedFilters: { persistedFilters: {
type: Object, type: Object,
@ -73,6 +86,12 @@ export default {
isEditing: this.openmct.editor.isEditing() isEditing: this.openmct.editor.isEditing()
} }
}, },
mounted() {
this.openmct.editor.on('isEditing', this.toggleIsEditing);
},
beforeDestroy() {
this.openmct.editor.off('isEditing', this.toggleIsEditing);
},
methods: { methods: {
toggleIsEditing(isEditing) { toggleIsEditing(isEditing) {
this.isEditing = isEditing; this.isEditing = isEditing;
@ -107,12 +126,6 @@ export default {
return accum; return accum;
}, []).join(', '); }, []).join(', ');
} }
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleIsEditing);
},
beforeDestroy() {
this.openmct.editor.off('isEditing', this.toggleIsEditing);
} }
} }
</script> </script>

View File

@ -1,50 +1,62 @@
<template> <template>
<li class="c-tree__item-h"> <li class="c-tree__item-h">
<div class="c-tree__item menus-to-left" <div
@click="toggleExpanded"> class="c-tree__item menus-to-left"
<div class="c-filter-tree-item__filter-indicator" @click="toggleExpanded"
:class="{'icon-filter': hasActiveFilters }"></div> >
<span class="c-disclosure-triangle is-enabled flex-elem" <div
:class="{'c-disclosure-triangle--expanded': expanded}"></span> class="c-filter-tree-item__filter-indicator"
<div class="c-tree__item__label c-object-label"> :class="{'icon-filter': hasActiveFilters }"
<div class="c-object-label"> ></div>
<div class="c-object-label__type-icon" <span
:class="objectCssClass"> class="c-disclosure-triangle is-enabled flex-elem"
</div> :class="{'c-disclosure-triangle--expanded': expanded}"
<div class="c-object-label__name flex-elem grows">{{ filterObject.name }}</div> ></span>
<div class="c-tree__item__label c-object-label">
<div class="c-object-label">
<div
class="c-object-label__type-icon"
:class="objectCssClass"
></div>
<div class="c-object-label__name flex-elem grows">
{{ filterObject.name }}
</div> </div>
</div> </div>
</div> </div>
</div>
<div v-if="expanded"> <div v-if="expanded">
<ul class="c-properties"> <ul class="c-properties">
<div class="c-properties__label span-all" <div
v-if="!isEditing && persistedFilters.useGlobal"> v-if="!isEditing && persistedFilters.useGlobal"
Uses global filter class="c-properties__label span-all"
</div> >
Uses global filter
</div>
<div class="c-properties__label span-all" <div
v-if="isEditing"> v-if="isEditing"
<toggle-switch class="c-properties__label span-all"
:id="keyString" >
@change="useGlobalFilter" <toggle-switch
:checked="persistedFilters.useGlobal"> :id="keyString"
</toggle-switch> :checked="persistedFilters.useGlobal"
Use global filter @change="useGlobalFilter"
</div> />
<filter-field Use global filter
v-if="(!persistedFilters.useGlobal && !isEditing) || isEditing" </div>
v-for="metadatum in filterObject.metadataWithFilters" <filter-field
:key="metadatum.key" v-for="metadatum in activeFilters"
:filterField="metadatum" :key="metadatum.key"
:useGlobal="persistedFilters.useGlobal" :filter-field="metadatum"
:persistedFilters="updatedFilters[metadatum.key]" :use-global="persistedFilters.useGlobal"
@filterSelected="updateFiltersWithSelectedValue" :persisted-filters="updatedFilters[metadatum.key]"
@filterTextValueChanged="updateFiltersWithTextValue"> @filterSelected="updateFiltersWithSelectedValue"
</filter-field> @filterTextValueChanged="updateFiltersWithTextValue"
</ul> />
</div> </ul>
</li> </div>
</li>
</template> </template>
<script> <script>
@ -58,7 +70,10 @@ export default {
ToggleSwitch ToggleSwitch
}, },
props: { props: {
filterObject: Object, filterObject: {
type: Object,
required: true
},
persistedFilters: { persistedFilters: {
type: Object, type: Object,
default: () => { default: () => {
@ -74,6 +89,23 @@ export default {
isEditing: this.openmct.editor.isEditing() isEditing: this.openmct.editor.isEditing()
} }
}, },
computed: {
// do not show filter fields if using global filter
// if editing however, show all filter fields
activeFilters() {
if (!this.isEditing && this.persistedFilters.useGlobal) {
return []
}
return this.filterObject.metadataWithFilters
},
hasActiveFilters() {
// Should be true when the user has entered any filter values.
return Object.values(this.persistedFilters).some(comparator => {
return (typeof(comparator) === 'object' && !_.isEmpty(comparator));
});
}
},
watch: { watch: {
persistedFilters: { persistedFilters: {
handler: function checkFilters(newpersistedFilters) { handler: function checkFilters(newpersistedFilters) {
@ -82,13 +114,14 @@ export default {
deep: true deep: true
} }
}, },
computed: { mounted() {
hasActiveFilters() { let type = this.openmct.types.get(this.filterObject.domainObject.type) || {};
// Should be true when the user has entered any filter values. this.keyString = this.openmct.objects.makeKeyString(this.filterObject.domainObject.identifier);
return Object.values(this.persistedFilters).some(comparator => { this.objectCssClass = type.definition.cssClass;
return (typeof(comparator) === 'object' && !_.isEmpty(comparator)); this.openmct.editor.on('isEditing', this.toggleIsEditing);
}); },
} beforeDestroy() {
this.openmct.editor.off('isEditing', this.toggleIsEditing);
}, },
methods: { methods: {
toggleExpanded() { toggleExpanded() {
@ -128,16 +161,7 @@ export default {
}, },
toggleIsEditing(isEditing) { toggleIsEditing(isEditing) {
this.isEditing = isEditing; this.isEditing = isEditing;
}, }
},
mounted() {
let type = this.openmct.types.get(this.filterObject.domainObject.type) || {};
this.keyString = this.openmct.objects.makeKeyString(this.filterObject.domainObject.identifier);
this.objectCssClass = type.definition.cssClass;
this.openmct.editor.on('isEditing', this.toggleIsEditing);
},
beforeDestroy() {
this.openmct.editor.off('isEditing', this.toggleIsEditing);
} }
} }
</script> </script>

View File

@ -1,22 +1,28 @@
<template> <template>
<ul class="c-tree c-filter-tree" v-if="Object.keys(children).length"> <ul
<h2>Data Filters</h2> v-if="Object.keys(children).length"
<div class="c-filter-indication" class="c-tree c-filter-tree"
v-if="hasActiveFilters">{{ label }} >
</div> <h2>Data Filters</h2>
<global-filters <div
:globalFilters="globalFilters" v-if="hasActiveFilters"
:globalMetadata="globalMetadata" class="c-filter-indication"
@persistGlobalFilters="persistGlobalFilters"> >
</global-filters> {{ label }}
<filter-object </div>
v-for="(child, key) in children" <global-filters
:key="key" :global-filters="globalFilters"
:filterObject="child" :global-metadata="globalMetadata"
:persistedFilters="persistedFilters[key]" @persistGlobalFilters="persistGlobalFilters"
@updateFilters="persistFilters"> />
</filter-object> <filter-object
</ul> v-for="(child, key) in children"
:key="key"
:filter-object="child"
:persisted-filters="persistedFilters[key]"
@updateFilters="persistFilters"
/>
</ul>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -40,210 +46,211 @@
</style> </style>
<script> <script>
import FilterObject from './FilterObject.vue'; import FilterObject from './FilterObject.vue';
import GlobalFilters from './GlobalFilters.vue' import GlobalFilters from './GlobalFilters.vue'
const FILTER_VIEW_TITLE = 'Filters applied'; const FILTER_VIEW_TITLE = 'Filters applied';
const FILTER_VIEW_TITLE_MIXED = 'Mixed filters applied'; const FILTER_VIEW_TITLE_MIXED = 'Mixed filters applied';
const USE_GLOBAL = 'useGlobal'; const USE_GLOBAL = 'useGlobal';
export default { export default {
components: { components: {
FilterObject, FilterObject,
GlobalFilters GlobalFilters
}, },
inject: [ inject: [
'openmct' 'openmct'
], ],
data() { data() {
let providedObject = this.openmct.selection.get()[0][0].context.item; let providedObject = this.openmct.selection.get()[0][0].context.item;
let configuration = providedObject.configuration; let configuration = providedObject.configuration;
return { return {
persistedFilters: (configuration && configuration.filters) || {}, persistedFilters: (configuration && configuration.filters) || {},
globalFilters: (configuration && configuration.globalFilters) || {}, globalFilters: (configuration && configuration.globalFilters) || {},
globalMetadata: {}, globalMetadata: {},
providedObject, providedObject,
children: {} children: {}
} }
}, },
computed: { computed: {
hasActiveFilters() { hasActiveFilters() {
// Should be true when the user has entered any filter values. // Should be true when the user has entered any filter values.
return Object.values(this.persistedFilters).some(filters => { return Object.values(this.persistedFilters).some(filters => {
return Object.values(filters).some(comparator => { return Object.values(filters).some(comparator => {
return (typeof(comparator) === 'object' && !_.isEmpty(comparator)); return (typeof(comparator) === 'object' && !_.isEmpty(comparator));
});
}); });
}, });
hasMixedFilters() { },
// Should be true when filter values are mixed. hasMixedFilters() {
let filtersToCompare = _.omit(this.persistedFilters[Object.keys(this.persistedFilters)[0]], [USE_GLOBAL]); // Should be true when filter values are mixed.
return Object.values(this.persistedFilters).some(filters => { let filtersToCompare = _.omit(this.persistedFilters[Object.keys(this.persistedFilters)[0]], [USE_GLOBAL]);
return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL])); return Object.values(this.persistedFilters).some(filters => {
}); return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL]));
}, });
label() { },
if (this.hasActiveFilters) { label() {
if (this.hasMixedFilters) { if (this.hasActiveFilters) {
return FILTER_VIEW_TITLE_MIXED; if (this.hasMixedFilters) {
} else { return FILTER_VIEW_TITLE_MIXED;
return FILTER_VIEW_TITLE; } else {
} return FILTER_VIEW_TITLE;
} }
} }
}, return '';
methods: { }
addChildren(domainObject) { },
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier); mounted() {
let metadata = this.openmct.telemetry.getMetadata(domainObject); this.composition = this.openmct.composition.get(this.providedObject);
let metadataWithFilters = metadata.valueMetadatas.filter(value => value.filters); this.composition.on('add', this.addChildren);
let hasFiltersWithKeyString = this.persistedFilters[keyString] !== undefined; this.composition.on('remove', this.removeChildren);
let mutateFilters = false; this.composition.load();
let childObject = { this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
name: domainObject.name, this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
domainObject: domainObject, this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
metadataWithFilters },
}; beforeDestroy() {
this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren);
this.unobserve();
this.unobserveGlobalFilters();
this.unobserveAllMutation();
},
methods: {
addChildren(domainObject) {
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
let metadata = this.openmct.telemetry.getMetadata(domainObject);
let metadataWithFilters = metadata.valueMetadatas.filter(value => value.filters);
let hasFiltersWithKeyString = this.persistedFilters[keyString] !== undefined;
let mutateFilters = false;
let childObject = {
name: domainObject.name,
domainObject: domainObject,
metadataWithFilters
};
if (metadataWithFilters.length) { if (metadataWithFilters.length) {
this.$set(this.children, keyString, childObject); this.$set(this.children, keyString, childObject);
metadataWithFilters.forEach(metadatum => { metadataWithFilters.forEach(metadatum => {
if (!this.globalFilters[metadatum.key]) { if (!this.globalFilters[metadatum.key]) {
this.$set(this.globalFilters, metadatum.key, {}); this.$set(this.globalFilters, metadatum.key, {});
}
if (!this.globalMetadata[metadatum.key]) {
this.$set(this.globalMetadata, metadatum.key, metadatum);
}
if (!hasFiltersWithKeyString) {
if (!this.persistedFilters[keyString]) {
this.$set(this.persistedFilters, keyString, {});
this.$set(this.persistedFilters[keyString], 'useGlobal', true);
mutateFilters = true;
} }
if (!this.globalMetadata[metadatum.key]) { this.$set(this.persistedFilters[keyString], metadatum.key, this.globalFilters[metadatum.key]);
this.$set(this.globalMetadata, metadatum.key, metadatum);
}
if (!hasFiltersWithKeyString) {
if (!this.persistedFilters[keyString]) {
this.$set(this.persistedFilters, keyString, {});
this.$set(this.persistedFilters[keyString], 'useGlobal', true);
mutateFilters = true;
}
this.$set(this.persistedFilters[keyString], metadatum.key, this.globalFilters[metadatum.key]);
}
});
}
if (mutateFilters) {
this.mutateConfigurationFilters();
}
},
removeChildren(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
let globalFiltersToRemove = this.getGlobalFiltersToRemove(keyString);
if (globalFiltersToRemove.length > 0) {
globalFiltersToRemove.forEach(key => {
this.$delete(this.globalFilters, key);
this.$delete(this.globalMetadata, key);
});
this.mutateConfigurationGlobalFilters();
}
this.$delete(this.children, keyString);
this.$delete(this.persistedFilters, keyString);
this.mutateConfigurationFilters();
},
getGlobalFiltersToRemove(keyString) {
let filtersToRemove = new Set();
this.children[keyString].metadataWithFilters.forEach(metadatum => {
let keepFilter = false
Object.keys(this.children).forEach(childKeyString => {
if (childKeyString !== keyString) {
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
if (filterMatched) {
keepFilter = true;
return;
}
}
});
if (!keepFilter) {
filtersToRemove.add(metadatum.key);
} }
}); });
}
return Array.from(filtersToRemove); if (mutateFilters) {
},
persistFilters(keyString, updatedFilters, useGlobalValues) {
this.persistedFilters[keyString] = updatedFilters;
if (useGlobalValues) {
Object.keys(this.persistedFilters[keyString]).forEach(key => {
if (typeof(this.persistedFilters[keyString][key]) === 'object') {
this.persistedFilters[keyString][key] = this.globalFilters[key];
}
});
}
this.mutateConfigurationFilters(); this.mutateConfigurationFilters();
}, }
updatePersistedFilters(filters) { },
this.persistedFilters = filters; removeChildren(identifier) {
}, let keyString = this.openmct.objects.makeKeyString(identifier);
persistGlobalFilters(key, filters) { let globalFiltersToRemove = this.getGlobalFiltersToRemove(keyString);
this.globalFilters[key] = filters[key];
if (globalFiltersToRemove.length > 0) {
globalFiltersToRemove.forEach(key => {
this.$delete(this.globalFilters, key);
this.$delete(this.globalMetadata, key);
});
this.mutateConfigurationGlobalFilters(); this.mutateConfigurationGlobalFilters();
let mutateFilters = false; }
Object.keys(this.children).forEach(keyString => { this.$delete(this.children, keyString);
if (this.persistedFilters[keyString].useGlobal !== false && this.containsField(keyString, key)) { this.$delete(this.persistedFilters, keyString);
if (!this.persistedFilters[keyString][key]) { this.mutateConfigurationFilters();
this.$set(this.persistedFilters[keyString], key, {}); },
getGlobalFiltersToRemove(keyString) {
let filtersToRemove = new Set();
this.children[keyString].metadataWithFilters.forEach(metadatum => {
let keepFilter = false
Object.keys(this.children).forEach(childKeyString => {
if (childKeyString !== keyString) {
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
if (filterMatched) {
keepFilter = true;
return;
} }
this.$set(this.persistedFilters[keyString], key, filters[key]);
mutateFilters = true;
} }
}); });
if (mutateFilters) { if (!keepFilter) {
this.mutateConfigurationFilters(); filtersToRemove.add(metadatum.key);
} }
}, });
updateGlobalFilters(filters) {
this.globalFilters = filters; return Array.from(filtersToRemove);
}, },
containsField(keyString, field) { persistFilters(keyString, updatedFilters, useGlobalValues) {
let hasField = false; this.persistedFilters[keyString] = updatedFilters;
this.children[keyString].metadataWithFilters.forEach(metadatum => {
if (metadatum.key === field) { if (useGlobalValues) {
hasField = true; Object.keys(this.persistedFilters[keyString]).forEach(key => {
return; if (typeof(this.persistedFilters[keyString][key]) === 'object') {
this.persistedFilters[keyString][key] = this.globalFilters[key];
} }
}); });
return hasField; }
},
mutateConfigurationFilters() { this.mutateConfigurationFilters();
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters); },
}, updatePersistedFilters(filters) {
mutateConfigurationGlobalFilters() { this.persistedFilters = filters;
this.openmct.objects.mutate(this.providedObject, 'configuration.globalFilters', this.globalFilters); },
persistGlobalFilters(key, filters) {
this.globalFilters[key] = filters[key];
this.mutateConfigurationGlobalFilters();
let mutateFilters = false;
Object.keys(this.children).forEach(keyString => {
if (this.persistedFilters[keyString].useGlobal !== false && this.containsField(keyString, key)) {
if (!this.persistedFilters[keyString][key]) {
this.$set(this.persistedFilters[keyString], key, {});
}
this.$set(this.persistedFilters[keyString], key, filters[key]);
mutateFilters = true;
}
});
if (mutateFilters) {
this.mutateConfigurationFilters();
} }
}, },
mounted(){ updateGlobalFilters(filters) {
this.composition = this.openmct.composition.get(this.providedObject); this.globalFilters = filters;
this.composition.on('add', this.addChildren);
this.composition.on('remove', this.removeChildren);
this.composition.load();
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
}, },
beforeDestroy() { containsField(keyString, field) {
this.composition.off('add', this.addChildren); let hasField = false;
this.composition.off('remove', this.removeChildren); this.children[keyString].metadataWithFilters.forEach(metadatum => {
this.unobserve(); if (metadatum.key === field) {
this.unobserveGlobalFilters(); hasField = true;
this.unobserveAllMutation(); return;
}
});
return hasField;
},
mutateConfigurationFilters() {
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
},
mutateConfigurationGlobalFilters() {
this.openmct.objects.mutate(this.providedObject, 'configuration.globalFilters', this.globalFilters);
} }
} }
}
</script> </script>

View File

@ -1,29 +1,40 @@
<template> <template>
<li class="c-tree__item-h"> <li class="c-tree__item-h">
<div class="c-tree__item menus-to-left" <div
@click="toggleExpanded"> class="c-tree__item menus-to-left"
<div class="c-filter-tree-item__filter-indicator" @click="toggleExpanded"
:class="{'icon-filter': hasActiveGlobalFilters }"></div> >
<span class="c-disclosure-triangle is-enabled flex-elem" <div
:class="{'c-disclosure-triangle--expanded': expanded}"></span> class="c-filter-tree-item__filter-indicator"
<div class="c-tree__item__label c-object-label"> :class="{'icon-filter': hasActiveGlobalFilters }"
<div class="c-object-label"> ></div>
<div class="c-object-label__type-icon icon-gear"></div> <span
<div class="c-object-label__name flex-elem grows">Global Filtering</div> class="c-disclosure-triangle is-enabled flex-elem"
:class="{'c-disclosure-triangle--expanded': expanded}"
></span>
<div class="c-tree__item__label c-object-label">
<div class="c-object-label">
<div class="c-object-label__type-icon icon-gear"></div>
<div class="c-object-label__name flex-elem grows">
Global Filtering
</div> </div>
</div> </div>
</div> </div>
<ul class="c-properties" v-if="expanded"> </div>
<filter-field <ul
v-for="metadatum in globalMetadata" v-if="expanded"
:key="metadatum.key" class="c-properties"
:filterField="metadatum" >
:persistedFilters="updatedFilters[metadatum.key]" <filter-field
@filterSelected="updateFiltersWithSelectedValue" v-for="metadatum in globalMetadata"
@filterTextValueChanged="updateFiltersWithTextValue"> :key="metadatum.key"
</filter-field> :filter-field="metadatum"
</ul> :persisted-filters="updatedFilters[metadatum.key]"
</li> @filterSelected="updateFiltersWithSelectedValue"
@filterTextValueChanged="updateFiltersWithTextValue"
/>
</ul>
</li>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -59,77 +70,80 @@
</style> </style>
<script> <script>
import FilterField from './FilterField.vue'; import FilterField from './FilterField.vue';
export default { export default {
inject: ['openmct'], inject: ['openmct'],
components: { components: {
FilterField FilterField
},
props: {
globalMetadata: {
type: Object,
required: true
}, },
props: { globalFilters: {
globalMetadata: Object, type: Object,
globalFilters: { default: () => {
type: Object, return {};
default: () => {
return {};
}
}
},
data() {
return {
expanded: false,
updatedFilters: JSON.parse(JSON.stringify(this.globalFilters))
}
},
computed: {
hasActiveGlobalFilters() {
return Object.values(this.globalFilters).some(field => {
return Object.values(field).some(comparator => {
return (comparator && (comparator !== '' || comparator.length > 0));
});
});
}
},
watch: {
globalFilters: {
handler: function checkFilters(newGlobalFilters) {
this.updatedFilters = JSON.parse(JSON.stringify(newGlobalFilters));
},
deep: true
}
},
methods: {
toggleExpanded() {
this.expanded = !this.expanded;
},
updateFiltersWithSelectedValue(key, comparator, valueName, value) {
let filterValue = this.updatedFilters[key];
if (filterValue[comparator]) {
if (value === true) {
filterValue[comparator].push(valueName);
} else {
if (filterValue[comparator].length === 1) {
this.$set(this.updatedFilters, key, {});
} else {
filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName);
}
}
} else {
this.$set(this.updatedFilters[key], comparator, [valueName]);
}
this.$emit('persistGlobalFilters', key, this.updatedFilters);
},
updateFiltersWithTextValue(key, comparator, value) {
if (value.trim() === '') {
this.$set(this.updatedFilters, key, {});
} else {
this.$set(this.updatedFilters[key], comparator, value);
}
this.$emit('persistGlobalFilters', key, this.updatedFilters);
} }
} }
},
data() {
return {
expanded: false,
updatedFilters: JSON.parse(JSON.stringify(this.globalFilters))
}
},
computed: {
hasActiveGlobalFilters() {
return Object.values(this.globalFilters).some(field => {
return Object.values(field).some(comparator => {
return (comparator && (comparator !== '' || comparator.length > 0));
});
});
}
},
watch: {
globalFilters: {
handler: function checkFilters(newGlobalFilters) {
this.updatedFilters = JSON.parse(JSON.stringify(newGlobalFilters));
},
deep: true
}
},
methods: {
toggleExpanded() {
this.expanded = !this.expanded;
},
updateFiltersWithSelectedValue(key, comparator, valueName, value) {
let filterValue = this.updatedFilters[key];
if (filterValue[comparator]) {
if (value === true) {
filterValue[comparator].push(valueName);
} else {
if (filterValue[comparator].length === 1) {
this.$set(this.updatedFilters, key, {});
} else {
filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName);
}
}
} else {
this.$set(this.updatedFilters[key], comparator, [valueName]);
}
this.$emit('persistGlobalFilters', key, this.updatedFilters);
},
updateFiltersWithTextValue(key, comparator, value) {
if (value.trim() === '') {
this.$set(this.updatedFilters, key, {});
} else {
this.$set(this.updatedFilters[key], comparator, value);
}
this.$emit('persistGlobalFilters', key, this.updatedFilters);
}
} }
}
</script> </script>

View File

@ -21,62 +21,65 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-fl-container" <div
:style="[{'flex-basis': sizeString}]" class="c-fl-container"
:class="{'is-empty': !frames.length}"> :style="[{'flex-basis': sizeString}]"
<div class="c-fl-container__header" :class="{'is-empty': !frames.length}"
v-show="isEditing" >
draggable="true" <div
@dragstart="startContainerDrag"> v-show="isEditing"
<span class="c-fl-container__size-indicator">{{ sizeString }}</span> class="c-fl-container__header"
</div> draggable="true"
@dragstart="startContainerDrag"
<drop-hint >
class="c-fl-frame__drop-hint" <span class="c-fl-container__size-indicator">{{ sizeString }}</span>
:index="-1"
:allow-drop="allowDrop"
@object-drop-to="moveOrCreateNewFrame">
</drop-hint>
<div class="c-fl-container__frames-holder">
<template
v-for="(frame, i) in frames">
<frame-component
class="c-fl-container__frame"
:key="frame.id"
:frame="frame"
:index="i"
:containerIndex="index"
:isEditing="isEditing">
</frame-component>
<drop-hint
class="c-fl-frame__drop-hint"
:key="i"
:index="i"
:allowDrop="allowDrop"
@object-drop-to="moveOrCreateNewFrame">
</drop-hint>
<resize-handle
v-if="(i !== frames.length - 1)"
:key="i"
:index="i"
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
@init-move="startFrameResizing"
@move="frameResizing"
@end-move="endFrameResizing"
:isEditing="isEditing">
</resize-handle>
</template>
</div>
</div> </div>
<drop-hint
class="c-fl-frame__drop-hint"
:index="-1"
:allow-drop="allowDrop"
@object-drop-to="moveOrCreateNewFrame"
/>
<div class="c-fl-container__frames-holder">
<template
v-for="(frame, i) in frames"
>
<frame-component
:key="frame.id"
class="c-fl-container__frame"
:frame="frame"
:index="i"
:container-index="index"
:is-editing="isEditing"
/>
<drop-hint
:key="i"
class="c-fl-frame__drop-hint"
:index="i"
:allow-drop="allowDrop"
@object-drop-to="moveOrCreateNewFrame"
/>
<resize-handle
v-if="(i !== frames.length - 1)"
:key="i"
:index="i"
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
:is-editing="isEditing"
@init-move="startFrameResizing"
@move="frameResizing"
@end-move="endFrameResizing"
/>
</template>
</div>
</div>
</template> </template>
<script> <script>
import FrameComponent from './frame.vue'; import FrameComponent from './frame.vue';
import Frame from '../utils/frame';
import ResizeHandle from './resizeHandle.vue'; import ResizeHandle from './resizeHandle.vue';
import DropHint from './dropHint.vue'; import DropHint from './dropHint.vue';
@ -84,12 +87,26 @@ const MIN_FRAME_SIZE = 5;
export default { export default {
inject:['openmct'], inject:['openmct'],
props: ['container', 'index', 'rowsLayout', 'isEditing'],
components: { components: {
FrameComponent, FrameComponent,
ResizeHandle, ResizeHandle,
DropHint DropHint
}, },
props: {
container: {
type: Object,
required: true
},
index: {
type: Number,
required: true
},
rowsLayout: Boolean,
isEditing: {
type: Boolean,
default: false
}
},
computed: { computed: {
frames() { frames() {
return this.container.frames; return this.container.frames;
@ -98,6 +115,19 @@ export default {
return `${Math.round(this.container.size)}%` return `${Math.round(this.container.size)}%`
} }
}, },
mounted() {
let context = {
item: this.$parent.domainObject,
addContainer: this.addContainer,
type: 'container',
containerId: this.container.id
}
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, false);
},
beforeDestroy() {
this.unsubscribeSelection();
},
methods: { methods: {
allowDrop(event, index) { allowDrop(event, index) {
if (event.dataTransfer.types.includes('openmct/domain-object-path')) { if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
@ -131,7 +161,7 @@ export default {
insertIndex insertIndex
); );
return; return;
}; }
// move frame. // move frame.
let frameId = event.dataTransfer.getData('frameid'); let frameId = event.dataTransfer.getData('frameid');
let containerIndex = Number(event.dataTransfer.getData('containerIndex')); let containerIndex = Number(event.dataTransfer.getData('containerIndex'));
@ -182,19 +212,6 @@ export default {
startContainerDrag(event) { startContainerDrag(event) {
event.dataTransfer.setData('containerid', this.container.id); event.dataTransfer.setData('containerid', this.container.id);
} }
},
mounted() {
let context = {
item: this.$parent.domainObject,
addContainer: this.addContainer,
type: 'container',
containerId: this.container.id
}
this.unsubscribeSelection = this.openmct.selection.selectable(this.$el, context, false);
},
beforeDestroy() {
this.unsubscribeSelection();
} }
} }
</script> </script>

View File

@ -21,15 +21,16 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div v-show="isValidTarget"> <div v-show="isValidTarget">
<div class="c-drop-hint c-drop-hint--always-show" <div
:class="{'is-mouse-over': isMouseOver}" class="c-drop-hint c-drop-hint--always-show"
@dragover.prevent :class="{'is-mouse-over': isMouseOver}"
@dragenter="dragenter" @dragover.prevent
@dragleave="dragleave" @dragenter="dragenter"
@drop="dropHandler"> @dragleave="dragleave"
</div> @drop="dropHandler"
</div> ></div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -39,7 +40,10 @@
<script> <script>
export default { export default {
props:{ props:{
index: Number, index: {
type: Number,
required: true
},
allowDrop: { allowDrop: {
type: Function, type: Function,
required: true required: true
@ -51,6 +55,16 @@ export default {
isValidTarget: false isValidTarget: false
} }
}, },
mounted() {
document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend);
document.addEventListener('drop', this.dragend);
},
destroyed() {
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);
document.removeEventListener('drop', this.dragend);
},
methods: { methods: {
dragenter() { dragenter() {
this.isMouseOver = true; this.isMouseOver = true;
@ -68,16 +82,6 @@ export default {
dragend() { dragend() {
this.isValidTarget = false; this.isValidTarget = false;
} }
},
mounted() {
document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend);
document.addEventListener('drop', this.dragend);
},
destroyed() {
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);
document.removeEventListener('drop', this.dragend);
} }
} }
</script> </script>

View File

@ -21,67 +21,69 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-fl"> <div class="c-fl">
<div <div
id="js-fl-drag-ghost" id="js-fl-drag-ghost"
class="c-fl__drag-ghost"> class="c-fl__drag-ghost"
</div> ></div>
<div class="c-fl__empty" <div
v-if="areAllContainersEmpty()"> v-if="areAllContainersEmpty()"
<span class="c-fl__empty-message">This Flexible Layout is currently empty</span> class="c-fl__empty"
</div> >
<span class="c-fl__empty-message">This Flexible Layout is currently empty</span>
<div class="c-fl__container-holder"
:class="{
'c-fl--rows': rowsLayout === true
}">
<template v-for="(container, index) in containers">
<drop-hint
class="c-fl-frame__drop-hint"
v-if="index === 0 && containers.length > 1"
:key="index"
:index="-1"
:allow-drop="allowContainerDrop"
@object-drop-to="moveContainer">
</drop-hint>
<container-component
class="c-fl__container"
:key="container.id"
:index="index"
:container="container"
:rowsLayout="rowsLayout"
:isEditing="isEditing"
@move-frame="moveFrame"
@new-frame="setFrameLocation"
@persist="persist">
</container-component>
<resize-handle
v-if="index !== (containers.length - 1)"
:key="index"
:index="index"
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
:isEditing="isEditing"
@init-move="startContainerResizing"
@move="containerResizing"
@end-move="endContainerResizing">
</resize-handle>
<drop-hint
class="c-fl-frame__drop-hint"
v-if="containers.length > 1"
:key="index"
:index="index"
:allowDrop="allowContainerDrop"
@object-drop-to="moveContainer">
</drop-hint>
</template>
</div>
</div> </div>
<div
class="c-fl__container-holder"
:class="{
'c-fl--rows': rowsLayout === true
}"
>
<template v-for="(container, index) in containers">
<drop-hint
v-if="index === 0 && containers.length > 1"
:key="index"
class="c-fl-frame__drop-hint"
:index="-1"
:allow-drop="allowContainerDrop"
@object-drop-to="moveContainer"
/>
<container-component
:key="container.id"
class="c-fl__container"
:index="index"
:container="container"
:rows-layout="rowsLayout"
:is-editing="isEditing"
@move-frame="moveFrame"
@new-frame="setFrameLocation"
@persist="persist"
/>
<resize-handle
v-if="index !== (containers.length - 1)"
:key="index"
:index="index"
:orientation="rowsLayout ? 'vertical' : 'horizontal'"
:is-editing="isEditing"
@init-move="startContainerResizing"
@move="containerResizing"
@end-move="endContainerResizing"
/>
<drop-hint
v-if="containers.length > 1"
:key="index"
class="c-fl-frame__drop-hint"
:index="index"
:allow-drop="allowContainerDrop"
@object-drop-to="moveContainer"
/>
</template>
</div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -465,15 +467,15 @@ export default {
ResizeHandle, ResizeHandle,
DropHint DropHint
}, },
props: {
isEditing: Boolean
},
data() { data() {
return { return {
domainObject: this.layoutObject, domainObject: this.layoutObject,
newFrameLocation: [] newFrameLocation: []
} }
}, },
props: {
isEditing: Boolean
},
computed: { computed: {
layoutDirectionStr() { layoutDirectionStr() {
if (this.rowsLayout) { if (this.rowsLayout) {
@ -489,9 +491,24 @@ export default {
return this.domainObject.configuration.rowsLayout; return this.domainObject.configuration.rowsLayout;
} }
}, },
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('remove', this.removeChildObject);
this.composition.on('add', this.addFrame);
this.RemoveAction = new RemoveAction(this.openmct);
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
},
beforeDestroy() {
this.composition.off('remove', this.removeChildObject);
this.composition.off('add', this.addFrame);
this.unobserve();
},
methods: { methods: {
areAllContainersEmpty() { areAllContainersEmpty() {
return !!!this.containers.filter(container => container.frames.length).length; return !this.containers.filter(container => container.frames.length).length;
}, },
addContainer() { addContainer() {
let container = new Container(); let container = new Container();
@ -589,7 +606,7 @@ export default {
return containerPos !== index && (containerPos - 1) !== index return containerPos !== index && (containerPos - 1) !== index
} }
}, },
persist(index){ persist(index) {
if (index) { if (index) {
this.openmct.objects.mutate(this.domainObject, `configuration.containers[${index}]`, this.containers[index]); this.openmct.objects.mutate(this.domainObject, `configuration.containers[${index}]`, this.containers[index]);
} else { } else {
@ -657,21 +674,6 @@ export default {
this.persist(); this.persist();
} }
},
mounted() {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('remove', this.removeChildObject);
this.composition.on('add', this.addFrame);
this.RemoveAction = new RemoveAction(this.openmct);
this.unobserve = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
},
beforeDestroy() {
this.composition.off('remove', this.removeChildObject);
this.composition.off('add', this.addFrame);
this.unobserve();
} }
} }
</script> </script>

View File

@ -21,56 +21,89 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-fl-frame" <div
:style="{ class="c-fl-frame"
'flex-basis': `${frame.size}%` :style="{
}"> 'flex-basis': `${frame.size}%`
}"
>
<div
ref="frame"
class="c-frame c-fl-frame__drag-wrapper is-selectable u-inspectable is-moveable"
draggable="true"
@dragstart="initDrag"
>
<object-frame
v-if="domainObject"
ref="objectFrame"
:domain-object="domainObject"
:object-path="objectPath"
:has-frame="hasFrame"
:show-edit-view="false"
/>
<div class="c-frame c-fl-frame__drag-wrapper is-selectable u-inspectable is-moveable" <div
draggable="true" v-if="isEditing"
@dragstart="initDrag" v-show="frame.size && frame.size < 100"
ref="frame"> class="c-fl-frame__size-indicator"
>
<object-frame {{ frame.size }}%
v-if="domainObject"
:domain-object="domainObject"
:object-path="objectPath"
:has-frame="hasFrame"
:show-edit-view="false"
ref="objectFrame">
</object-frame>
<div class="c-fl-frame__size-indicator"
v-if="isEditing"
v-show="frame.size && frame.size < 100">
{{frame.size}}%
</div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
import ResizeHandle from './resizeHandle.vue';
import ObjectFrame from '../../../ui/components/ObjectFrame.vue'; import ObjectFrame from '../../../ui/components/ObjectFrame.vue';
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: ['frame', 'index', 'containerIndex', 'isEditing'], components: {
ObjectFrame
},
props: {
frame: {
type: Object,
required: true
},
index: {
type: Number,
required: true
},
containerIndex: {
type: Number,
required: true
},
isEditing: {
type: Boolean,
default: false
}
},
data() { data() {
return { return {
domainObject: undefined, domainObject: undefined,
objectPath: undefined objectPath: undefined
} }
}, },
components: {
ResizeHandle,
ObjectFrame
},
computed: { computed: {
hasFrame() { hasFrame() {
return !this.frame.noFrame; return !this.frame.noFrame;
} }
}, },
mounted() {
if (this.frame.domainObjectIdentifier) {
this.openmct.objects.get(this.frame.domainObjectIdentifier).then((object)=>{
this.setDomainObject(object);
});
}
this.dragGhost = document.getElementById('js-fl-drag-ghost');
},
beforeDestroy() {
if (this.unsubscribeSelection) {
this.unsubscribeSelection();
}
},
methods: { methods: {
setDomainObject(object) { setDomainObject(object) {
this.domainObject = object; this.domainObject = object;
@ -105,20 +138,6 @@ export default {
event.dataTransfer.setData('frameid', this.frame.id); event.dataTransfer.setData('frameid', this.frame.id);
event.dataTransfer.setData('containerIndex', this.containerIndex); event.dataTransfer.setData('containerIndex', this.containerIndex);
} }
},
mounted() {
if (this.frame.domainObjectIdentifier) {
this.openmct.objects.get(this.frame.domainObjectIdentifier).then((object)=>{
this.setDomainObject(object);
});
}
this.dragGhost = document.getElementById('js-fl-drag-ghost');
},
beforeDestroy() {
if (this.unsubscribeSelection) {
this.unsubscribeSelection();
}
} }
} }
</script> </script>

View File

@ -21,22 +21,46 @@
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-fl-frame__resize-handle" <div
:class="[orientation]" v-show="isEditing && !isDragging"
v-show="isEditing && !isDragging" class="c-fl-frame__resize-handle"
@mousedown="mousedown"> :class="[orientation]"
</div> @mousedown="mousedown"
></div>
</template> </template>
<script> <script>
export default { export default {
props: ['orientation', 'index', 'isEditing'], props: {
orientation: {
type: String,
required: true
},
index: {
type: Number,
required: true
},
isEditing: {
type: Boolean,
default: false
}
},
data() { data() {
return { return {
initialPos: 0, initialPos: 0,
isDragging: false, isDragging: false
} }
}, },
mounted() {
document.addEventListener('dragstart', this.setDragging);
document.addEventListener('dragend', this.unsetDragging);
document.addEventListener('drop', this.unsetDragging);
},
destroyed() {
document.removeEventListener('dragstart', this.setDragging);
document.removeEventListener('dragend', this.unsetDragging);
document.removeEventListener('drop', this.unsetDragging);
},
methods: { methods: {
mousedown(event) { mousedown(event) {
event.preventDefault(); event.preventDefault();
@ -75,16 +99,6 @@ export default {
unsetDragging(event) { unsetDragging(event) {
this.isDragging = false; this.isDragging = false;
} }
},
mounted() {
document.addEventListener('dragstart', this.setDragging);
document.addEventListener('dragend', this.unsetDragging);
document.addEventListener('drop', this.unsetDragging);
},
destroyed() {
document.removeEventListener('dragstart', this.setDragging);
document.removeEventListener('dragend', this.unsetDragging);
document.removeEventListener('drop', this.unsetDragging);
} }
} }
</script> </script>

View File

@ -44,19 +44,19 @@ define([
return { return {
show: function (element, isEditing) { show: function (element, isEditing) {
component = new Vue({ component = new Vue({
data() {
return {
isEditing: isEditing
}
},
components: {
FlexibleLayoutComponent: FlexibleLayoutComponent.default
},
provide: { provide: {
openmct, openmct,
layoutObject: domainObject layoutObject: domainObject
}, },
el: element, el: element,
components: {
FlexibleLayoutComponent: FlexibleLayoutComponent.default
},
data() {
return {
isEditing: isEditing
}
},
template: '<flexible-layout-component ref="flexibleLayout" :isEditing="isEditing"></flexible-layout-component>' template: '<flexible-layout-component ref="flexibleLayout" :isEditing="isEditing"></flexible-layout-component>'
}); });
}, },

View File

@ -41,6 +41,7 @@ define([
return { return {
show: function (element) { show: function (element) {
component = new Vue({ component = new Vue({
el: element,
components: { components: {
gridViewComponent: GridViewComponent.default gridViewComponent: GridViewComponent.default
}, },
@ -48,7 +49,6 @@ define([
openmct, openmct,
domainObject domainObject
}, },
el: element,
template: '<grid-view-component></grid-view-component>' template: '<grid-view-component></grid-view-component>'
}); });
}, },

View File

@ -43,6 +43,7 @@ define([
return { return {
show: function (element) { show: function (element) {
component = new Vue({ component = new Vue({
el: element,
components: { components: {
listViewComponent: ListViewComponent.default listViewComponent: ListViewComponent.default
}, },
@ -51,7 +52,6 @@ define([
domainObject, domainObject,
Moment Moment
}, },
el: element,
template: '<list-view-component></list-view-component>' template: '<list-view-component></list-view-component>'
}); });
}, },

View File

@ -1,25 +1,38 @@
<template> <template>
<a class="l-grid-view__item c-grid-item" <a
:class="{ 'is-alias': item.isAlias === true }" class="l-grid-view__item c-grid-item"
:href="objectLink"> :class="{ 'is-alias': item.isAlias === true }"
<div class="c-grid-item__type-icon" :href="objectLink"
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'"> >
<div
class="c-grid-item__type-icon"
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'"
></div>
<div class="c-grid-item__details">
<!-- Name and metadata -->
<div
class="c-grid-item__name"
:title="item.model.name"
>{{ item.model.name }}</div>
<div
class="c-grid-item__metadata"
:title="item.type.name"
>
<span class="c-grid-item__metadata__type">{{ item.type.name }}</span>
</div> </div>
<div class="c-grid-item__details"> </div>
<!-- Name and metadata --> <div class="c-grid-item__controls">
<div class="c-grid-item__name" <div
:title="item.model.name">{{item.model.name}}</div> class="icon-people"
<div class="c-grid-item__metadata" title="Shared"
:title="item.type.name"> ></div>
<span class="c-grid-item__metadata__type">{{item.type.name}}</span> <button
</div> class="c-icon-button icon-info c-info-button"
</div> title="More Info"
<div class="c-grid-item__controls"> ></button>
<div class="icon-people" title='Shared'></div> <div class="icon-pointer-right c-pointer-icon"></div>
<button class="c-icon-button icon-info c-info-button" title='More Info'></button> </div>
<div class="icon-pointer-right c-pointer-icon"></div> </a>
</div>
</a>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -154,6 +167,11 @@ import objectLink from '../../../ui/mixins/object-link';
export default { export default {
mixins: [contextMenuGesture, objectLink], mixins: [contextMenuGesture, objectLink],
props: ['item'] props: {
item: {
type: Object,
required: true
}
}
} }
</script> </script>

View File

@ -1,11 +1,12 @@
<template> <template>
<div class="l-grid-view"> <div class="l-grid-view">
<grid-item v-for="(item, index) in items" <grid-item
:key="index" v-for="(item, index) in items"
:item="item" :key="index"
:object-path="item.objectPath"> :item="item"
</grid-item> :object-path="item.objectPath"
</div> />
</div>
</template> </template>
<style lang="scss"> <style lang="scss">

View File

@ -1,17 +1,31 @@
<template> <template>
<tr class="c-list-item" <tr
:class="{ 'is-alias': item.isAlias === true }" class="c-list-item"
@click="navigate"> :class="{ 'is-alias': item.isAlias === true }"
<td class="c-list-item__name"> @click="navigate"
<a :href="objectLink" ref="objectLink"> >
<div class="c-list-item__type-icon" :class="item.type.cssClass"></div> <td class="c-list-item__name">
<div class="c-list-item__name-value">{{item.model.name}}</div> <a
</a> ref="objectLink"
</td> :href="objectLink"
<td class="c-list-item__type">{{ item.type.name }}</td> >
<td class="c-list-item__date-created">{{ formatTime(item.model.persisted, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td> <div
<td class="c-list-item__date-updated">{{ formatTime(item.model.modified, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td> class="c-list-item__type-icon"
</tr> :class="item.type.cssClass"
></div>
<div class="c-list-item__name-value">{{ item.model.name }}</div>
</a>
</td>
<td class="c-list-item__type">
{{ item.type.name }}
</td>
<td class="c-list-item__date-created">
{{ formatTime(item.model.persisted, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z
</td>
<td class="c-list-item__date-updated">
{{ formatTime(item.model.modified, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z
</td>
</tr>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -64,7 +78,12 @@ import objectLink from '../../../ui/mixins/object-link';
export default { export default {
mixins: [contextMenuGesture, objectLink], mixins: [contextMenuGesture, objectLink],
props: ['item'], props: {
item: {
type: Object,
required: true
}
},
methods: { methods: {
formatTime(timestamp, format) { formatTime(timestamp, format) {
return moment(timestamp).format(format); return moment(timestamp).format(format);

View File

@ -1,55 +1,64 @@
<template> <template>
<div class="c-table c-table--sortable c-list-view"> <div class="c-table c-table--sortable c-list-view">
<table class="c-table__body"> <table class="c-table__body">
<thead class="c-table__header"> <thead class="c-table__header">
<tr> <tr>
<th class="is-sortable" <th
:class="{ class="is-sortable"
'is-sorting': sortBy === 'model.name', :class="{
'asc': ascending, 'is-sorting': sortBy === 'model.name',
'desc': !ascending 'asc': ascending,
}" 'desc': !ascending
@click="sort('model.name', true)"> }"
Name @click="sort('model.name', true)"
</th> >
<th class="is-sortable" Name
:class="{ </th>
'is-sorting': sortBy === 'type.name', <th
'asc': ascending, class="is-sortable"
'desc': !ascending :class="{
}" 'is-sorting': sortBy === 'type.name',
@click="sort('type.name', true)"> 'asc': ascending,
Type 'desc': !ascending
</th> }"
<th class="is-sortable" @click="sort('type.name', true)"
:class="{ >
'is-sorting': sortBy === 'model.persisted', Type
'asc': ascending, </th>
'desc': !ascending <th
}" class="is-sortable"
@click="sort('model.persisted', false)"> :class="{
Created Date 'is-sorting': sortBy === 'model.persisted',
</th> 'asc': ascending,
<th class="is-sortable" 'desc': !ascending
:class="{ }"
'is-sorting': sortBy === 'model.modified', @click="sort('model.persisted', false)"
'asc': ascending, >
'desc': !ascending Created Date
}" </th>
@click="sort('model.modified', false)"> <th
Updated Date class="is-sortable"
</th> :class="{
</tr> 'is-sorting': sortBy === 'model.modified',
</thead> 'asc': ascending,
<tbody> 'desc': !ascending
<list-item v-for="item in sortedItems" }"
:key="item.objectKeyString" @click="sort('model.modified', false)"
:item="item" >
:object-path="item.objectPath"> Updated Date
</list-item> </th>
</tbody> </tr>
</table> </thead>
</div> <tbody>
<list-item
v-for="item in sortedItems"
:key="item.objectKeyString"
:item="item"
:object-path="item.objectPath"
/>
</tbody>
</table>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -93,8 +102,6 @@
</style> </style>
<script> <script>
import lodash from 'lodash';
import compositionLoader from './composition-loader'; import compositionLoader from './composition-loader';
import ListItem from './ListItem.vue'; import ListItem from './ListItem.vue';
@ -120,7 +127,7 @@ export default {
}; };
}, },
computed: { computed: {
sortedItems () { sortedItems() {
let sortedItems = _.sortBy(this.items, this.sortBy); let sortedItems = _.sortBy(this.items, this.sortBy);
if (!this.ascending) { if (!this.ascending) {
sortedItems = sortedItems.reverse(); sortedItems = sortedItems.reverse();

View File

@ -20,21 +20,30 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-about c-about--licenses"> <div class="c-about c-about--licenses">
<h1>Open MCT Third Party Licenses</h1> <h1>Open MCT Third Party Licenses</h1>
<p>This software includes components released under the following licenses:</p> <p>This software includes components released under the following licenses:</p>
<div v-for="(pkg, key) in packages" :key="key" class="c-license"> <div
<h2 class="c-license__name">{{key}}</h2> v-for="(pkg, key) in packages"
<div class="c-license__details"> :key="key"
<span class="c-license__author"><em>Author</em> {{pkg.publisher}}</span> | class="c-license"
<span class="c-license__license"><em>License(s)</em> {{pkg.licenses}}</span> | >
<span class="c-license__repo"><em>Repository</em> <a :href="pkg.repository" target="_blank">{{pkg.repository}}</a></span> <h2 class="c-license__name">
</div> {{ key }}
<div class="c-license__text"> </h2>
<p>{{pkg.licenseText}}</p> <div class="c-license__details">
</div> <span class="c-license__author"><em>Author</em> {{ pkg.publisher }}</span> |
<span class="c-license__license"><em>License(s)</em> {{ pkg.licenses }}</span> |
<span class="c-license__repo"><em>Repository</em> <a
:href="pkg.repository"
target="_blank"
>{{ pkg.repository }}</a></span>
</div>
<div class="c-license__text">
<p>{{ pkg.licenseText }}</p>
</div> </div>
</div> </div>
</div>
</template> </template>
<style lang="sass"> <style lang="sass">
</style> </style>
@ -49,4 +58,3 @@ export default {
} }
} }
</script> </script>

View File

@ -141,7 +141,6 @@ function (
var self = this, var self = this,
snapshot = new Vue({ snapshot = new Vue({
template: SnapshotTemplate,
data: function () { data: function () {
return { return {
embed: self.embed embed: self.embed
@ -151,7 +150,8 @@ function (
formatTime: self.formatTime, formatTime: self.formatTime,
annotateSnapshot: annotateSnapshot(self.openmct), annotateSnapshot: annotateSnapshot(self.openmct),
findInArray: self.findInArray findInArray: self.findInArray
} },
template: SnapshotTemplate
}); });
var snapshotOverlay = this.openmct.overlays.overlay({ var snapshotOverlay = this.openmct.overlays.overlay({

View File

@ -84,7 +84,6 @@ function (
}; };
var NotebookVue = Vue.extend({ var NotebookVue = Vue.extend({
template: NotebookTemplate,
provide: {openmct: self.openmct, domainObject: self.domainObject}, provide: {openmct: self.openmct, domainObject: self.domainObject},
components: { components: {
'notebook-entry': entryComponent, 'notebook-entry': entryComponent,
@ -111,7 +110,8 @@ function (
newEntry: self.newEntry, newEntry: self.newEntry,
filterBySearch: self.filterBySearch, filterBySearch: self.filterBySearch,
sort: self.sort sort: self.sort
} },
template: NotebookTemplate
}); });
this.NotebookVue = new NotebookVue(); this.NotebookVue = new NotebookVue();

View File

@ -1,44 +1,58 @@
<template> <template>
<div class="c-tabs-view"> <div class="c-tabs-view">
<div class="c-tabs-view__tabs-holder c-tabs" <div
:class="{ class="c-tabs-view__tabs-holder c-tabs"
'is-dragging': isDragging, :class="{
'is-mouse-over': allowDrop 'is-dragging': isDragging,
}"> 'is-mouse-over': allowDrop
<div class="c-drop-hint" }"
@drop="onDrop" >
@dragenter="dragenter" <div
@dragleave="dragleave"> class="c-drop-hint"
</div> @drop="onDrop"
<div class="c-tabs-view__empty-message" @dragenter="dragenter"
v-if="!tabsList.length > 0">Drag objects here to add them to this view.</div> @dragleave="dragleave"
<button class="c-tabs-view__tab c-tab" ></div>
v-for="(tab,index) in tabsList" <div
:key="index" v-if="!tabsList.length > 0"
:class="[ class="c-tabs-view__empty-message"
{'is-current': isCurrent(tab)}, >
tab.type.definition.cssClass Drag objects here to add them to this view.
]"
@click="showTab(tab)">
<span class="c-button__label">{{tab.domainObject.name}}</span>
</button>
</div> </div>
<div class="c-tabs-view__object-holder" <button
v-for="(tab, index) in tabsList" v-for="(tab,index) in tabsList"
:key="index" :key="index"
:class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}"> class="c-tabs-view__tab c-tab"
<div v-if="currentTab" :class="[
class="c-tabs-view__object-name l-browse-bar__object-name--w" {'is-current': isCurrent(tab)},
:class="currentTab.type.definition.cssClass"> tab.type.definition.cssClass
<div class="l-browse-bar__object-name"> ]"
{{currentTab.domainObject.name}} @click="showTab(tab)"
</div> >
</div> <span class="c-button__label">{{ tab.domainObject.name }}</span>
<object-view class="c-tabs-view__object" </button>
:object="tab.domainObject">
</object-view>
</div>
</div> </div>
<div
v-for="(tab, index) in tabsList"
:key="index"
class="c-tabs-view__object-holder"
:class="{'c-tabs-view__object-holder--hidden': !isCurrent(tab)}"
>
<div
v-if="currentTab"
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.domainObject.name }}
</div>
</div>
<object-view
class="c-tabs-view__object"
:object="tab.domainObject"
/>
</div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -129,6 +143,25 @@ export default {
allowDrop: false allowDrop: false
}; };
}, },
mounted() {
if (this.composition) {
this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.onReorder);
this.composition.load();
}
document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend);
},
destroyed() {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.onReorder);
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);
},
methods:{ methods:{
showTab(tab) { showTab(tab) {
this.currentTab = tab; this.currentTab = tab;
@ -169,7 +202,7 @@ export default {
onDrop(e) { onDrop(e) {
this.setCurrentTab = true; this.setCurrentTab = true;
}, },
dragstart (e) { dragstart(e) {
if (e.dataTransfer.types.includes('openmct/domain-object-path')) { if (e.dataTransfer.types.includes('openmct/domain-object-path')) {
this.isDragging = true; this.isDragging = true;
} }
@ -187,25 +220,6 @@ export default {
isCurrent(tab) { isCurrent(tab) {
return _.isEqual(this.currentTab, tab) return _.isEqual(this.currentTab, tab)
} }
},
mounted () {
if (this.composition) {
this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.onReorder);
this.composition.load();
}
document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend);
},
destroyed() {
this.composition.off('add', this.addItem);
this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.onReorder);
document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend);
} }
} }
</script> </script>

View File

@ -44,6 +44,7 @@ define([
return { return {
show: function (element) { show: function (element) {
component = new Vue({ component = new Vue({
el: element,
components: { components: {
TabsComponent: TabsComponent.default TabsComponent: TabsComponent.default
}, },
@ -52,7 +53,6 @@ define([
domainObject, domainObject,
composition: openmct.composition.get(domainObject) composition: openmct.composition.get(domainObject)
}, },
el: element,
template: '<tabs-component></tabs-component>' template: '<tabs-component></tabs-component>'
}); });
}, },

View File

@ -54,11 +54,11 @@ define([
openmct, openmct,
tableConfiguration tableConfiguration
}, },
el: element,
components: { components: {
TableConfiguration: TableConfigurationComponent.default TableConfiguration: TableConfigurationComponent.default
}, },
template: '<table-configuration></table-configuration>', template: '<table-configuration></table-configuration>'
el: element
}); });
}, },
destroy: function () { destroy: function () {

View File

@ -54,20 +54,20 @@ define([
return { return {
show: function (element, editMode) { show: function (element, editMode) {
component = new Vue({ component = new Vue({
el: element,
components: {
TableComponent: TableComponent.default
},
data() { data() {
return { return {
isEditing: editMode isEditing: editMode
} }
}, },
components: {
TableComponent: TableComponent.default
},
provide: { provide: {
openmct, openmct,
table, table,
objectPath objectPath
}, },
el: element,
template: '<table-component :isEditing="isEditing" :enableMarking="true"></table-component>' template: '<table-component :isEditing="isEditing" :enableMarking="true"></table-component>'
}); });
}, },

View File

@ -1,14 +1,19 @@
<template> <template>
<div v-if="filterNames.length > 0" <div
:title=title v-if="filterNames.length > 0"
class="c-filter-indication" :title="title"
:class="{ 'c-filter-indication--mixed': hasMixedFilters }"> class="c-filter-indication"
<span class="c-filter-indication__mixed">{{ label }}</span> :class="{ 'c-filter-indication--mixed': hasMixedFilters }"
<span v-for="(name, index) in filterNames" >
class="c-filter-indication__label"> <span class="c-filter-indication__mixed">{{ label }}</span>
{{ name }} <span
</span> v-for="(name, index) in filterNames"
</div> :key="index"
class="c-filter-indication__label"
>
{{ name }}
</span>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -53,113 +58,113 @@
</style> </style>
<script> <script>
const FILTER_INDICATOR_LABEL = 'Filters:'; const FILTER_INDICATOR_LABEL = 'Filters:';
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:'; const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.'; const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.';
const FILTER_INDICATOR_TITLE_MIXED = 'A mix of data filter values are being applied to this view.'; const FILTER_INDICATOR_TITLE_MIXED = 'A mix of data filter values are being applied to this view.';
const USE_GLOBAL = 'useGlobal'; const USE_GLOBAL = 'useGlobal';
export default { export default {
inject: ['openmct', 'table'], inject: ['openmct', 'table'],
data() { data() {
return { return {
filterNames: [], filterNames: [],
filteredTelemetry: {} filteredTelemetry: {}
}
},
computed: {
hasMixedFilters() {
let filtersToCompare = _.omit(this.filteredTelemetry[Object.keys(this.filteredTelemetry)[0]], [USE_GLOBAL]);
return Object.values(this.filteredTelemetry).some(filters => {
return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL]));
});
},
label() {
if (this.hasMixedFilters) {
return FILTER_INDICATOR_LABEL_MIXED;
} else {
return FILTER_INDICATOR_LABEL;
} }
}, },
computed: { title() {
hasMixedFilters() { if (this.hasMixedFilters) {
let filtersToCompare = _.omit(this.filteredTelemetry[Object.keys(this.filteredTelemetry)[0]], [USE_GLOBAL]); return FILTER_INDICATOR_TITLE_MIXED;
return Object.values(this.filteredTelemetry).some(filters => { } else {
return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL])); return FILTER_INDICATOR_TITLE;
});
},
label() {
if (this.hasMixedFilters) {
return FILTER_INDICATOR_LABEL_MIXED;
} else {
return FILTER_INDICATOR_LABEL;
}
},
title() {
if (this.hasMixedFilters) {
return FILTER_INDICATOR_TITLE_MIXED;
} else {
return FILTER_INDICATOR_TITLE;
}
} }
}, }
methods: { },
setFilterNames() { mounted() {
let names = []; let filters = this.table.configuration.getConfiguration().filters || {};
let composition = this.openmct.composition.get(this.table.configuration.domainObject); this.table.configuration.on('change', this.handleConfigurationChanges);
this.updateFilters(filters);
},
destroyed() {
this.table.configuration.off('change', this.handleConfigurationChanges);
},
methods: {
setFilterNames() {
let names = [];
let composition = this.openmct.composition.get(this.table.configuration.domainObject);
composition && composition.load().then((domainObjects) => { composition && composition.load().then((domainObjects) => {
domainObjects.forEach(telemetryObject => { domainObjects.forEach(telemetryObject => {
let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier); let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier);
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values(); let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
let filters = this.filteredTelemetry[keyString]; let filters = this.filteredTelemetry[keyString];
if (filters !== undefined) { if (filters !== undefined) {
names.push(this.getFilterNamesFromMetadata(filters, metadataValues)); names.push(this.getFilterNamesFromMetadata(filters, metadataValues));
}
});
names = _.flatten(names);
this.filterNames = names.length === 0 ? names : Array.from(new Set(names));
});
},
getFilterNamesFromMetadata(filters, metadataValues) {
let filterNames = [];
filters = _.omit(filters, [USE_GLOBAL]);
Object.keys(filters).forEach(key => {
if (!_.isEmpty(filters[key])) {
metadataValues.forEach(metadatum => {
if (key === metadatum.key) {
if (typeof metadatum.filters[0] === "object") {
filterNames.push(this.getFilterLabels(filters[key], metadatum));
} else {
filterNames.push(metadatum.name);
}
}
});
} }
}); });
return _.flatten(filterNames); names = _.flatten(names);
}, this.filterNames = names.length === 0 ? names : Array.from(new Set(names));
getFilterLabels(filterObject, metadatum, ) { });
let filterLabels = []; },
Object.values(filterObject).forEach(comparator => { getFilterNamesFromMetadata(filters, metadataValues) {
comparator.forEach(filterValue => { let filterNames = [];
metadatum.filters[0].possibleValues.forEach(option => { filters = _.omit(filters, [USE_GLOBAL]);
if (option.value === filterValue) {
filterLabels.push(option.label); Object.keys(filters).forEach(key => {
if (!_.isEmpty(filters[key])) {
metadataValues.forEach(metadatum => {
if (key === metadatum.key) {
if (typeof metadatum.filters[0] === "object") {
filterNames.push(this.getFilterLabels(filters[key], metadatum));
} else {
filterNames.push(metadatum.name);
} }
}); }
});
}
});
return _.flatten(filterNames);
},
getFilterLabels(filterObject, metadatum,) {
let filterLabels = [];
Object.values(filterObject).forEach(comparator => {
comparator.forEach(filterValue => {
metadatum.filters[0].possibleValues.forEach(option => {
if (option.value === filterValue) {
filterLabels.push(option.label);
}
}); });
}); });
});
return filterLabels; return filterLabels;
}, },
handleConfigurationChanges(configuration) { handleConfigurationChanges(configuration) {
if (!_.eq(this.filteredTelemetry, configuration.filters)) { if (!_.eq(this.filteredTelemetry, configuration.filters)) {
this.updateFilters(configuration.filters || {}); this.updateFilters(configuration.filters || {});
}
},
updateFilters(filters) {
this.filteredTelemetry = JSON.parse(JSON.stringify(filters));
this.setFilterNames();
} }
}, },
mounted() { updateFilters(filters) {
let filters = this.table.configuration.getConfiguration().filters || {}; this.filteredTelemetry = JSON.parse(JSON.stringify(filters));
this.table.configuration.on('change', this.handleConfigurationChanges); this.setFilterNames();
this.updateFilters(filters);
},
destroyed() {
this.table.configuration.off('change', this.handleConfigurationChanges);
} }
} }
}
</script> </script>

View File

@ -20,7 +20,12 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<td @click="selectCell($event.currentTarget, columnKey)" :title="formattedValue">{{formattedValue}}</td> <td
:title="formattedValue"
@click="selectCell($event.currentTarget, columnKey)"
>
{{ formattedValue }}
</td>
</template> </template>
<script> <script>
export default { export default {
@ -32,11 +37,20 @@ export default {
}, },
columnKey: { columnKey: {
type: String, type: String,
require: true required: true
}, },
objectPath: { objectPath: {
type: Array, type: Array,
require: false required: true
}
},
computed: {
formattedValue() {
return this.row.getFormattedValue(this.columnKey);
},
isSelectable() {
let column = this.row.columns[this.columnKey];
return column && column.selectable;
} }
}, },
methods: { methods: {
@ -57,15 +71,6 @@ export default {
}], false); }], false);
event.stopPropagation(); event.stopPropagation();
} }
},
},
computed: {
formattedValue() {
return this.row.getFormattedValue(this.columnKey);
},
isSelectable() {
let column = this.row.columns[this.columnKey];
return column && column.selectable;
} }
} }
}; };

View File

@ -29,30 +29,49 @@
drop: columnMoveEnd, drop: columnMoveEnd,
dragleave: hideDropTarget, dragleave: hideDropTarget,
dragover: dragOverColumn dragover: dragOverColumn
} : {}"> } : {}"
<div class="c-telemetry-table__headers__content" :class="[ >
<div
class="c-telemetry-table__headers__content"
:class="[
isSortable ? 'is-sortable' : '', isSortable ? 'is-sortable' : '',
isSortable && sortOptions.key === headerKey ? 'is-sorting' : '', isSortable && sortOptions.key === headerKey ? 'is-sorting' : '',
isSortable && sortOptions.direction].join(' ')"> isSortable && sortOptions.direction].join(' ')"
<div class="c-telemetry-table__resize-hitarea" >
@mousedown="resizeColumnStart" <div
></div> class="c-telemetry-table__resize-hitarea"
<slot></slot> @mousedown="resizeColumnStart"
</div> ></div>
<slot></slot>
</div>
</th> </th>
</template> </template>
<script> <script>
import _ from 'lodash';
const MOVE_COLUMN_DT_TYPE = 'movecolumnfromindex'; const MOVE_COLUMN_DT_TYPE = 'movecolumnfromindex';
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
headerKey: String, headerKey: {
headerIndex: Number, type: String,
isHeaderTitle: Boolean, default: undefined
sortOptions: Object, },
columnWidth: Number, headerIndex: {
type: Number,
default: undefined
},
isHeaderTitle: {
type: Boolean,
default: undefined
},
sortOptions: {
type: Object,
default: undefined
},
columnWidth: {
type: Number,
default: undefined
},
hotzone: Boolean, hotzone: Boolean,
isEditing: Boolean isEditing: Boolean
}, },
@ -71,13 +90,13 @@ export default {
event.preventDefault(); event.preventDefault();
}, },
resizeColumnEnd(event) { resizeColumnEnd(event) {
this.resizeStartX = undefined; this.resizeStartX = undefined;
this.resizeStartWidth = undefined; this.resizeStartWidth = undefined;
document.removeEventListener('mousemove', this.resizeColumn); document.removeEventListener('mousemove', this.resizeColumn);
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.$emit('resizeColumnEnd'); this.$emit('resizeColumnEnd');
}, },
resizeColumn(event) { resizeColumn(event) {
let delta = event.clientX - this.resizeStartX; let delta = event.clientX - this.resizeStartX;
@ -94,7 +113,7 @@ export default {
return [...event.dataTransfer.types].includes(MOVE_COLUMN_DT_TYPE); return [...event.dataTransfer.types].includes(MOVE_COLUMN_DT_TYPE);
}, },
dragOverColumn(event) { dragOverColumn(event) {
if (this.isColumnMoveEvent(event)){ if (this.isColumnMoveEvent(event)) {
event.preventDefault(); event.preventDefault();
this.updateDropOffset(event.currentTarget, event.clientX); this.updateDropOffset(event.currentTarget, event.clientX);
} else { } else {
@ -114,19 +133,19 @@ export default {
this.$emit('dropTargetOffsetChanged', dropOffsetLeft); this.$emit('dropTargetOffsetChanged', dropOffsetLeft);
this.$emit('dropTargetActive', true); this.$emit('dropTargetActive', true);
}, },
hideDropTarget(){ hideDropTarget() {
this.$emit('dropTargetActive', false); this.$emit('dropTargetActive', false);
}, },
columnMoveEnd(event){ columnMoveEnd(event) {
if (this.isColumnMoveEvent(event)){ if (this.isColumnMoveEvent(event)) {
let toIndex = this.headerIndex; let toIndex = this.headerIndex;
let fromIndex = event.dataTransfer.getData(MOVE_COLUMN_DT_TYPE); let fromIndex = event.dataTransfer.getData(MOVE_COLUMN_DT_TYPE);
if (event.offsetX < event.target.offsetWidth / 2) { if (event.offsetX < event.target.offsetWidth / 2) {
if (toIndex > fromIndex){ if (toIndex > fromIndex) {
toIndex--; toIndex--;
} }
} else { } else {
if (toIndex < fromIndex){ if (toIndex < fromIndex) {
toIndex++; toIndex++;
} }
} }

View File

@ -1,18 +1,50 @@
<template> <template>
<div class="c-properties"> <div class="c-properties">
<template v-if="isEditing"> <template v-if="isEditing">
<div class="c-properties__header">Table Column Size</div> <div class="c-properties__header">
Table Column Size
</div>
<ul class="c-properties__section"> <ul class="c-properties__section">
<li class="c-properties__row"> <li class="c-properties__row">
<div class="c-properties__label" title="Auto-size table"><label for="AutoSizeControl">Auto-size</label></div> <div
<div class="c-properties__value"><input type="checkbox" id="AutoSizeControl" :checked="configuration.autosize !== false" @change="toggleAutosize()"></div> class="c-properties__label"
title="Auto-size table"
>
<label for="AutoSizeControl">Auto-size</label>
</div>
<div class="c-properties__value">
<input
id="AutoSizeControl"
type="checkbox"
:checked="configuration.autosize !== false"
@change="toggleAutosize()"
>
</div>
</li> </li>
</ul> </ul>
<div class="c-properties__header">Table Column Visibility</div> <div class="c-properties__header">
Table Column Visibility
</div>
<ul class="c-properties__section"> <ul class="c-properties__section">
<li class="c-properties__row" v-for="(title, key) in headers"> <li
<div class="c-properties__label" title="Show or hide column"><label :for="key + 'ColumnControl'">{{title}}</label></div> v-for="(title, key) in headers"
<div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div> :key="key"
class="c-properties__row"
>
<div
class="c-properties__label"
title="Show or hide column"
>
<label :for="key + 'ColumnControl'">{{ title }}</label>
</div>
<div class="c-properties__value">
<input
:id="key + 'ColumnControl'"
type="checkbox"
:checked="configuration.hiddenColumns[key] !== true"
@change="toggleColumn(key)"
>
</div>
</li> </li>
</ul> </ul>
</template> </template>
@ -34,6 +66,28 @@ export default {
configuration: this.tableConfiguration.getConfiguration() configuration: this.tableConfiguration.getConfiguration()
} }
}, },
mounted() {
this.unlisteners = [];
this.openmct.editor.on('isEditing', this.toggleEdit);
let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject);
compositionCollection.load()
.then((composition) => {
this.addColumnsForAllObjects(composition);
this.updateHeaders(this.tableConfiguration.getAllHeaders());
compositionCollection.on('add', this.addObject);
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
compositionCollection.on('remove', this.removeObject);
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
});
},
destroyed() {
this.tableConfiguration.destroy();
this.openmct.editor.off('isEditing', this.toggleEdit);
this.unlisteners.forEach((unlisten) => unlisten());
},
methods: { methods: {
updateHeaders(headers) { updateHeaders(headers) {
this.headers = headers; this.headers = headers;
@ -45,7 +99,7 @@ export default {
this.tableConfiguration.updateConfiguration(this.configuration); this.tableConfiguration.updateConfiguration(this.configuration);
}, },
addObject(domainObject) { addObject(domainObject) {
this.addColumnsForObject(domainObject, true); this.addColumnsForObject(domainObject, true);
this.updateHeaders(this.tableConfiguration.getAllHeaders()); this.updateHeaders(this.tableConfiguration.getAllHeaders());
}, },
removeObject(objectIdentifier) { removeObject(objectIdentifier) {
@ -70,28 +124,6 @@ export default {
this.tableConfiguration.addSingleColumnForObject(telemetryObject, column); this.tableConfiguration.addSingleColumnForObject(telemetryObject, column);
}); });
} }
},
mounted() {
this.unlisteners = [];
this.openmct.editor.on('isEditing', this.toggleEdit);
let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject);
compositionCollection.load()
.then((composition) => {
this.addColumnsForAllObjects(composition);
this.updateHeaders(this.tableConfiguration.getAllHeaders());
compositionCollection.on('add', this.addObject);
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
compositionCollection.on('remove', this.removeObject);
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
});
},
destroyed() {
this.tableConfiguration.destroy();
this.openmct.editor.off('isEditing', this.toggleEdit);
this.unlisteners.forEach((unlisten) => unlisten());
} }
} }
</script> </script>

View File

@ -20,22 +20,25 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<tr :style="{ top: rowTop }" <tr
:style="{ top: rowTop }"
class="noselect" class="noselect"
:class="[ :class="[
rowClass, rowClass,
{'is-selected': marked} {'is-selected': marked}
]" ]"
v-on="listeners"> v-on="listeners"
<component v-for="(title, key) in headers" >
:key="key" <component
:is="componentList[key]" :is="componentList[key]"
:columnKey="key" v-for="(title, key) in headers"
:key="key"
:column-key="key"
:style="columnWidths[key] === undefined ? {} : { width: columnWidths[key] + 'px', 'max-width': columnWidths[key] + 'px'}" :style="columnWidths[key] === undefined ? {} : { width: columnWidths[key] + 'px', 'max-width': columnWidths[key] + 'px'}"
:class="[cellLimitClasses[key], selectableColumns[key] ? 'is-selectable' : '']" :class="[cellLimitClasses[key], selectableColumns[key] ? 'is-selectable' : '']"
:objectPath="objectPath" :object-path="objectPath"
:row="row"> :row="row"
</component> />
</tr> </tr>
</template> </template>
@ -55,21 +58,9 @@
import TableCell from './table-cell.vue'; import TableCell from './table-cell.vue';
export default { export default {
inject: ['openmct', 'objectPath'], inject: ['openmct'],
data: function () { components: {
return { TableCell
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
rowClass: this.row.getRowClass(),
cellLimitClasses: this.row.getCellLimitClasses(),
componentList: Object.keys(this.headers).reduce((components, header) => {
components[header] = this.row.getCellComponentName(header) || 'table-cell';
return components
}, {}),
selectableColumns : Object.keys(this.row.columns).reduce((selectable, columnKeys) => {
selectable[columnKeys] = this.row.columns[columnKeys].selectable;
return selectable;
}, {})
}
}, },
props: { props: {
headers: { headers: {
@ -86,7 +77,7 @@ export default {
}, },
objectPath: { objectPath: {
type: Array, type: Array,
required: false required: true
}, },
rowIndex: { rowIndex: {
type: Number, type: Number,
@ -109,6 +100,42 @@ export default {
default: false default: false
} }
}, },
data: function () {
return {
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
rowClass: this.row.getRowClass(),
cellLimitClasses: this.row.getCellLimitClasses(),
componentList: Object.keys(this.headers).reduce((components, header) => {
components[header] = this.row.getCellComponentName(header) || 'table-cell';
return components
}, {}),
selectableColumns : Object.keys(this.row.columns).reduce((selectable, columnKeys) => {
selectable[columnKeys] = this.row.columns[columnKeys].selectable;
return selectable;
}, {})
}
},
computed: {
listeners() {
let listenersObject = {
click: this.markRow
}
if (this.row.getContextMenuActions().length) {
listenersObject.contextmenu = this.showContextMenu;
}
return listenersObject;
}
},
// TODO: use computed properties
watch: {
rowOffset: 'calculateRowTop',
row: {
handler: 'formatRow',
deep: true
}
},
methods: { methods: {
calculateRowTop: function (rowOffset) { calculateRowTop: function (rowOffset) {
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px'; this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
@ -136,7 +163,7 @@ export default {
}, },
selectCell(element, columnKey) { selectCell(element, columnKey) {
if (this.selectableColumns[columnKey]) { if (this.selectableColumns[columnKey]) {
//TODO: This is a hack. Cannot get parent this way. //TODO: This is a hack. Cannot get parent this way.
this.openmct.selection.select([{ this.openmct.selection.select([{
element: element, element: element,
context: { context: {
@ -153,7 +180,7 @@ export default {
event.stopPropagation(); event.stopPropagation();
} }
}, },
showContextMenu: function (event) { showContextMenu: function (event) {
event.preventDefault(); event.preventDefault();
this.openmct.objects.get(this.row.objectKeyString).then((domainObject) => { this.openmct.objects.get(this.row.objectKeyString).then((domainObject) => {
@ -163,30 +190,6 @@ export default {
this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions()); this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions());
}); });
} }
},
// TODO: use computed properties
watch: {
rowOffset: 'calculateRowTop',
row: {
handler: 'formatRow',
deep: true
}
},
components: {
TableCell
},
computed: {
listeners() {
let listenersObject = {
click: this.markRow
}
if (this.row.getContextMenuActions().length) {
listenersObject.contextmenu = this.showContextMenu;
}
return listenersObject;
}
} }
} }
</script> </script>

View File

@ -22,131 +22,178 @@
<template> <template>
<div class="c-table-wrapper"> <div class="c-table-wrapper">
<div class="c-table-control-bar c-control-bar"> <div class="c-table-control-bar c-control-bar">
<button class="c-button icon-download labeled" <button
v-if="allowExport" v-if="allowExport"
v-on:click="exportAllDataAsCSV()" class="c-button icon-download labeled"
title="Export This View's Data"> title="Export This View's Data"
@click="exportAllDataAsCSV()"
>
<span class="c-button__label">Export Table Data</span> <span class="c-button__label">Export Table Data</span>
</button> </button>
<button class="c-button icon-download labeled" <button
v-if="allowExport" v-if="allowExport"
v-show="markedRows.length" v-show="markedRows.length"
v-on:click="exportMarkedDataAsCSV()" class="c-button icon-download labeled"
title="Export Marked Rows As CSV"> title="Export Marked Rows As CSV"
@click="exportMarkedDataAsCSV()"
>
<span class="c-button__label">Export Marked Rows</span> <span class="c-button__label">Export Marked Rows</span>
</button> </button>
<button class="c-button icon-x labeled" <button
v-show="markedRows.length" v-show="markedRows.length"
v-on:click="unmarkAllRows()" class="c-button icon-x labeled"
title="Unmark All Rows"> title="Unmark All Rows"
@click="unmarkAllRows()"
>
<span class="c-button__label">Unmark All Rows</span> <span class="c-button__label">Unmark All Rows</span>
</button> </button>
<div v-if="enableMarking" <div
class="c-separator"> v-if="enableMarking"
</div> class="c-separator"
<button v-if="enableMarking" ></div>
class="c-button icon-pause pause-play labeled" <button
:class=" paused ? 'icon-play is-paused' : 'icon-pause'" v-if="enableMarking"
v-on:click="togglePauseByButton()" class="c-button icon-pause pause-play labeled"
:title="paused ? 'Continue Data Flow' : 'Pause Data Flow'"> :class=" paused ? 'icon-play is-paused' : 'icon-pause'"
<span class="c-button__label"> :title="paused ? 'Continue Data Flow' : 'Pause Data Flow'"
{{paused ? 'Play' : 'Pause'}} @click="togglePauseByButton()"
</span> >
<span class="c-button__label">
{{ paused ? 'Play' : 'Pause' }}
</span>
</button> </button>
<slot name="buttons"></slot> <slot name="buttons"></slot>
</div> </div>
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar" <div
:class="{ class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
'loading': loading, :class="{
'paused' : paused 'loading': loading,
}"> 'paused' : paused
}"
>
<div :style="{ 'max-width': widthWithScroll, 'min-width': '150px'}">
<slot></slot>
</div>
<div :style="{ 'max-width': widthWithScroll, 'min-width': '150px'}"><slot></slot></div> <div
v-if="isDropTargetActive"
<div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div> class="c-telemetry-table__drop-target"
:style="dropTargetStyle"
></div>
<!-- Headers table --> <!-- Headers table -->
<div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}"> <div
ref="headersTable"
class="c-telemetry-table__headers-w js-table__headers-w"
:style="{ 'max-width': widthWithScroll}"
>
<table class="c-table__headers c-telemetry-table__headers"> <table class="c-table__headers c-telemetry-table__headers">
<thead> <thead>
<tr class="c-telemetry-table__headers__labels"> <tr class="c-telemetry-table__headers__labels">
<table-column-header <table-column-header
v-for="(title, key, headerIndex) in headers" v-for="(title, key, headerIndex) in headers"
:key="key" :key="key"
:headerKey="key" :header-key="key"
:headerIndex="headerIndex" :header-index="headerIndex"
:column-width="columnWidths[key]"
:sort-options="sortOptions"
:is-editing="isEditing"
@sort="allowSorting && sortBy(key)" @sort="allowSorting && sortBy(key)"
@resizeColumn="resizeColumn" @resizeColumn="resizeColumn"
@dropTargetOffsetChanged="setDropTargetOffset" @dropTargetOffsetChanged="setDropTargetOffset"
@dropTargetActive="dropTargetActive" @dropTargetActive="dropTargetActive"
@reorderColumn="reorderColumn" @reorderColumn="reorderColumn"
@resizeColumnEnd="updateConfiguredColumnWidths" @resizeColumnEnd="updateConfiguredColumnWidths"
:columnWidth="columnWidths[key]" >
:sortOptions="sortOptions" <span class="c-telemetry-table__headers__label">{{ title }}</span>
:isEditing="isEditing"
><span class="c-telemetry-table__headers__label">{{title}}</span>
</table-column-header> </table-column-header>
</tr> </tr>
<tr v-if="allowFiltering" class="c-telemetry-table__headers__filter"> <tr
v-if="allowFiltering"
class="c-telemetry-table__headers__filter"
>
<table-column-header <table-column-header
v-for="(title, key, headerIndex) in headers" v-for="(title, key, headerIndex) in headers"
:key="key" :key="key"
:headerKey="key" :header-key="key"
:headerIndex="headerIndex" :header-index="headerIndex"
:column-width="columnWidths[key]"
:is-editing="isEditing"
@resizeColumn="resizeColumn" @resizeColumn="resizeColumn"
@dropTargetOffsetChanged="setDropTargetOffset" @dropTargetOffsetChanged="setDropTargetOffset"
@dropTargetActive="dropTargetActive" @dropTargetActive="dropTargetActive"
@reorderColumn="reorderColumn" @reorderColumn="reorderColumn"
@resizeColumnEnd="updateConfiguredColumnWidths" @resizeColumnEnd="updateConfiguredColumnWidths"
:columnWidth="columnWidths[key]" >
:isEditing="isEditing" <search
>
<search class="c-table__search"
v-model="filters[key]" v-model="filters[key]"
v-on:input="filterChanged(key)" class="c-table__search"
v-on:clear="clearFilter(key)" /> @input="filterChanged(key)"
@clear="clearFilter(key)"
/>
</table-column-header> </table-column-header>
</tr> </tr>
</thead> </thead>
</table> </table>
</div> </div>
<!-- Content table --> <!-- Content table -->
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}"> <div
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div> class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w"
<table class="c-table__body c-telemetry-table__body js-telemetry-table__content" :style="{ 'max-width': widthWithScroll}"
:style="{ height: totalHeight + 'px'}"> @scroll="scroll"
>
<div
class="c-telemetry-table__scroll-forcer"
:style="{ width: totalWidth + 'px' }"
></div>
<table
class="c-table__body c-telemetry-table__body js-telemetry-table__content"
:style="{ height: totalHeight + 'px'}"
>
<tbody> <tbody>
<telemetry-table-row v-for="(row, rowIndex) in visibleRows" <telemetry-table-row
v-for="(row, rowIndex) in visibleRows"
:key="rowIndex"
:headers="headers" :headers="headers"
:columnWidths="columnWidths" :column-widths="columnWidths"
:rowIndex="rowIndex" :row-index="rowIndex"
:objectPath="objectPath" :object-path="objectPath"
:rowOffset="rowOffset" :row-offset="rowOffset"
:rowHeight="rowHeight" :row-height="rowHeight"
:row="row" :row="row"
:marked="row.marked" :marked="row.marked"
@mark="markRow" @mark="markRow"
@unmark="unmarkRow" @unmark="unmarkRow"
@markMultipleConcurrent="markMultipleConcurrentRows"> @markMultipleConcurrent="markMultipleConcurrentRows"
</telemetry-table-row> />
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Sizing table --> <!-- Sizing table -->
<table class="c-telemetry-table__sizing js-telemetry-table__sizing" :style="sizingTableWidth"> <table
class="c-telemetry-table__sizing js-telemetry-table__sizing"
:style="sizingTableWidth"
>
<tr> <tr>
<template v-for="(title, key) in headers"> <template v-for="(title, key) in headers">
<th :key="key" :style="{ width: configuredColumnWidths[key] + 'px', 'max-width': configuredColumnWidths[key] + 'px'}">{{title}}</th> <th
:key="key"
:style="{ width: configuredColumnWidths[key] + 'px', 'max-width': configuredColumnWidths[key] + 'px'}"
>
{{ title }}
</th>
</template> </template>
</tr> </tr>
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows" <telemetry-table-row
v-for="(sizingRowData, objectKeyString) in sizingRows"
:key="objectKeyString" :key="objectKeyString"
:headers="headers" :headers="headers"
:columnWidths="configuredColumnWidths" :column-widths="configuredColumnWidths"
:row="sizingRowData"> :row="sizingRowData"
</telemetry-table-row> :object-path="objectPath"
/>
</table> </table>
<telemetry-filter-indicator></telemetry-filter-indicator> <telemetry-filter-indicator />
</div> </div>
</div><!-- closes c-table-wrapper --> </div><!-- closes c-table-wrapper -->
</template> </template>
@ -343,9 +390,6 @@ const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17; const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200; const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 100; const AUTO_SCROLL_TRIGGER_HEIGHT = 100;
const RESIZE_HOT_ZONE = 10;
const MOVE_TRIGGER_WAIT = 500;
const VERTICAL_SCROLL_WIDTH = 30;
export default { export default {
components: { components: {
@ -440,6 +484,59 @@ export default {
return style; return style;
} }
}, },
created() {
this.filterChanged = _.debounce(this.filterChanged, 500);
},
mounted() {
this.csvExporter = new CSVExporter();
this.rowsAdded = _.throttle(this.rowsAdded, 200);
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
this.scroll = _.throttle(this.scroll, 100);
this.table.on('object-added', this.addObject);
this.table.on('object-removed', this.removeObject);
this.table.on('outstanding-requests', this.outstandingRequests);
this.table.on('refresh', this.clearRowsAndRerender);
this.table.filteredRows.on('add', this.rowsAdded);
this.table.filteredRows.on('remove', this.rowsRemoved);
this.table.filteredRows.on('sort', this.updateVisibleRows);
this.table.filteredRows.on('filter', this.updateVisibleRows);
//Default sort
this.sortOptions = this.table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
this.table.configuration.on('change', this.updateConfiguration);
this.calculateTableSize();
this.pollForResize();
this.calculateScrollbarWidth();
this.table.initialize();
},
destroyed() {
this.table.off('object-added', this.addObject);
this.table.off('object-removed', this.removeObject);
this.table.off('outstanding-requests', this.outstandingRequests);
this.table.off('refresh', this.clearRowsAndRerender);
this.table.filteredRows.off('add', this.rowsAdded);
this.table.filteredRows.off('remove', this.rowsRemoved);
this.table.filteredRows.off('sort', this.updateVisibleRows);
this.table.filteredRows.off('filter', this.updateVisibleRows);
this.table.configuration.off('change', this.updateConfiguration);
clearInterval(this.resizePollHandle);
this.table.configuration.destroy();
this.table.destroy();
},
methods: { methods: {
updateVisibleRows() { updateVisibleRows() {
if (!this.updatingView) { if (!this.updatingView) {
@ -527,7 +624,7 @@ export default {
} }
this.table.sortBy(this.sortOptions); this.table.sortBy(this.sortOptions);
}, },
scroll () { scroll() {
this.updateVisibleRows(); this.updateVisibleRows();
this.synchronizeScrollX(); this.synchronizeScrollX();
@ -557,7 +654,7 @@ export default {
this.table.filteredRows.setColumnFilter(columnKey, ''); this.table.filteredRows.setColumnFilter(columnKey, '');
this.setHeight(); this.setHeight();
}, },
rowsAdded (rows) { rowsAdded(rows) {
this.setHeight(); this.setHeight();
let sizingRow; let sizingRow;
@ -578,7 +675,7 @@ export default {
this.updateVisibleRows(); this.updateVisibleRows();
}, },
rowsRemoved (rows) { rowsRemoved(rows) {
this.setHeight(); this.setHeight();
this.updateVisibleRows(); this.updateVisibleRows();
}, },
@ -811,66 +908,12 @@ export default {
let row = allRows[i]; let row = allRows[i];
row.marked = true; row.marked = true;
if (row !== baseRow){ if (row !== baseRow) {
this.markedRows.push(row); this.markedRows.push(row);
} }
} }
} }
} }
},
created() {
this.filterChanged = _.debounce(this.filterChanged, 500);
},
mounted() {
console.log("Table mounted");
this.csvExporter = new CSVExporter();
this.rowsAdded = _.throttle(this.rowsAdded, 200);
this.rowsRemoved = _.throttle(this.rowsRemoved, 200);
this.scroll = _.throttle(this.scroll, 100);
this.table.on('object-added', this.addObject);
this.table.on('object-removed', this.removeObject);
this.table.on('outstanding-requests', this.outstandingRequests);
this.table.on('refresh', this.clearRowsAndRerender);
this.table.filteredRows.on('add', this.rowsAdded);
this.table.filteredRows.on('remove', this.rowsRemoved);
this.table.filteredRows.on('sort', this.updateVisibleRows);
this.table.filteredRows.on('filter', this.updateVisibleRows);
//Default sort
this.sortOptions = this.table.filteredRows.sortBy();
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
this.contentTable = this.$el.querySelector('.js-telemetry-table__content');
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
this.table.configuration.on('change', this.updateConfiguration);
this.calculateTableSize();
this.pollForResize();
this.calculateScrollbarWidth();
this.table.initialize();
},
destroyed() {
this.table.off('object-added', this.addObject);
this.table.off('object-removed', this.removeObject);
this.table.off('outstanding-requests', this.outstandingRequests);
this.table.off('refresh', this.clearRowsAndRerender);
this.table.filteredRows.off('add', this.rowsAdded);
this.table.filteredRows.off('remove', this.rowsRemoved);
this.table.filteredRows.off('sort', this.updateVisibleRows);
this.table.filteredRows.off('filter', this.updateVisibleRows);
this.table.configuration.off('change', this.updateConfiguration);
clearInterval(this.resizePollHandle);
this.table.configuration.destroy();
this.table.destroy();
} }
} }
</script> </script>

View File

@ -20,87 +20,123 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-conductor" <div
:class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode']"> class="c-conductor"
<form class="u-contents" ref="conductorForm" @submit.prevent="updateTimeFromConductor"> :class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode']"
<div class="c-conductor__time-bounds"> >
<button class="c-input--submit" type="submit" ref="submitButton"></button> <form
<ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon> ref="conductorForm"
class="u-contents"
@submit.prevent="updateTimeFromConductor"
>
<div class="c-conductor__time-bounds">
<button
ref="submitButton"
class="c-input--submit"
type="submit"
></button>
<ConductorModeIcon class="c-conductor__mode-icon" />
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed" <div
v-if="isFixed"> v-if="isFixed"
<!-- Fixed start --> class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed"
<div class="c-conductor__start-fixed__label">Start</div> >
<input class="c-input--datetime" <!-- Fixed start -->
type="text" autocorrect="off" spellcheck="false" <div class="c-conductor__start-fixed__label">
ref="startDate" Start
v-model="formattedBounds.start"
@change="validateAllBounds(); submitForm()" />
<date-picker
v-if="isFixed && isUTCBased"
:default-date-time="formattedBounds.start"
:formatter="timeFormatter"
@date-selected="startDateSelected"></date-picker>
</div> </div>
<input
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta" ref="startDate"
v-if="!isFixed"> v-model="formattedBounds.start"
<!-- RT start --> class="c-input--datetime"
<div class="c-direction-indicator icon-minus"></div> type="text"
<input class="c-input--hrs-min-sec" autocorrect="off"
type="text" autocorrect="off" spellcheck="false"
ref="startOffset" @change="validateAllBounds(); submitForm()"
spellcheck="false" >
v-model="offsets.start" <date-picker
@change="validateAllOffsets(); submitForm()"> v-if="isFixed && isUTCBased"
</div> :default-date-time="formattedBounds.start"
:formatter="timeFormatter"
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed"> @date-selected="startDateSelected"
<!-- Fixed end and RT 'last update' display --> />
<div class="c-conductor__end-fixed__label">
{{ isFixed ? 'End' : 'Updated' }}
</div>
<input class="c-input--datetime"
type="text" autocorrect="off" spellcheck="false"
v-model="formattedBounds.end"
:disabled="!isFixed"
ref="endDate"
@change="validateAllBounds(); submitForm()">
<date-picker
v-if="isFixed && isUTCBased"
class="c-ctrl-wrapper--menus-left"
:default-date-time="formattedBounds.end"
:formatter="timeFormatter"
@date-selected="endDateSelected"></date-picker>
</div>
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
v-if="!isFixed">
<!-- RT end -->
<div class="c-direction-indicator icon-plus"></div>
<input class="c-input--hrs-min-sec"
type="text"
autocorrect="off"
spellcheck="false"
ref="endOffset"
v-model="offsets.end"
@change="validateAllOffsets(); submitForm()">
</div>
<conductor-axis
class="c-conductor__ticks"
:bounds="rawBounds"
@panAxis="setViewFromBounds"></conductor-axis>
</div> </div>
<div class="c-conductor__controls">
<!-- Mode, time system menu buttons and duration slider --> <div
<ConductorMode class="c-conductor__mode-select"></ConductorMode> v-if="!isFixed"
<ConductorTimeSystem class="c-conductor__time-system-select"></ConductorTimeSystem> class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta"
>
<!-- RT start -->
<div class="c-direction-indicator icon-minus"></div>
<input
ref="startOffset"
v-model="offsets.start"
class="c-input--hrs-min-sec"
type="text"
autocorrect="off"
spellcheck="false"
@change="validateAllOffsets(); submitForm()"
>
</div> </div>
<input type="submit" class="invisible">
</form> <div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
</div> <!-- Fixed end and RT 'last update' display -->
<div class="c-conductor__end-fixed__label">
{{ isFixed ? 'End' : 'Updated' }}
</div>
<input
ref="endDate"
v-model="formattedBounds.end"
class="c-input--datetime"
type="text"
autocorrect="off"
spellcheck="false"
:disabled="!isFixed"
@change="validateAllBounds(); submitForm()"
>
<date-picker
v-if="isFixed && isUTCBased"
class="c-ctrl-wrapper--menus-left"
:default-date-time="formattedBounds.end"
:formatter="timeFormatter"
@date-selected="endDateSelected"
/>
</div>
<div
v-if="!isFixed"
class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
>
<!-- RT end -->
<div class="c-direction-indicator icon-plus"></div>
<input
ref="endOffset"
v-model="offsets.end"
class="c-input--hrs-min-sec"
type="text"
autocorrect="off"
spellcheck="false"
@change="validateAllOffsets(); submitForm()"
>
</div>
<conductor-axis
class="c-conductor__ticks"
:bounds="rawBounds"
@panAxis="setViewFromBounds"
/>
</div>
<div class="c-conductor__controls">
<!-- Mode, time system menu buttons and duration slider -->
<ConductorMode class="c-conductor__mode-select" />
<ConductorTimeSystem class="c-conductor__time-system-select" />
</div>
<input
type="submit"
class="invisible"
>
</form>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -288,7 +324,6 @@
</style> </style>
<script> <script>
import moment from 'moment';
import ConductorMode from './ConductorMode.vue'; import ConductorMode from './ConductorMode.vue';
import ConductorTimeSystem from './ConductorTimeSystem.vue'; import ConductorTimeSystem from './ConductorTimeSystem.vue';
import DatePicker from './DatePicker.vue'; import DatePicker from './DatePicker.vue';
@ -296,11 +331,6 @@ import ConductorAxis from './ConductorAxis.vue';
import ConductorModeIcon from './ConductorModeIcon.vue'; import ConductorModeIcon from './ConductorModeIcon.vue';
const DEFAULT_DURATION_FORMATTER = 'duration'; const DEFAULT_DURATION_FORMATTER = 'duration';
const SECONDS = 1000;
const DAYS = 24 * 60 * 60 * SECONDS;
const YEARS = 365 * DAYS;
const RESIZE_POLL_INTERVAL = 200;
export default { export default {
inject: ['openmct', 'configuration'], inject: ['openmct', 'configuration'],
@ -323,7 +353,7 @@ export default {
durationFormatter: durationFormatter, durationFormatter: durationFormatter,
offsets: { offsets: {
start: offsets && durationFormatter.format(Math.abs(offsets.start)), start: offsets && durationFormatter.format(Math.abs(offsets.start)),
end: offsets && durationFormatter.format(Math.abs(offsets.end)), end: offsets && durationFormatter.format(Math.abs(offsets.end))
}, },
formattedBounds: { formattedBounds: {
start: timeFormatter.format(bounds.start), start: timeFormatter.format(bounds.start),
@ -338,6 +368,14 @@ export default {
showDatePicker: false showDatePicker: false
} }
}, },
mounted() {
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
this.openmct.time.on('bounds', this.setViewFromBounds);
this.openmct.time.on('timeSystem', this.setTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
},
methods: { methods: {
setTimeSystem(timeSystem) { setTimeSystem(timeSystem) {
this.timeFormatter = this.getFormatter(timeSystem.timeFormat); this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
@ -347,7 +385,7 @@ export default {
this.isUTCBased = timeSystem.isUTCBased; this.isUTCBased = timeSystem.isUTCBased;
}, },
setOffsetsFromView($event) { setOffsetsFromView($event) {
if (this.$refs.conductorForm.checkValidity()){ if (this.$refs.conductorForm.checkValidity()) {
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start); let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
let endOffset = this.durationFormatter.parse(this.offsets.end); let endOffset = this.durationFormatter.parse(this.offsets.end);
@ -362,7 +400,7 @@ export default {
} }
}, },
setBoundsFromView($event) { setBoundsFromView($event) {
if (this.$refs.conductorForm.checkValidity()){ if (this.$refs.conductorForm.checkValidity()) {
let start = this.timeFormatter.parse(this.formattedBounds.start); let start = this.timeFormatter.parse(this.formattedBounds.start);
let end = this.timeFormatter.parse(this.formattedBounds.end); let end = this.timeFormatter.parse(this.formattedBounds.end);
@ -404,7 +442,7 @@ export default {
[this.$refs.startOffset, this.$refs.endOffset].forEach(this.clearValidationForInput); [this.$refs.startOffset, this.$refs.endOffset].forEach(this.clearValidationForInput);
} }
}, },
clearValidationForInput(input){ clearValidationForInput(input) {
input.setCustomValidity(''); input.setCustomValidity('');
input.title = ''; input.title = '';
}, },
@ -419,7 +457,7 @@ export default {
formattedDate = this.formattedBounds.end; formattedDate = this.formattedBounds.end;
} }
if (!this.timeFormatter.validate(formattedDate)){ if (!this.timeFormatter.validate(formattedDate)) {
validationResult = 'Invalid date'; validationResult = 'Invalid date';
} else { } else {
let boundsValues = { let boundsValues = {
@ -429,7 +467,7 @@ export default {
validationResult = this.openmct.time.validateBounds(boundsValues); validationResult = this.openmct.time.validateBounds(boundsValues);
} }
if (validationResult !== true){ if (validationResult !== true) {
input.setCustomValidity(validationResult); input.setCustomValidity(validationResult);
input.title = validationResult; input.title = validationResult;
return false; return false;
@ -461,7 +499,7 @@ export default {
validationResult = this.openmct.time.validateOffsets(offsetValues); validationResult = this.openmct.time.validateOffsets(offsetValues);
} }
if (validationResult !== true){ if (validationResult !== true) {
input.setCustomValidity(validationResult); input.setCustomValidity(validationResult);
input.title = validationResult; input.title = validationResult;
return false; return false;
@ -482,24 +520,16 @@ export default {
format: key format: key
}).formatter; }).formatter;
}, },
startDateSelected(date){ startDateSelected(date) {
this.formattedBounds.start = this.timeFormatter.format(date); this.formattedBounds.start = this.timeFormatter.format(date);
this.validateAllBounds(); this.validateAllBounds();
this.submitForm(); this.submitForm();
}, },
endDateSelected(date){ endDateSelected(date) {
this.formattedBounds.end = this.timeFormatter.format(date); this.formattedBounds.end = this.timeFormatter.format(date);
this.validateAllBounds(); this.validateAllBounds();
this.submitForm(); this.submitForm();
}, }
},
mounted() {
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
this.openmct.time.on('bounds', this.setViewFromBounds);
this.openmct.time.on('timeSystem', this.setTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
} }
} }
</script> </script>

View File

@ -20,10 +20,11 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-conductor-axis" <div
ref="axisHolder" ref="axisHolder"
@mousedown="dragStart($event)"> class="c-conductor-axis"
</div> @mousedown="dragStart($event)"
></div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -126,106 +127,9 @@ const PIXELS_PER_TICK_WIDE = 200;
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
bounds: Object bounds: {
}, type: Object,
methods: { required: true
setScale() {
let timeSystem = this.openmct.time.timeSystem();
let bounds = this.bounds;
if (timeSystem.isUTCBased) {
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
} else {
this.xScale.domain([bounds.start, bounds.end]);
}
this.xAxis.scale(this.xScale);
this.xScale.range([PADDING, this.width - PADDING * 2]);
this.axisElement.call(this.xAxis);
if (this.width > 1800) {
this.xAxis.ticks(this.width / PIXELS_PER_TICK_WIDE);
} else {
this.xAxis.ticks(this.width / PIXELS_PER_TICK);
}
this.msPerPixel = (bounds.end - bounds.start) / this.width;
},
setViewFromTimeSystem(timeSystem) {
let format = this.getActiveFormatter();
let bounds = this.openmct.time.bounds();
//The D3 scale used depends on the type of time system as d3
// supports UTC out of the box.
if (timeSystem.isUTCBased) {
this.xScale = d3Scale.scaleUtc();
} else {
this.xScale = d3Scale.scaleLinear();
}
this.xAxis.scale(this.xScale);
this.xAxis.tickFormat(utcMultiTimeFormat);
this.axisElement.call(this.xAxis);
this.setScale();
},
getActiveFormatter() {
let timeSystem = this.openmct.time.timeSystem();
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed) {
return this.getFormatter(timeSystem.timeFormat);
} else {
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
}
},
getFormatter(key) {
return this.openmct.telemetry.getValueFormatter({
format: key
}).formatter;
},
dragStart($event){
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed){
this.dragStartX = $event.clientX;
document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.dragEnd, {
once: true
});
}
},
drag($event) {
if (!this.dragging){
this.dragging = true;
requestAnimationFrame(()=>{
let deltaX = $event.clientX - this.dragStartX;
let percX = deltaX / this.width;
let bounds = this.openmct.time.bounds();
let deltaTime = bounds.end - bounds.start;
let newStart = bounds.start - percX * deltaTime;
this.$emit('panAxis',{
start: newStart,
end: newStart + deltaTime
});
this.dragging = false;
})
} else {
console.log('Rejected drag due to RAF cap');
}
},
dragEnd() {
document.removeEventListener('mousemove', this.drag);
this.openmct.time.bounds({
start: this.bounds.start,
end: this.bounds.end
});
},
resize() {
if (this.$refs.axisHolder.clientWidth !== this.width) {
this.width = this.$refs.axisHolder.clientWidth;
this.setScale();
}
} }
}, },
watch: { watch: {
@ -259,6 +163,103 @@ export default {
setInterval(this.resize, RESIZE_POLL_INTERVAL); setInterval(this.resize, RESIZE_POLL_INTERVAL);
}, },
destroyed() { destroyed() {
},
methods: {
setScale() {
let timeSystem = this.openmct.time.timeSystem();
let bounds = this.bounds;
if (timeSystem.isUTCBased) {
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
} else {
this.xScale.domain([bounds.start, bounds.end]);
}
this.xAxis.scale(this.xScale);
this.xScale.range([PADDING, this.width - PADDING * 2]);
this.axisElement.call(this.xAxis);
if (this.width > 1800) {
this.xAxis.ticks(this.width / PIXELS_PER_TICK_WIDE);
} else {
this.xAxis.ticks(this.width / PIXELS_PER_TICK);
}
this.msPerPixel = (bounds.end - bounds.start) / this.width;
},
setViewFromTimeSystem(timeSystem) {
//The D3 scale used depends on the type of time system as d3
// supports UTC out of the box.
if (timeSystem.isUTCBased) {
this.xScale = d3Scale.scaleUtc();
} else {
this.xScale = d3Scale.scaleLinear();
}
this.xAxis.scale(this.xScale);
this.xAxis.tickFormat(utcMultiTimeFormat);
this.axisElement.call(this.xAxis);
this.setScale();
},
getActiveFormatter() {
let timeSystem = this.openmct.time.timeSystem();
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed) {
return this.getFormatter(timeSystem.timeFormat);
} else {
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
}
},
getFormatter(key) {
return this.openmct.telemetry.getValueFormatter({
format: key
}).formatter;
},
dragStart($event) {
let isFixed = this.openmct.time.clock() === undefined;
if (isFixed) {
this.dragStartX = $event.clientX;
document.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.dragEnd, {
once: true
});
}
},
drag($event) {
if (!this.dragging) {
this.dragging = true;
requestAnimationFrame(()=>{
let deltaX = $event.clientX - this.dragStartX;
let percX = deltaX / this.width;
let bounds = this.openmct.time.bounds();
let deltaTime = bounds.end - bounds.start;
let newStart = bounds.start - percX * deltaTime;
this.$emit('panAxis',{
start: newStart,
end: newStart + deltaTime
});
this.dragging = false;
})
} else {
console.log('Rejected drag due to RAF cap');
}
},
dragEnd() {
document.removeEventListener('mousemove', this.drag);
this.openmct.time.bounds({
start: this.bounds.start,
end: this.bounds.end
});
},
resize() {
if (this.$refs.axisHolder.clientWidth !== this.width) {
this.width = this.$refs.axisHolder.clientWidth;
this.setScale();
}
}
} }
} }

View File

@ -20,33 +20,43 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"> <div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
<button class="c-button--menu c-mode-button" <button
@click.prevent="toggle"> class="c-button--menu c-mode-button"
<span class="c-button__label">{{selectedMode.name}}</span> @click.prevent="toggle"
</button> >
<div class="c-menu c-super-menu c-conductor__mode-menu" <span class="c-button__label">{{ selectedMode.name }}</span>
v-if="open"> </button>
<div class="c-super-menu__menu"> <div
<ul> v-if="open"
<li v-for="mode in modes" class="c-menu c-super-menu c-conductor__mode-menu"
:key="mode.key" >
@click="setOption(mode)" <div class="c-super-menu__menu">
@mouseover="hoveredMode = mode" <ul>
@mouseleave="hoveredMode = {}" <li
class="menu-item-a" v-for="mode in modes"
:class="mode.cssClass"> :key="mode.key"
{{mode.name}} class="menu-item-a"
</li> :class="mode.cssClass"
</ul> @click="setOption(mode)"
@mouseover="hoveredMode = mode"
@mouseleave="hoveredMode = {}"
>
{{ mode.name }}
</li>
</ul>
</div>
<div class="c-super-menu__item-description">
<div :class="['l-item-description__icon', 'bg-' + hoveredMode.cssClass]"></div>
<div class="l-item-description__name">
{{ hoveredMode.name }}
</div> </div>
<div class="c-super-menu__item-description"> <div class="l-item-description__description">
<div :class="['l-item-description__icon', 'bg-' + hoveredMode.cssClass]"></div> {{ hoveredMode.description }}
<div class="l-item-description__name">{{hoveredMode.name}}</div>
<div class="l-item-description__description">{{hoveredMode.description}}</div>
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -87,6 +97,14 @@ export default {
hoveredMode: {} hoveredMode: {}
}; };
}, },
mounted: function () {
this.loadClocksFromConfiguration();
this.openmct.time.on('clock', this.setViewFromClock);
},
destroyed: function () {
this.openmct.time.off('clock', this.setViewFromClock);
},
methods: { methods: {
loadClocksFromConfiguration() { loadClocksFromConfiguration() {
let clocks = this.configuration.menuOptions let clocks = this.configuration.menuOptions
@ -180,14 +198,6 @@ export default {
setViewFromClock(clock) { setViewFromClock(clock) {
this.selectedMode = this.getModeOptionForClock(clock); this.selectedMode = this.getModeOptionForClock(clock);
} }
},
mounted: function () {
this.loadClocksFromConfiguration();
this.openmct.time.on('clock', this.setViewFromClock);
},
destroyed: function () {
this.openmct.time.off('clock', this.setViewFromClock);
} }
} }

View File

@ -20,10 +20,10 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-clock-symbol"> <div class="c-clock-symbol">
<div class="hand-little"></div> <div class="hand-little"></div>
<div class="hand-big"></div> <div class="hand-big"></div>
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">

View File

@ -20,24 +20,33 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up" <div
v-if="selectedTimeSystem.name"> v-if="selectedTimeSystem.name"
<button class="c-button--menu c-time-system-button" class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
:class="selectedTimeSystem.cssClass" >
@click.prevent="toggle"> <button
<span class="c-button__label">{{selectedTimeSystem.name}}</span> class="c-button--menu c-time-system-button"
</button> :class="selectedTimeSystem.cssClass"
<div class="c-menu" v-if="open"> @click.prevent="toggle"
<ul> >
<li @click="setTimeSystemFromView(timeSystem)" <span class="c-button__label">{{ selectedTimeSystem.name }}</span>
v-for="timeSystem in timeSystems" </button>
:key="timeSystem.key" <div
:class="timeSystem.cssClass"> v-if="open"
{{timeSystem.name}} class="c-menu"
</li> >
</ul> <ul>
</div> <li
v-for="timeSystem in timeSystems"
:key="timeSystem.key"
:class="timeSystem.cssClass"
@click="setTimeSystemFromView(timeSystem)"
>
{{ timeSystem.name }}
</li>
</ul>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -54,6 +63,14 @@ export default {
timeSystems: this.getValidTimesystemsForClock(activeClock) timeSystems: this.getValidTimesystemsForClock(activeClock)
}; };
}, },
mounted: function () {
this.openmct.time.on('timeSystem', this.setViewFromTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
},
destroyed: function () {
this.openmct.time.off('timeSystem', this.setViewFromTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
},
methods: { methods: {
getValidTimesystemsForClock(clock) { getValidTimesystemsForClock(clock) {
return this.configuration.menuOptions return this.configuration.menuOptions
@ -111,14 +128,6 @@ export default {
let activeClock = this.openmct.time.clock(); let activeClock = this.openmct.time.clock();
this.timeSystems = this.getValidTimesystemsForClock(activeClock); this.timeSystems = this.getValidTimesystemsForClock(activeClock);
} }
},
mounted: function () {
this.openmct.time.on('timeSystem', this.setViewFromTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
},
destroyed: function () {
this.openmct.time.off('timeSystem', this.setViewFromTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock);
} }
} }

View File

@ -20,41 +20,68 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up c-datetime-picker__wrapper" ref="calendarHolder"> <div
<a class="c-icon-button icon-calendar" ref="calendarHolder"
@click="toggle"></a> class="c-ctrl-wrapper c-ctrl-wrapper--menus-up c-datetime-picker__wrapper"
<div class="c-menu c-menu--mobile-modal c-datetime-picker" >
v-if="open"> <a
<div class="c-datetime-picker__close-button"> class="c-icon-button icon-calendar"
<button class="c-click-icon icon-x-in-circle" @click="toggle"
@click="toggle"></button> ></a>
</div> <div
<div class="c-datetime-picker__pager c-pager l-month-year-pager"> v-if="open"
<div class="c-pager__prev c-icon-button icon-arrow-left" class="c-menu c-menu--mobile-modal c-datetime-picker"
@click.stop="changeMonth(-1)"></div> >
<div class="c-pager__month-year">{{model.month}} {{model.year}}</div> <div class="c-datetime-picker__close-button">
<div class="c-pager__next c-icon-button icon-arrow-right" <button
@click.stop="changeMonth(1)"></div> class="c-click-icon icon-x-in-circle"
</div> @click="toggle"
<div class="c-datetime-picker__calendar c-calendar"> ></button>
<ul class="c-calendar__row--header l-cal-row"> </div>
<li v-for="day in ['Su','Mo','Tu','We','Th','Fr','Sa']" <div class="c-datetime-picker__pager c-pager l-month-year-pager">
:key="day">{{day}}</li> <div
</ul> class="c-pager__prev c-icon-button icon-arrow-left"
<ul class="c-calendar__row--body" @click.stop="changeMonth(-1)"
v-for="(row, index) in table" ></div>
:key="index"> <div class="c-pager__month-year">
<li v-for="(cell, index) in row" {{ model.month }} {{ model.year }}
:key="index"
@click="select(cell)"
:class="{ 'is-in-month': isInCurrentMonth(cell), selected: isSelected(cell) }">
<div class="c-calendar__day--prime">{{cell.day}}</div>
<div class="c-calendar__day--sub">{{cell.dayOfYear}}</div>
</li>
</ul>
</div> </div>
<div
class="c-pager__next c-icon-button icon-arrow-right"
@click.stop="changeMonth(1)"
></div>
</div>
<div class="c-datetime-picker__calendar c-calendar">
<ul class="c-calendar__row--header l-cal-row">
<li
v-for="day in ['Su','Mo','Tu','We','Th','Fr','Sa']"
:key="day"
>
{{ day }}
</li>
</ul>
<ul
v-for="(row, tableIndex) in table"
:key="tableIndex"
class="c-calendar__row--body"
>
<li
v-for="(cell, rowIndex) in row"
:key="rowIndex"
:class="{ 'is-in-month': isInCurrentMonth(cell), selected: isSelected(cell) }"
@click="select(cell)"
>
<div class="c-calendar__day--prime">
{{ cell.day }}
</div>
<div class="c-calendar__day--sub">
{{ cell.dayOfYear }}
</div>
</li>
</ul>
</div> </div>
</div> </div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -163,10 +190,10 @@ import moment from 'moment';
import toggleMixin from '../../ui/mixins/toggle-mixin'; import toggleMixin from '../../ui/mixins/toggle-mixin';
const TIME_NAMES = { const TIME_NAMES = {
'hours': "Hour", 'hours': "Hour",
'minutes': "Minute", 'minutes': "Minute",
'seconds': "Second" 'seconds': "Second"
}; };
const MONTHS = moment.months(); const MONTHS = moment.months();
const TIME_OPTIONS = (function makeRanges() { const TIME_OPTIONS = (function makeRanges() {
let arr = []; let arr = [];
@ -184,8 +211,14 @@ export default {
inject: ['openmct'], inject: ['openmct'],
mixins: [toggleMixin], mixins: [toggleMixin],
props: { props: {
defaultDateTime: String, defaultDateTime: {
formatter: Object type: String,
default: undefined
},
formatter: {
type: Object,
required: true
}
}, },
data: function () { data: function () {
return { return {
@ -196,13 +229,17 @@ export default {
}, },
model: { model: {
year: undefined, year: undefined,
month: undefined, month: undefined
}, },
table: undefined, table: undefined,
date: undefined, date: undefined,
time: undefined time: undefined
} }
}, },
mounted: function () {
this.updateFromModel(this.defaultDateTime);
this.updateViewForMonth();
},
methods: { methods: {
generateTable() { generateTable() {
let m = moment.utc({ year: this.picker.year, month: this.picker.month }).day(0), let m = moment.utc({ year: this.picker.year, month: this.picker.month }).day(0),
@ -233,9 +270,7 @@ export default {
}, },
updateFromModel(defaultDateTime) { updateFromModel(defaultDateTime) {
let m; let m = moment.utc(defaultDateTime);
m = moment.utc(defaultDateTime);
this.date = { this.date = {
year: m.year(), year: m.year(),
@ -314,11 +349,7 @@ export default {
optionsFor(key) { optionsFor(key) {
return TIME_OPTIONS[key]; return TIME_OPTIONS[key];
}, }
},
mounted: function () {
this.updateFromModel(this.defaultDateTime);
this.updateViewForMonth();
} }
} }
</script> </script>

View File

@ -37,6 +37,7 @@ export default function WebPage(openmct) {
return { return {
show: function (element) { show: function (element) {
component = new Vue({ component = new Vue({
el: element,
components: { components: {
WebPageComponent: WebPageComponent WebPageComponent: WebPageComponent
}, },
@ -44,7 +45,6 @@ export default function WebPage(openmct) {
openmct, openmct,
domainObject domainObject
}, },
el: element,
template: '<web-page-component></web-page-component>' template: '<web-page-component></web-page-component>'
}); });
}, },

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="l-iframe abs"> <div class="l-iframe abs">
<iframe :src="currentDomainObject.url"></iframe> <iframe :src="currentDomainObject.url"></iframe>
</div> </div>
</template> </template>
<script> <script>

View File

@ -20,34 +20,40 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="c-so-view has-local-controls" <div
:class="{ class="c-so-view has-local-controls"
'c-so-view--no-frame': !hasFrame, :class="{
'has-complex-content': complexContent 'c-so-view--no-frame': !hasFrame,
}"> 'has-complex-content': complexContent
<div class="c-so-view__header"> }"
<div class="c-so-view__header__icon" :class="cssClass"></div> >
<div class="c-so-view__header__name"> <div class="c-so-view__header">
{{ domainObject && domainObject.name }} <div
</div> class="c-so-view__header__icon"
<context-menu-drop-down :class="cssClass"
:object-path="objectPath"> ></div>
</context-menu-drop-down> <div class="c-so-view__header__name">
{{ domainObject && domainObject.name }}
</div> </div>
<div class="c-so-view__local-controls c-so-view__view-large h-local-controls c-local-controls--show-on-hover"> <context-menu-drop-down
<button class="c-button icon-expand" :object-path="objectPath"
title="View Large" />
@click="expand">
</button>
</div>
<object-view
class="c-so-view__object-view"
ref="objectView"
:object="domainObject"
:show-edit-view="showEditView"
:object-path="objectPath">
</object-view>
</div> </div>
<div class="c-so-view__local-controls c-so-view__view-large h-local-controls c-local-controls--show-on-hover">
<button
class="c-button icon-expand"
title="View Large"
@click="expand"
></button>
</div>
<object-view
ref="objectView"
class="c-so-view__object-view"
:object="domainObject"
:show-edit-view="showEditView"
:object-path="objectPath"
/>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -131,58 +137,64 @@
</style> </style>
<script> <script>
import ObjectView from './ObjectView.vue' import ObjectView from './ObjectView.vue'
import ContextMenuDropDown from './contextMenuDropDown.vue'; import ContextMenuDropDown from './contextMenuDropDown.vue';
const SIMPLE_CONTENT_TYPES = [ const SIMPLE_CONTENT_TYPES = [
'clock', 'clock',
'timer', 'timer',
'summary-widget', 'summary-widget',
'hyperlink' 'hyperlink'
]; ];
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { components: {
domainObject: Object, ObjectView,
objectPath: Array, ContextMenuDropDown
hasFrame: Boolean, },
showEditView: { props: {
type: Boolean, domainObject: {
default: () => true type: Object,
} required: true
}, },
components: { objectPath: {
ObjectView, type: Array,
ContextMenuDropDown, required: true
}, },
methods: { hasFrame: Boolean,
expand() { showEditView: {
let objectView = this.$refs.objectView, type: Boolean,
parentElement = objectView.$el, default: true
childElement = parentElement.children[0]; }
},
data() {
let objectType = this.openmct.types.get(this.domainObject.type),
cssClass = objectType && objectType.definition ? objectType.definition.cssClass : 'icon-object-unknown',
complexContent = !SIMPLE_CONTENT_TYPES.includes(this.domainObject.type);
this.openmct.overlays.overlay({ return {
element: childElement, cssClass,
size: 'large', complexContent
onDestroy() { }
parentElement.append(childElement); },
} methods: {
}); expand() {
}, let objectView = this.$refs.objectView,
getSelectionContext() { parentElement = objectView.$el,
return this.$refs.objectView.getSelectionContext(); childElement = parentElement.children[0];
}
this.openmct.overlays.overlay({
element: childElement,
size: 'large',
onDestroy() {
parentElement.append(childElement);
}
});
}, },
data() { getSelectionContext() {
let objectType = this.openmct.types.get(this.domainObject.type), return this.$refs.objectView.getSelectionContext();
cssClass = objectType && objectType.definition ? objectType.definition.cssClass : 'icon-object-unknown',
complexContent = !SIMPLE_CONTENT_TYPES.includes(this.domainObject.type);
return {
cssClass,
complexContent
}
} }
} }
}
</script> </script>

View File

@ -1,11 +1,15 @@
<template> <template>
<a class="c-tree__item__label c-object-label" <a
class="c-tree__item__label c-object-label"
draggable="true" draggable="true"
:href="objectLink"
@dragstart="dragStart" @dragstart="dragStart"
@click="navigateOrPreview" @click="navigateOrPreview"
:href="objectLink"> >
<div class="c-tree__item__type-icon c-object-label__type-icon" <div
:class="typeClass"></div> class="c-tree__item__type-icon c-object-label__type-icon"
:class="typeClass"
></div>
<div class="c-tree__item__name c-object-label__name">{{ observedObject.name }}</div> <div class="c-tree__item__name c-object-label__name">{{ observedObject.name }}</div>
</a> </a>
</template> </template>
@ -53,29 +57,24 @@ export default {
mixins: [ObjectLink, ContextMenuGesture], mixins: [ObjectLink, ContextMenuGesture],
inject: ['openmct'], inject: ['openmct'],
props: { props: {
domainObject: Object, domainObject: {
type: Object,
required: true
},
objectPath: { objectPath: {
type: Array, type: Array,
default() { required: true
return [];
}
}, },
navigateToPath: String navigateToPath: {
type: String,
default: undefined
}
}, },
data() { data() {
return { return {
observedObject: this.domainObject observedObject: this.domainObject
}; };
}, },
mounted() {
if (this.observedObject) {
let removeListener = this.openmct.objects.observe(this.observedObject, '*', (newObject) => {
this.observedObject = newObject;
});
this.$once('hook:destroyed', removeListener);
}
this.previewAction = new PreviewAction(this.openmct);
},
computed: { computed: {
typeClass() { typeClass() {
let type = this.openmct.types.get(this.observedObject.type); let type = this.openmct.types.get(this.observedObject.type);
@ -85,15 +84,24 @@ export default {
return type.definition.cssClass; return type.definition.cssClass;
} }
}, },
mounted() {
if (this.observedObject) {
let removeListener = this.openmct.objects.observe(this.observedObject, '*', (newObject) => {
this.observedObject = newObject;
});
this.$once('hook:destroyed', removeListener);
}
this.previewAction = new PreviewAction(this.openmct);
},
methods: { methods: {
navigateOrPreview(event) { navigateOrPreview(event) {
if (this.openmct.editor.isEditing()){ if (this.openmct.editor.isEditing()) {
event.preventDefault(); event.preventDefault();
this.preview(); this.preview();
} }
}, },
preview() { preview() {
if (this.previewAction.appliesTo(this.objectPath)){ if (this.previewAction.appliesTo(this.objectPath)) {
this.previewAction.invoke(this.objectPath); this.previewAction.invoke(this.objectPath);
} }
}, },

View File

@ -1,4 +1,5 @@
<template> <template>
<div></div>
</template> </template>
<script> <script>
@ -7,10 +8,23 @@ import _ from "lodash"
export default { export default {
inject: ["openmct"], inject: ["openmct"],
props: { props: {
view: String, object: {
object: Object, type: Object,
default: undefined
},
showEditView: Boolean, showEditView: Boolean,
objectPath: Array objectPath: {
type: Array,
default: () => {
return [];
}
}
},
watch: {
object(newObject, oldObject) {
this.currentObject = newObject;
this.debounceUpdateView();
}
}, },
destroyed() { destroyed() {
this.clear(); this.clear();
@ -18,16 +32,6 @@ export default {
this.releaseEditModeHandler(); this.releaseEditModeHandler();
} }
}, },
watch: {
view(newView, oldView) {
this.viewKey = newView;
this.debounceUpdateView();
},
object(newObject, oldObject) {
this.currentObject = newObject;
this.debounceUpdateView();
}
},
created() { created() {
this.debounceUpdateView = _.debounce(this.updateView, 10); this.debounceUpdateView = _.debounce(this.updateView, 10);
}, },
@ -114,7 +118,7 @@ export default {
this.currentView.show(this.viewContainer, this.openmct.editor.isEditing()); this.currentView.show(this.viewContainer, this.openmct.editor.isEditing());
if (immediatelySelect) { if (immediatelySelect) {
this.removeSelectable = openmct.selection.selectable( this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.getSelectionContext(), true); this.$el, this.getSelectionContext(), true);
} }
@ -190,7 +194,7 @@ export default {
provider.canEdit && provider.canEdit &&
provider.canEdit(this.currentObject) && provider.canEdit(this.currentObject) &&
!this.openmct.editor.isEditing()) { !this.openmct.editor.isEditing()) {
this.openmct.editor.edit(); this.openmct.editor.edit();
} }
}, },
hasComposableDomainObject(event) { hasComposableDomainObject(event) {

View File

@ -1,17 +1,20 @@
<template> <template>
<div class="c-progress-bar"> <div class="c-progress-bar">
<div class="c-progress-bar__holder"> <div class="c-progress-bar__holder">
<div class="c-progress-bar__bar" <div
:class="{'--indeterminate': model.progressPerc === 'unknown'}" class="c-progress-bar__bar"
:style="`width: ${model.progressPerc}%;`"> :class="{'--indeterminate': model.progressPerc === 'unknown'}"
</div> :style="styleBarWidth"
</div> ></div>
<div class="c-progress-bar__text"
v-if="model.progressText !== undefined">
<span v-if="model.progressPerc > 0">{{model.progressPerc}}% complete.</span>
{{model.progressText}}
</div>
</div> </div>
<div
v-if="model.progressText !== undefined"
class="c-progress-bar__text"
>
<span v-if="model.progressPerc > 0">{{ model.progressPerc }}% complete.</span>
{{ model.progressText }}
</div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -64,6 +67,16 @@
<script> <script>
export default { export default {
props:['model'] props: {
model: {
type: Object,
required: true
}
},
computed: {
styleBarWidth() {
return `width: ${this.model.progressPerc}%;`
}
}
} }
</script> </script>

View File

@ -1,11 +1,13 @@
<template> <template>
<label class="c-toggle-switch"> <label class="c-toggle-switch">
<input type="checkbox" <input
:id="id" :id="id"
:checked="checked" type="checkbox"
@change="onUserSelect($event)"/> :checked="checked"
<span class="c-toggle-switch__slider"></span> @change="onUserSelect($event)"
</label> >
<span class="c-toggle-switch__slider"></span>
</label>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -63,16 +65,19 @@
</style> </style>
<script> <script>
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
id: String, id: {
checked: Boolean type: String,
required: true
}, },
methods: { checked: Boolean
onUserSelect(event) { },
this.$emit('change', event.target.checked); methods: {
} onUserSelect(event) {
this.$emit('change', event.target.checked);
} }
} }
}
</script> </script>

View File

@ -1,14 +1,21 @@
<template> <template>
<div class="c-so-view__context-actions c-disclosure-button" <div
@click="showContextMenu"></div> class="c-so-view__context-actions c-disclosure-button"
@click="showContextMenu"
></div>
</template> </template>
<script> <script>
import contextMenu from '../mixins/context-menu-gesture' import contextMenu from '../mixins/context-menu-gesture'
export default { export default {
props: ['objectPath'],
mixins: [contextMenu], mixins: [contextMenu],
props: {
objectPath: {
type: Array,
required: true
}
}
} }
</script> </script>

View File

@ -1,15 +1,21 @@
<template> <template>
<div class="c-search" <div
:class="{ 'is-active': active === true }"> class="c-search"
<input class="c-search__input" :class="{ 'is-active': active === true }"
tabindex="10000" >
type="search" <input
v-bind="$attrs" class="c-search__input"
v-bind:value="value" tabindex="10000"
v-on="inputListeners"/> type="search"
<a class="c-search__clear-input icon-x-in-circle" v-bind="$attrs"
v-on:click="clearInput"></a> :value="value"
</div> v-on="inputListeners"
>
<a
class="c-search__clear-input icon-x-in-circle"
@click="clearInput"
></a>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -44,37 +50,40 @@
</style> </style>
<script> <script>
/* Emits input and clear events */ /* Emits input and clear events */
export default { export default {
inheritAttrs: false, inheritAttrs: false,
props: { props: {
value: String value: {
}, type: String,
computed: { default: ''
inputListeners: function () { }
let vm = this; },
return Object.assign({}, data: function () {
this.$listeners, return {
{ active: false
input: function (event) { }
vm.$emit('input', event.target.value); },
vm.active = (event.target.value.length > 0); computed: {
} inputListeners: function () {
let vm = this;
return Object.assign({},
this.$listeners,
{
input: function (event) {
vm.$emit('input', event.target.value);
vm.active = (event.target.value.length > 0);
} }
) }
} )
}, }
data: function() { },
return { methods: {
active: false clearInput() {
} // Clear the user's input and set 'active' to false
}, this.$emit('clear','');
methods: { this.active = false;
clearInput() {
// Clear the user's input and set 'active' to false
this.$emit('clear','');
this.active = false;
}
} }
} }
}
</script> </script>

View File

@ -1,25 +1,27 @@
<template> <template>
<span class="c-disclosure-triangle" <span
class="c-disclosure-triangle"
:class="{ :class="{
'c-disclosure-triangle--expanded' : value, 'c-disclosure-triangle--expanded' : value,
'is-enabled' : enabled 'is-enabled' : enabled
}" }"
@click="$emit('input', !value)"></span> @click="$emit('input', !value)"
></span>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
value: { value: {
type: Boolean, type: Boolean,
default: false default: false
}, },
enabled: { enabled: {
// Provided to allow the view-control to still occupy space without displaying a control icon. // Provided to allow the view-control to still occupy space without displaying a control icon.
// Used as such in the tree - when a node doesn't have children, set disabled to true. // Used as such in the tree - when a node doesn't have children, set disabled to true.
type: Boolean, type: Boolean,
default: false default: false
}
} }
} }
}
</script> </script>

View File

@ -1,29 +1,49 @@
<template> <template>
<div class="c-elements-pool"> <div class="c-elements-pool">
<Search class="c-elements-pool__search" <Search
class="c-elements-pool__search"
:value="currentSearch" :value="currentSearch"
@input="applySearch" @input="applySearch"
@clear="applySearch"> @clear="applySearch"
</Search> />
<div class="c-elements-pool__elements" <div
:class="{'is-dragging': isDragging}"> class="c-elements-pool__elements"
<ul class="c-tree c-elements-pool__tree" id="inspector-elements-tree" :class="{'is-dragging': isDragging}"
v-if="elements.length > 0"> >
<li :key="element.identifier.key" v-for="(element, index) in elements" <ul
v-if="elements.length > 0"
id="inspector-elements-tree"
class="c-tree c-elements-pool__tree"
>
<li
v-for="(element, index) in elements"
:key="element.identifier.key"
@drop="moveTo(index)" @drop="moveTo(index)"
@dragover="allowDrop"> @dragover="allowDrop"
<div class="c-tree__item c-elements-pool__item" >
draggable="true" <div
@dragstart="moveFrom(index)"> class="c-tree__item c-elements-pool__item"
<span class="c-elements-pool__grippy" draggable="true"
v-if="elements.length > 1 && isEditing"> @dragstart="moveFrom(index)"
</span> >
<object-label :domainObject="element" :objectPath="[element, parentObject]"></object-label> <span
v-if="elements.length > 1 && isEditing"
class="c-elements-pool__grippy"
></span>
<object-label
:domain-object="element"
:object-path="[element, parentObject]"
/>
</div> </div>
</li> </li>
<li class="js-last-place" @drop="moveToIndex(elements.length)"></li> <li
class="js-last-place"
@drop="moveToIndex(elements.length)"
></li>
</ul> </ul>
<div v-if="elements.length === 0">No contained elements</div> <div v-if="elements.length === 0">
No contained elements
</div>
</div> </div>
</div> </div>
</template> </template>
@ -89,12 +109,23 @@ export default {
}, },
mounted() { mounted() {
let selection = this.openmct.selection.get(); let selection = this.openmct.selection.get();
if (selection && selection.length > 0){ if (selection && selection.length > 0) {
this.showSelection(selection); this.showSelection(selection);
} }
this.openmct.selection.on('change', this.showSelection); this.openmct.selection.on('change', this.showSelection);
this.openmct.editor.on('isEditing', this.setEditState); this.openmct.editor.on('isEditing', this.setEditState);
}, },
destroyed() {
this.openmct.editor.off('isEditing', this.setEditState);
this.openmct.selection.off('change', this.showSelection);
if (this.mutationUnobserver) {
this.mutationUnobserver();
}
if (this.compositionUnlistener) {
this.compositionUnlistener();
}
},
methods: { methods: {
setEditState(isEditing) { setEditState(isEditing) {
this.isEditing = isEditing; this.isEditing = isEditing;
@ -168,7 +199,7 @@ export default {
moveTo(moveToIndex) { moveTo(moveToIndex) {
this.composition.reorder(this.moveFromIndex, moveToIndex); this.composition.reorder(this.moveFromIndex, moveToIndex);
}, },
moveFrom(index){ moveFrom(index) {
this.isDragging = true; this.isDragging = true;
this.moveFromIndex = index; this.moveFromIndex = index;
document.addEventListener('dragend', this.hideDragStyling); document.addEventListener('dragend', this.hideDragStyling);
@ -177,17 +208,6 @@ export default {
this.isDragging = false; this.isDragging = false;
document.removeEventListener('dragend', this.hideDragStyling); document.removeEventListener('dragend', this.hideDragStyling);
} }
},
destroyed() {
this.openmct.editor.off('isEditing', this.setEditState);
this.openmct.selection.off('change', this.showSelection);
if (this.mutationUnobserver) {
this.mutationUnobserver();
}
if (this.compositionUnlistener) {
this.compositionUnlistener();
}
} }
} }
</script> </script>

View File

@ -1,17 +1,22 @@
<template> <template>
<multipane class="c-inspector" <multipane
type="vertical"> class="c-inspector"
<pane class="c-inspector__properties"> type="vertical"
<properties></properties> >
<location></location> <pane class="c-inspector__properties">
<inspector-views></inspector-views> <properties />
</pane> <location />
<pane class="c-inspector__elements" <inspector-views />
handle="before" </pane>
label="Elements" v-if="isEditing && hasComposition"> <pane
<elements></elements> v-if="isEditing && hasComposition"
</pane> class="c-inspector__elements"
</multipane> handle="before"
label="Elements"
>
<elements />
</pane>
</multipane>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -174,46 +179,46 @@
</style> </style>
<script> <script>
import multipane from '../layout/multipane.vue'; import multipane from '../layout/multipane.vue';
import pane from '../layout/pane.vue'; import pane from '../layout/pane.vue';
import Elements from './Elements.vue'; import Elements from './Elements.vue';
import Location from './Location.vue'; import Location from './Location.vue';
import Properties from './Properties.vue'; import Properties from './Properties.vue';
import InspectorViews from './InspectorViews.vue'; import InspectorViews from './InspectorViews.vue';
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { components: {
'isEditing': Boolean multipane,
}, pane,
components: { Elements,
multipane, Properties,
pane, Location,
Elements, InspectorViews
Properties, },
Location, props: {
InspectorViews 'isEditing': Boolean
}, },
data() { data() {
return { return {
hasComposition: false hasComposition: false
} }
}, },
methods: { mounted() {
refreshComposition(selection) { this.openmct.selection.on('change', this.refreshComposition);
if (selection.length > 0 && selection[0].length > 0) { this.refreshComposition(this.openmct.selection.get());
let parentObject = selection[0][0].context.item; },
destroyed() {
this.openmct.selection.off('change', this.refreshComposition);
},
methods: {
refreshComposition(selection) {
if (selection.length > 0 && selection[0].length > 0) {
let parentObject = selection[0][0].context.item;
this.hasComposition = !!(parentObject && this.openmct.composition.get(parentObject)); this.hasComposition = !!(parentObject && 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> </script>

View File

@ -1,46 +1,43 @@
<template> <template>
<div> <div></div>
</div>
</template> </template>
<style> <style>
</style> </style>
<script> <script>
import _ from 'lodash'; export default {
inject: ['openmct'],
data() {
return {
selection: []
}
},
mounted() {
this.openmct.selection.on('change', this.updateSelection);
this.updateSelection(this.openmct.selection.get());
},
destroyed() {
this.openmct.selection.off('change', this.updateSelection);
},
methods: {
updateSelection(selection) {
this.selection = selection;
export default { if (this.selectedViews) {
inject: ['openmct'],
mounted() {
this.openmct.selection.on('change', this.updateSelection);
this.updateSelection(this.openmct.selection.get());
},
destroyed() {
this.openmct.selection.off('change', this.updateSelection);
},
data() {
return {
selection: []
}
},
methods: {
updateSelection(selection) {
this.selection = selection;
if (this.selectedViews) {
this.selectedViews.forEach(selectedView => {
selectedView.destroy();
});
this.$el.innerHTML = '';
}
this.selectedViews = this.openmct.inspectorViews.get(selection);
this.selectedViews.forEach(selectedView => { this.selectedViews.forEach(selectedView => {
let viewContainer = document.createElement('div'); selectedView.destroy();
this.$el.append(viewContainer)
selectedView.show(viewContainer);
}); });
this.$el.innerHTML = '';
} }
this.selectedViews = this.openmct.inspectorViews.get(selection);
this.selectedViews.forEach(selectedView => {
let viewContainer = document.createElement('div');
this.$el.append(viewContainer)
selectedView.show(viewContainer);
});
} }
} }
}
</script> </script>

View File

@ -1,21 +1,39 @@
<template> <template>
<div class="c-properties c-properties--location"> <div class="c-properties c-properties--location">
<div class="c-properties__header" title="The location of this linked object.">Original Location</div> <div
<ul class="c-properties__section" v-if="!multiSelect"> class="c-properties__header"
<li class="c-properties__row" v-if="originalPath.length"> title="The location of this linked object."
>
Original Location
</div>
<ul
v-if="!multiSelect"
class="c-properties__section"
>
<li
v-if="originalPath.length"
class="c-properties__row"
>
<ul class="c-properties__value c-location"> <ul class="c-properties__value c-location">
<li v-for="pathObject in orderedOriginalPath" <li
v-for="pathObject in orderedOriginalPath"
:key="pathObject.key"
class="c-location__item" class="c-location__item"
:key="pathObject.key"> >
<object-label <object-label
:domainObject="pathObject.domainObject" :domain-object="pathObject.domainObject"
:objectPath="pathObject.objectPath"> :object-path="pathObject.objectPath"
</object-label> />
</li> </li>
</ul> </ul>
</li> </li>
</ul> </ul>
<div class="c-properties__row--span-all" v-if="multiSelect">No location to display for multiple items</div> <div
v-if="multiSelect"
class="c-properties__row--span-all"
>
No location to display for multiple items
</div>
</div> </div>
</template> </template>
@ -78,6 +96,11 @@ export default {
keyString: '' keyString: ''
} }
}, },
computed: {
orderedOriginalPath() {
return this.originalPath.slice().reverse();
}
},
mounted() { mounted() {
this.openmct.selection.on('change', this.updateSelection); this.openmct.selection.on('change', this.updateSelection);
this.updateSelection(this.openmct.selection.get()); this.updateSelection(this.openmct.selection.get());
@ -139,11 +162,6 @@ export default {
.then(this.setOriginalPath); .then(this.setOriginalPath);
} }
} }
},
computed: {
orderedOriginalPath() {
return this.originalPath.reverse();
}
} }
} }
</script> </script>

View File

@ -1,32 +1,75 @@
<template> <template>
<div class="c-properties c-properties--properties"> <div class="c-properties c-properties--properties">
<div class="c-properties__header">Properties</div> <div class="c-properties__header">
<ul class="c-properties__section" v-if="!multiSelect && !singleSelectNonObject"> Properties
</div>
<ul
v-if="!multiSelect && !singleSelectNonObject"
class="c-properties__section"
>
<li class="c-properties__row"> <li class="c-properties__row">
<div class="c-properties__label">Title</div> <div class="c-properties__label">
<div class="c-properties__value">{{ item.name }}</div> Title
</div>
<div class="c-properties__value">
{{ item.name }}
</div>
</li> </li>
<li class="c-properties__row"> <li class="c-properties__row">
<div class="c-properties__label">Type</div> <div class="c-properties__label">
<div class="c-properties__value">{{ typeName }}</div> Type
</div>
<div class="c-properties__value">
{{ typeName }}
</div>
</li> </li>
<li class="c-properties__row" v-if="item.created"> <li
<div class="c-properties__label">Created</div> v-if="item.created"
<div class="c-properties__value c-ne__text">{{ formatTime(item.created) }}</div> class="c-properties__row"
>
<div class="c-properties__label">
Created
</div>
<div class="c-properties__value c-ne__text">
{{ formatTime(item.created) }}
</div>
</li> </li>
<li class="c-properties__row" v-if="item.modified"> <li
<div class="c-properties__label">Modified</div> v-if="item.modified"
<div class="c-properties__value c-ne__text">{{ formatTime(item.modified) }}</div> class="c-properties__row"
>
<div class="c-properties__label">
Modified
</div>
<div class="c-properties__value c-ne__text">
{{ formatTime(item.modified) }}
</div>
</li> </li>
<li class="c-properties__row" <li
v-for="prop in typeProperties" v-for="prop in typeProperties"
:key="prop.name"> :key="prop.name"
<div class="c-properties__label">{{ prop.name }}</div> class="c-properties__row"
<div class="c-properties__value">{{ prop.value }}</div> >
<div class="c-properties__label">
{{ prop.name }}
</div>
<div class="c-properties__value">
{{ prop.value }}
</div>
</li> </li>
</ul> </ul>
<div class="c-properties__row--span-all" v-if="multiSelect">No properties to display for multiple items</div> <div
<div class="c-properties__row--span-all" v-if="singleSelectNonObject">No properties to display for this item</div> v-if="multiSelect"
class="c-properties__row--span-all"
>
No properties to display for multiple items
</div>
<div
v-if="singleSelectNonObject"
class="c-properties__row--span-all"
>
No properties to display for this item
</div>
</div> </div>
</template> </template>
@ -77,8 +120,8 @@ export default {
.map((field) => { .map((field) => {
return { return {
name: field.name, name: field.name,
value: field.path.reduce((object, field) => { value: field.path.reduce((object, key) => {
return object[field]; return object[key];
}, this.item) }, this.item)
}; };
}); });

View File

@ -1,22 +1,34 @@
<template> <template>
<!-- eslint-disable vue/no-v-html -->
<div class="c-about c-about--splash"> <div class="c-about c-about--splash">
<div class="c-about__image c-splash-image"></div> <div class="c-about__image c-splash-image"></div>
<div class="c-about__text s-text"> <div class="c-about__text s-text">
<div class="c-about__text__element" v-if="branding.aboutHtml" v-html="branding.aboutHtml"></div> <div
v-if="branding.aboutHtml"
class="c-about__text__element"
v-html="branding.aboutHtml"
></div>
<div class="c-about__text__element"> <div class="c-about__text__element">
<h1 class="l-title s-title">Open MCT</h1> <h1 class="l-title s-title">
Open MCT
</h1>
<div class="l-description s-description"> <div class="l-description s-description">
<p>Open MCT, Copyright &copy; 2014-2019, United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved.</p> <p>Open MCT, Copyright &copy; 2014-2019, United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved.</p>
<p>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 <a target="_blank" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>.</p> <p>
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 <a
target="_blank"
href="http://www.apache.org/licenses/LICENSE-2.0"
>http://www.apache.org/licenses/LICENSE-2.0</a>.
</p>
<p>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.</p> <p>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.</p>
<p>Open MCT includes source code licensed under additional open source licenses. See the Open Source Licenses file included with this distribution or <a @click="showLicenses">click here for third party licensing information</a>.</p> <p>Open MCT includes source code licensed under additional open source licenses. See the Open Source Licenses file included with this distribution or <a @click="showLicenses">click here for third party licensing information</a>.</p>
</div> </div>
<h2>Version Information</h2> <h2>Version Information</h2>
<ul class="t-info l-info s-info"> <ul class="t-info l-info s-info">
<li>Version: {{buildInfo.version || 'Unknown'}}</li> <li>Version: {{ buildInfo.version || 'Unknown' }}</li>
<li>Build Date: {{buildInfo.buildDate || 'Unknown'}}</li> <li>Build Date: {{ buildInfo.buildDate || 'Unknown' }}</li>
<li>Revision: {{buildInfo.revision || 'Unknown'}}</li> <li>Revision: {{ buildInfo.revision || 'Unknown' }}</li>
<li>Branch: {{buildInfo.branch || 'Unknown'}}</li> <li>Branch: {{ buildInfo.branch || 'Unknown' }}</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -20,7 +20,11 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="l-shell__app-logo" @click="launchAbout" ref="aboutLogo"></div> <div
ref="aboutLogo"
class="l-shell__app-logo"
@click="launchAbout"
></div>
</template> </template>
<style lang="scss"> <style lang="scss">
.l-shell__app-logo { .l-shell__app-logo {
@ -38,12 +42,12 @@ export default {
inject: ['openmct'], inject: ['openmct'],
mounted() { mounted() {
let branding = this.openmct.branding(); let branding = this.openmct.branding();
if (branding.smallLogoImage){ if (branding.smallLogoImage) {
this.$refs.aboutLogo.style.backgroundImage = `url('${branding.smallLogoImage}')` this.$refs.aboutLogo.style.backgroundImage = `url('${branding.smallLogoImage}')`
} }
}, },
methods: { methods: {
launchAbout(){ launchAbout() {
let vm = new Vue({ let vm = new Vue({
provide: { provide: {
openmct: this.openmct openmct: this.openmct

View File

@ -1,61 +1,93 @@
<template> <template>
<div class="l-browse-bar"> <div class="l-browse-bar">
<div class="l-browse-bar__start"> <div class="l-browse-bar__start">
<button v-if="hasParent" <button
class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-pointer-left" v-if="hasParent"
@click="goToParent"></button> class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-pointer-left"
<div class="l-browse-bar__object-name--w" @click="goToParent"
:class="type.cssClass"> ></button>
<span <div
class="l-browse-bar__object-name c-input-inline" class="l-browse-bar__object-name--w"
@blur="updateName" :class="type.cssClass"
@keydown.enter.prevent >
@keyup.enter.prevent="updateNameOnEnterKeyPress" <span
contenteditable> class="l-browse-bar__object-name c-input-inline"
{{ domainObject.name }} contenteditable
</span> @blur="updateName"
</div> @keydown.enter.prevent
<div class="l-browse-bar__context-actions c-disclosure-button" @click.prevent.stop="showContextMenu"></div> @keyup.enter.prevent="updateNameOnEnterKeyPress"
>
{{ domainObject.name }}
</span>
</div> </div>
<div
class="l-browse-bar__context-actions c-disclosure-button"
@click.prevent.stop="showContextMenu"
></div>
</div>
<div class="l-browse-bar__end"> <div class="l-browse-bar__end">
<view-switcher <view-switcher
:currentView="currentView" :current-view="currentView"
:views="views" :views="views"
@setView="setView"> @setView="setView"
</view-switcher> />
<!-- Action buttons --> <!-- Action buttons -->
<div class="l-browse-bar__actions"> <div class="l-browse-bar__actions">
<button v-if="notebookEnabled" <button
class="l-browse-bar__actions__notebook-entry c-button icon-notebook" v-if="notebookEnabled"
title="New Notebook entry" class="l-browse-bar__actions__notebook-entry c-button icon-notebook"
@click="snapshot()"> title="New Notebook entry"
</button> @click="snapshot()"
<button class="l-browse-bar__actions__edit c-button c-button--major icon-pencil" title="Edit" v-if="isViewEditable & !isEditing" @click="edit()"></button> ></button>
<button
v-if="isViewEditable & !isEditing"
class="l-browse-bar__actions__edit c-button c-button--major icon-pencil"
title="Edit"
@click="edit()"
></button>
<div class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left" <div
v-if="isEditing"> v-if="isEditing"
<button class="c-button--menu c-button--major icon-save" title="Save" @click.stop="toggleSaveMenu"></button> class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
<div class="c-menu" v-show="showSaveMenu"> >
<ul> <button
<li @click="saveAndFinishEditing" class="c-button--menu c-button--major icon-save"
class="icon-save" title="Save"
title="Save and Finish Editing"> @click.stop="toggleSaveMenu"
Save and Finish Editing ></button>
</li> <div
<li @click="saveAndContinueEditing" v-show="showSaveMenu"
class="icon-save" class="c-menu"
title="Save and Continue Editing"> >
Save and Continue Editing <ul>
</li> <li
</ul> class="icon-save"
</div> title="Save and Finish Editing"
@click="saveAndFinishEditing"
>
Save and Finish Editing
</li>
<li
class="icon-save"
title="Save and Continue Editing"
@click="saveAndContinueEditing"
>
Save and Continue Editing
</li>
</ul>
</div> </div>
<button class="l-browse-bar__actions c-button icon-x" title="Cancel Editing" v-if="isEditing" @click="promptUserandCancelEditing()"></button>
</div> </div>
<button
v-if="isEditing"
class="l-browse-bar__actions c-button icon-x"
title="Cancel Editing"
@click="promptUserandCancelEditing()"
></button>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -63,187 +95,186 @@ import NotebookSnapshot from '../utils/notebook-snapshot';
import ViewSwitcher from './ViewSwitcher.vue'; import ViewSwitcher from './ViewSwitcher.vue';
const PLACEHOLDER_OBJECT = {}; const PLACEHOLDER_OBJECT = {};
export default { export default {
inject: ['openmct'], inject: ['openmct'],
components: { components: {
ViewSwitcher ViewSwitcher
},
data: function () {
return {
showViewMenu: false,
showSaveMenu: false,
domainObject: PLACEHOLDER_OBJECT,
viewKey: undefined,
isEditing: this.openmct.editor.isEditing(),
notebookEnabled: false
}
},
computed: {
currentView() {
return this.views.filter(v => v.key === this.viewKey)[0] || {};
}, },
methods: { views() {
toggleSaveMenu() { return this
this.showSaveMenu = !this.showSaveMenu; .openmct
}, .objectViews
closeViewAndSaveMenu() { .get(this.domainObject)
this.showViewMenu = false; .map((p) => {
this.showSaveMenu = false; return {
}, key: p.key,
updateName(event) { cssClass: p.cssClass,
if (event.target.innerText !== this.domainObject.name && event.target.innerText.match(/\S/)) { name: p.name
this.openmct.objects.mutate(this.domainObject, 'name', event.target.innerText); };
} else {
event.target.innerText = this.domainObject.name;
}
},
updateNameOnEnterKeyPress (event) {
event.target.blur();
},
setView(view) {
this.viewKey = view.key;
this.openmct.router.updateParams({
view: this.viewKey
}); });
},
edit() {
this.openmct.editor.edit();
},
promptUserandCancelEditing() {
let dialog = this.openmct.overlays.dialog({
iconClass: 'alert',
message: 'Any unsaved changes will be lost. Are you sure you want to continue?',
buttons: [
{
label: 'Ok',
emphasis: true,
callback: () => {
this.openmct.editor.cancel().then(() => {
//refresh object view
this.openmct.layout.$refs.browseObject.show(this.domainObject, this.viewKey, true);
});
dialog.dismiss();
}
},
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
}
}
]
});
},
promptUserbeforeNavigatingAway(event) {
if(this.openmct.editor.isEditing()) {
event.preventDefault();
event.returnValue = '';
}
},
saveAndFinishEditing() {
let dialog = this.openmct.overlays.progressDialog({
progressPerc: 'unknown',
message: 'Do not navigate away from this page or close this browser tab while this message is displayed.',
iconClass: 'info',
title: 'Saving',
});
return this.openmct.editor.save()
.then(()=> {
dialog.dismiss();
this.openmct.notifications.info('Save successful');
}).catch((error) => {
dialog.dismiss();
this.openmct.notifications.error('Error saving objects');
console.error(error);
});
},
saveAndContinueEditing() {
this.saveAndFinishEditing().then(() => {
this.openmct.editor.edit();
});
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.openmct.router.path, event.clientX, event.clientY);
},
snapshot() {
let element = document.getElementsByClassName("l-shell__main-container")[0];
this.notebookSnapshot.capture(this.domainObject, element);
},
goToParent(){
window.location.hash = this.parentUrl;
}
}, },
data: function () { hasParent() {
return { return this.domainObject !== PLACEHOLDER_OBJECT &&
showViewMenu: false,
showSaveMenu: false,
domainObject: PLACEHOLDER_OBJECT,
viewKey: undefined,
isEditing: this.openmct.editor.isEditing(),
notebookEnabled: false
}
},
computed: {
currentView() {
return this.views.filter(v => v.key === this.viewKey)[0] || {};
},
views() {
return this
.openmct
.objectViews
.get(this.domainObject)
.map((p) => {
return {
key: p.key,
cssClass: p.cssClass,
name: p.name
};
});
},
hasParent() {
return this.domainObject !== PLACEHOLDER_OBJECT &&
this.parentUrl !== '#/browse' this.parentUrl !== '#/browse'
},
parentUrl() {
let objectKeyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
let hash = window.location.hash;
return hash.slice(0, hash.lastIndexOf('/' + objectKeyString));
},
type() {
let objectType = this.openmct.types.get(this.domainObject.type);
if (!objectType) {
return {}
}
return objectType.definition;
},
isViewEditable() {
let currentViewKey = this.currentView.key;
if (currentViewKey !== undefined) {
let currentViewProvider = this.openmct.objectViews.getByProviderKey(currentViewKey);
return currentViewProvider.canEdit && currentViewProvider.canEdit(this.domainObject);
}
return false;
}
}, },
mounted: function () { parentUrl() {
let objectKeyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
if (this.openmct.types.get('notebook')) { let hash = window.location.hash;
this.notebookSnapshot = new NotebookSnapshot(this.openmct); return hash.slice(0, hash.lastIndexOf('/' + objectKeyString));
this.notebookEnabled = true;
}
document.addEventListener('click', this.closeViewAndSaveMenu);
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
this.openmct.editor.on('isEditing', (isEditing) => {
this.isEditing = isEditing;
});
}, },
watch: { type() {
domainObject() { let objectType = this.openmct.types.get(this.domainObject.type);
if (this.mutationObserver) { if (!objectType) {
this.mutationObserver(); return {}
}
this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
this.domainObject = domainObject;
});
} }
return objectType.definition;
}, },
beforeDestroy: function () { isViewEditable() {
let currentViewKey = this.currentView.key;
if (currentViewKey !== undefined) {
let currentViewProvider = this.openmct.objectViews.getByProviderKey(currentViewKey);
return currentViewProvider.canEdit && currentViewProvider.canEdit(this.domainObject);
}
return false;
}
},
watch: {
domainObject() {
if (this.mutationObserver) { if (this.mutationObserver) {
this.mutationObserver(); this.mutationObserver();
} }
document.removeEventListener('click', this.closeViewAndSaveMenu); this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
window.removeEventListener('click', this.promptUserbeforeNavigatingAway); this.domainObject = domainObject;
});
}
},
mounted: function () {
if (this.openmct.types.get('notebook')) {
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
this.notebookEnabled = true;
}
document.addEventListener('click', this.closeViewAndSaveMenu);
window.addEventListener('beforeunload', this.promptUserbeforeNavigatingAway);
this.openmct.editor.on('isEditing', (isEditing) => {
this.isEditing = isEditing;
});
},
beforeDestroy: function () {
if (this.mutationObserver) {
this.mutationObserver();
}
document.removeEventListener('click', this.closeViewAndSaveMenu);
window.removeEventListener('click', this.promptUserbeforeNavigatingAway);
},
methods: {
toggleSaveMenu() {
this.showSaveMenu = !this.showSaveMenu;
},
closeViewAndSaveMenu() {
this.showViewMenu = false;
this.showSaveMenu = false;
},
updateName(event) {
if (event.target.innerText !== this.domainObject.name && event.target.innerText.match(/\S/)) {
this.openmct.objects.mutate(this.domainObject, 'name', event.target.innerText);
} else {
event.target.innerText = this.domainObject.name;
}
},
updateNameOnEnterKeyPress(event) {
event.target.blur();
},
setView(view) {
this.viewKey = view.key;
this.openmct.router.updateParams({
view: this.viewKey
});
},
edit() {
this.openmct.editor.edit();
},
promptUserandCancelEditing() {
let dialog = this.openmct.overlays.dialog({
iconClass: 'alert',
message: 'Any unsaved changes will be lost. Are you sure you want to continue?',
buttons: [
{
label: 'Ok',
emphasis: true,
callback: () => {
this.openmct.editor.cancel().then(() => {
//refresh object view
this.openmct.layout.$refs.browseObject.show(this.domainObject, this.viewKey, true);
});
dialog.dismiss();
}
},
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
}
}
]
});
},
promptUserbeforeNavigatingAway(event) {
if(this.openmct.editor.isEditing()) {
event.preventDefault();
event.returnValue = '';
}
},
saveAndFinishEditing() {
let dialog = this.openmct.overlays.progressDialog({
progressPerc: 'unknown',
message: 'Do not navigate away from this page or close this browser tab while this message is displayed.',
iconClass: 'info',
title: 'Saving'
});
return this.openmct.editor.save().then(()=> {
dialog.dismiss();
this.openmct.notifications.info('Save successful');
}).catch((error) => {
dialog.dismiss();
this.openmct.notifications.error('Error saving objects');
console.error(error);
});
},
saveAndContinueEditing() {
this.saveAndFinishEditing().then(() => {
this.openmct.editor.edit();
});
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.openmct.router.path, event.clientX, event.clientY);
},
snapshot() {
let element = document.getElementsByClassName("l-shell__main-container")[0];
this.notebookSnapshot.capture(this.domainObject, element);
},
goToParent() {
window.location.hash = this.parentUrl;
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -1,30 +1,40 @@
<template> <template>
<div class="c-create-button--w"> <div class="c-create-button--w">
<button class="c-create-button c-button--menu c-button--major icon-plus" <button
@click="open"> class="c-create-button c-button--menu c-button--major icon-plus"
<span class="c-button__label">Create</span> @click="open"
</button> >
<div class="c-create-menu c-super-menu" <span class="c-button__label">Create</span>
v-if="opened"> </button>
<div class="c-super-menu__menu"> <div
<ul> v-if="opened"
<li v-for="(item, index) in sortedItems" class="c-create-menu c-super-menu"
:key="index" >
:class="item.class" <div class="c-super-menu__menu">
:title="item.title" <ul>
@mouseover="showItemDescription(item)" <li
@click="create(item)"> v-for="(item, index) in sortedItems"
{{ item.name }} :key="index"
</li> :class="item.class"
</ul> :title="item.title"
@mouseover="showItemDescription(item)"
@click="create(item)"
>
{{ item.name }}
</li>
</ul>
</div>
<div class="c-super-menu__item-description">
<div :class="['l-item-description__icon', 'bg-' + selectedMenuItem.class]"></div>
<div class="l-item-description__name">
{{ selectedMenuItem.name }}
</div> </div>
<div class="c-super-menu__item-description"> <div class="l-item-description__description">
<div :class="['l-item-description__icon', 'bg-' + selectedMenuItem.class]"></div> {{ selectedMenuItem.title }}
<div class="l-item-description__name">{{selectedMenuItem.name}}</div>
<div class="l-item-description__description">{{selectedMenuItem.title}}</div>
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -62,102 +72,97 @@
</style> </style>
<script> <script>
import CreateAction from '../../../platform/commonUI/edit/src/creation/CreateAction'; import CreateAction from '../../../platform/commonUI/edit/src/creation/CreateAction';
import objectUtils from '../../api/objects/object-utils'; import objectUtils from '../../api/objects/object-utils';
function convertToLegacyObject(domainObject) { export default {
let keyString = objectUtils.makeKeyString(domainObject.identifier); inject: ['openmct'],
let oldModel = objectUtils.toOldFormat(domainObject); data: function () {
return instantiate(oldModel, keyString); let items = [];
}
export default { this.openmct.types.listKeys().forEach(key => {
inject: ['openmct'], let menuItem = this.openmct.types.get(key).definition;
methods: {
open: function () { if (menuItem.creatable) {
if (this.opened) { let menuItemTemplate = {
return; key: key,
} name: menuItem.name,
this.opened = true; class: menuItem.cssClass,
setTimeout(() => document.addEventListener('click', this.close)); title: menuItem.description
}, };
close: function () {
if (!this.opened) { items.push(menuItemTemplate);
return;
}
this.opened = false;
document.removeEventListener('click', this.close);
},
showItemDescription: function (menuItem) {
this.selectedMenuItem = menuItem;
},
create: function (item) {
// Hack for support. TODO: rewrite create action.
// 1. Get contextual object from navigation
// 2. Get legacy type from legacy api
// 3. Instantiate create action with type, parent, context
// 4. perform action.
return this.openmct.objects.get(this.openmct.router.path[0].identifier)
.then((currentObject) => {
let legacyContextualParent = this.convertToLegacy(currentObject);
let legacyType = this.openmct.$injector.get('typeService').getType(item.key);
let context = {
key: "create",
domainObject: legacyContextualParent // should be same as parent object.
};
let action = new CreateAction(
legacyType,
legacyContextualParent,
context,
this.openmct
);
return action.perform();
});
},
convertToLegacy (domainObject) {
let keyString = objectUtils.makeKeyString(domainObject.identifier);
let oldModel = objectUtils.toOldFormat(domainObject);
return this.openmct.$injector.get('instantiate')(oldModel, keyString);
} }
}, });
destroyed () {
document.removeEventListener('click', this.close);
},
data: function() {
let items = [];
this.openmct.types.listKeys().forEach(key => { return {
let menuItem = this.openmct.types.get(key).definition; items: items,
selectedMenuItem: {},
if (menuItem.creatable) { opened: false
let menuItemTemplate = { }
key: key, },
name: menuItem.name, computed: {
class: menuItem.cssClass, sortedItems() {
title: menuItem.description return this.items.slice().sort((a,b) => {
}; if (a.name < b.name) {
return -1;
items.push(menuItemTemplate); } else if (a.name > b.name) {
return 1;
} else {
return 0;
} }
}); });
}
return { },
items: items, destroyed() {
selectedMenuItem: {}, document.removeEventListener('click', this.close);
opened: false },
methods: {
open: function () {
if (this.opened) {
return;
} }
this.opened = true;
setTimeout(() => document.addEventListener('click', this.close));
}, },
computed: { close: function () {
sortedItems () { if (!this.opened) {
return this.items.sort((a,b) => { return;
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
} }
this.opened = false;
document.removeEventListener('click', this.close);
},
showItemDescription: function (menuItem) {
this.selectedMenuItem = menuItem;
},
create: function (item) {
// Hack for support. TODO: rewrite create action.
// 1. Get contextual object from navigation
// 2. Get legacy type from legacy api
// 3. Instantiate create action with type, parent, context
// 4. perform action.
return this.openmct.objects.get(this.openmct.router.path[0].identifier)
.then((currentObject) => {
let legacyContextualParent = this.convertToLegacy(currentObject);
let legacyType = this.openmct.$injector.get('typeService').getType(item.key);
let context = {
key: "create",
domainObject: legacyContextualParent // should be same as parent object.
};
let action = new CreateAction(
legacyType,
legacyContextualParent,
context,
this.openmct
);
return action.perform();
});
},
convertToLegacy(domainObject) {
let keyString = objectUtils.makeKeyString(domainObject.identifier);
let oldModel = objectUtils.toOldFormat(domainObject);
return this.openmct.$injector.get('instantiate')(oldModel, keyString);
} }
} }
}
</script> </script>

View File

@ -1,60 +1,84 @@
<template> <template>
<div class="l-shell" :class="{ <div
'is-editing': isEditing class="l-shell"
}"> :class="{
<div class="l-shell__head" :class="{ 'is-editing': isEditing
}"
>
<div
class="l-shell__head"
:class="{
'l-shell__head--expanded': headExpanded, 'l-shell__head--expanded': headExpanded,
'l-shell__head--minify-indicators': !headExpanded 'l-shell__head--minify-indicators': !headExpanded
}"> }"
<CreateButton class="l-shell__create-button"></CreateButton> >
<indicators class="l-shell__head-section l-shell__indicators"> <CreateButton class="l-shell__create-button" />
</indicators> <indicators class="l-shell__head-section l-shell__indicators" />
<button class="l-shell__head__collapse-button c-button" <button
@click="toggleShellHead"></button> class="l-shell__head__collapse-button c-button"
<notification-banner></notification-banner> @click="toggleShellHead"
<div class="l-shell__head-section l-shell__controls"> ></button>
<button class="c-icon-button c-icon-button--major icon-new-window" title="Open in a new browser tab" <notification-banner />
@click="openInNewTab" <div class="l-shell__head-section l-shell__controls">
target="_blank"> <button
</button> class="c-icon-button c-icon-button--major icon-new-window"
<button v-bind:class="['c-icon-button c-icon-button--major', fullScreen ? 'icon-fullscreen-collapse' : 'icon-fullscreen-expand']" title="Open in a new browser tab"
v-bind:title="`${fullScreen ? 'Exit' : 'Enable'} full screen mode`" target="_blank"
@click="fullScreenToggle"> @click="openInNewTab"
</button> ></button>
</div> <button
<app-logo></app-logo> :class="['c-icon-button c-icon-button--major', fullScreen ? 'icon-fullscreen-collapse' : 'icon-fullscreen-expand']"
:title="`${fullScreen ? 'Exit' : 'Enable'} full screen mode`"
@click="fullScreenToggle"
></button>
</div> </div>
<multipane class="l-shell__main" <app-logo />
type="horizontal">
<pane class="l-shell__pane-tree"
handle="after"
label="Browse"
collapsable>
<mct-tree class="l-shell__tree"></mct-tree>
</pane>
<pane class="l-shell__pane-main">
<browse-bar class="l-shell__main-view-browse-bar"
ref="browseBar">
</browse-bar>
<toolbar v-if="toolbar" class="l-shell__toolbar"></toolbar>
<object-view class="l-shell__main-container"
ref="browseObject"
:showEditView="true"
data-selectable
>
</object-view>
<component class="l-shell__time-conductor"
:is="conductorComponent">
</component>
</pane>
<pane class="l-shell__pane-inspector l-pane--holds-multipane"
handle="before"
label="Inspect"
collapsable>
<Inspector :isEditing="isEditing" ref="inspector"></Inspector>
</pane>
</multipane>
</div> </div>
<multipane
class="l-shell__main"
type="horizontal"
>
<pane
class="l-shell__pane-tree"
handle="after"
label="Browse"
collapsable
>
<mct-tree class="l-shell__tree" />
</pane>
<pane class="l-shell__pane-main">
<browse-bar
ref="browseBar"
class="l-shell__main-view-browse-bar"
/>
<toolbar
v-if="toolbar"
class="l-shell__toolbar"
/>
<object-view
ref="browseObject"
class="l-shell__main-container"
:show-edit-view="true"
data-selectable
/>
<component
:is="conductorComponent"
class="l-shell__time-conductor"
/>
</pane>
<pane
class="l-shell__pane-inspector l-pane--holds-multipane"
handle="before"
label="Inspect"
collapsable
>
<Inspector
ref="inspector"
:is-editing="isEditing"
/>
</pane>
</multipane>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -302,126 +326,126 @@
</style> </style>
<script> <script>
import Inspector from '../inspector/Inspector.vue'; import Inspector from '../inspector/Inspector.vue';
import MctTree from './mct-tree.vue'; import MctTree from './mct-tree.vue';
import ObjectView from '../components/ObjectView.vue'; import ObjectView from '../components/ObjectView.vue';
import MctTemplate from '../legacy/mct-template.vue'; import MctTemplate from '../legacy/mct-template.vue';
import CreateButton from './CreateButton.vue'; import CreateButton from './CreateButton.vue';
import multipane from './multipane.vue'; import multipane from './multipane.vue';
import pane from './pane.vue'; import pane from './pane.vue';
import BrowseBar from './BrowseBar.vue'; import BrowseBar from './BrowseBar.vue';
import Toolbar from '../toolbar/Toolbar.vue'; import Toolbar from '../toolbar/Toolbar.vue';
import AppLogo from './AppLogo.vue'; import AppLogo from './AppLogo.vue';
import Indicators from './status-bar/Indicators.vue'; import Indicators from './status-bar/Indicators.vue';
import NotificationBanner from './status-bar/NotificationBanner.vue'; import NotificationBanner from './status-bar/NotificationBanner.vue';
var enterFullScreen = () => { var enterFullScreen = () => {
var docElm = document.documentElement; var docElm = document.documentElement;
if (docElm.requestFullscreen) { if (docElm.requestFullscreen) {
docElm.requestFullscreen(); docElm.requestFullscreen();
} else if (docElm.mozRequestFullScreen) { /* Firefox */ } else if (docElm.mozRequestFullScreen) { /* Firefox */
docElm.mozRequestFullScreen(); docElm.mozRequestFullScreen();
} else if (docElm.webkitRequestFullscreen) { /* Chrome, Safari and Opera */ } else if (docElm.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
docElm.webkitRequestFullscreen(); docElm.webkitRequestFullscreen();
} else if (docElm.msRequestFullscreen) { /* IE/Edge */ } else if (docElm.msRequestFullscreen) { /* IE/Edge */
docElm.msRequestFullscreen(); docElm.msRequestFullscreen();
}
};
var exitFullScreen = () => {
if (document.exitFullscreen) {
document.exitFullscreen();
}
else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
}
else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
}
else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
export default {
inject: ['openmct'],
components: {
Inspector,
MctTree,
ObjectView,
'mct-template': MctTemplate,
CreateButton,
multipane,
pane,
BrowseBar,
Toolbar,
AppLogo,
Indicators,
NotificationBanner
},
data: function () {
let storedHeadProps = window.localStorage.getItem('openmct-shell-head');
let headExpanded = true;
if (storedHeadProps) {
headExpanded = JSON.parse(storedHeadProps).expanded;
} }
};
var exitFullScreen = () => { return {
if (document.exitFullscreen) { fullScreen: false,
document.exitFullscreen(); conductorComponent: undefined,
isEditing: false,
hasToolbar: false,
headExpanded
} }
else if (document.mozCancelFullScreen) { },
document.mozCancelFullScreen(); computed: {
toolbar() {
return this.hasToolbar && this.isEditing;
} }
else if (document.webkitCancelFullScreen) { },
document.webkitCancelFullScreen(); mounted() {
} this.openmct.editor.on('isEditing', (isEditing)=>{
else if (document.msExitFullscreen) { this.isEditing = isEditing;
document.msExitFullscreen(); });
}
} this.openmct.selection.on('change', this.toggleHasToolbar);
},
export default { methods: {
inject: ['openmct'], toggleShellHead() {
components: { this.headExpanded = !this.headExpanded;
Inspector,
MctTree, window.localStorage.setItem(
ObjectView, 'openmct-shell-head',
'mct-template': MctTemplate, JSON.stringify(
CreateButton, {
multipane, expanded: this.headExpanded
pane, }
BrowseBar, )
Toolbar, );
AppLogo, },
Indicators, fullScreenToggle() {
NotificationBanner if (this.fullScreen) {
}, this.fullScreen = false;
mounted() { exitFullScreen();
this.openmct.editor.on('isEditing', (isEditing)=>{ } else {
this.isEditing = isEditing; this.fullScreen = true;
}); enterFullScreen();
}
this.openmct.selection.on('change', this.toggleHasToolbar); },
}, openInNewTab(event) {
data: function () { window.open(window.location.href);
let storedHeadProps = window.localStorage.getItem('openmct-shell-head'); },
let headExpanded = true; toggleHasToolbar(selection) {
if (storedHeadProps) { let structure = undefined;
headExpanded = JSON.parse(storedHeadProps).expanded;
} if (!selection || !selection[0]) {
structure = [];
return { } else {
fullScreen: false, structure = this.openmct.toolbars.get(selection);
conductorComponent: undefined, }
isEditing: false,
hasToolbar: false, this.hasToolbar = structure.length > 0;
headExpanded
}
},
computed: {
toolbar() {
return this.hasToolbar && this.isEditing;
}
},
methods: {
toggleShellHead() {
this.headExpanded = !this.headExpanded;
window.localStorage.setItem(
'openmct-shell-head',
JSON.stringify(
{
expanded: this.headExpanded
}
)
);
},
fullScreenToggle() {
if (this.fullScreen) {
this.fullScreen = false;
exitFullScreen();
} else {
this.fullScreen = true;
enterFullScreen();
}
},
openInNewTab(event) {
window.open(window.location.href);
},
toggleHasToolbar(selection) {
let structure = undefined;
if (!selection || !selection[0]) {
structure = [];
} else {
structure = this.openmct.toolbars.get(selection);
}
this.hasToolbar = structure.length > 0;
}
} }
} }
}
</script> </script>

View File

@ -1,7 +1,10 @@
<template> <template>
<div class="c-search c-search--major"> <div class="c-search c-search--major">
<input type="search" placeholder="Search"/> <input
</div> type="search"
placeholder="Search"
>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -20,6 +23,6 @@
</style> </style>
<script> <script>
export default { export default {
} }
</script> </script>

View File

@ -1,39 +1,60 @@
<template> <template>
<div class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left" <div
v-if="views.length > 1"> v-if="views.length > 1"
<button class="c-button--menu" class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
:class="currentView.cssClass" >
title="Switch view type" <button
@click.stop="toggleViewMenu"> class="c-button--menu"
<span class="c-button__label"> :class="currentView.cssClass"
{{ currentView.name }} title="Switch view type"
</span> @click.stop="toggleViewMenu"
</button> >
<div class="c-menu" v-show="showViewMenu"> <span class="c-button__label">
<ul> {{ currentView.name }}
<li v-for="(view, index) in views" </span>
@click="setView(view)" </button>
:key="index" <div
:class="view.cssClass" v-show="showViewMenu"
:title="view.name"> class="c-menu"
{{ view.name }} >
</li> <ul>
</ul> <li
</div> v-for="(view, index) in views"
:key="index"
:class="view.cssClass"
:title="view.name"
@click="setView(view)"
>
{{ view.name }}
</li>
</ul>
</div> </div>
</div>
</template> </template>
<script> <script>
export default { export default {
props: [ props: {
'currentView', currentView: {
'views' type: Object,
], required: true
},
views: {
type: Array,
required: true
}
},
data() { data() {
return { return {
showViewMenu: false showViewMenu: false
} }
}, },
mounted() {
document.addEventListener('click', this.hideViewMenu);
},
destroyed() {
document.removeEventListener('click', this.hideViewMenu);
},
methods: { methods: {
setView(view) { setView(view) {
this.$emit('setView', view); this.$emit('setView', view);
@ -44,12 +65,6 @@ export default {
hideViewMenu() { hideViewMenu() {
this.showViewMenu = false; this.showViewMenu = false;
} }
},
mounted() {
document.addEventListener('click', this.hideViewMenu);
},
destroyed() {
document.removeEventListener('click', this.hideViewMenu);
} }
} }
</script> </script>

View File

@ -1,44 +1,56 @@
<template> <template>
<div class="c-tree-and-search"> <div class="c-tree-and-search">
<div class="c-tree-and-search__search"> <div class="c-tree-and-search__search">
<search class="c-search" ref="shell-search" <search
:value="searchValue" ref="shell-search"
@input="searchTree" class="c-search"
@clear="searchTree"> :value="searchValue"
</search> @input="searchTree"
</div> @clear="searchTree"
/>
<!-- loading -->
<div class="c-tree-and-search__loading loading"
v-if="isLoading"></div>
<!-- end loading -->
<div class="c-tree-and-search__no-results"
v-if="(allTreeItems.length === 0) || (searchValue && filteredTreeItems.length === 0)">
No results found
</div>
<!-- main tree -->
<ul class="c-tree-and-search__tree c-tree"
v-if="!isLoading"
v-show="!searchValue">
<tree-item v-for="treeItem in allTreeItems"
:key="treeItem.id"
:node="treeItem">
</tree-item>
</ul>
<!-- end main tree -->
<!-- search tree -->
<ul class="c-tree-and-search__tree c-tree"
v-if="searchValue">
<tree-item v-for="treeItem in filteredTreeItems"
:key="treeItem.id"
:node="treeItem">
</tree-item>
</ul>
<!-- end search tree -->
</div> </div>
<!-- loading -->
<div
v-if="isLoading"
class="c-tree-and-search__loading loading"
></div>
<!-- end loading -->
<div
v-if="(allTreeItems.length === 0) || (searchValue && filteredTreeItems.length === 0)"
class="c-tree-and-search__no-results"
>
No results found
</div>
<!-- main tree -->
<ul
v-if="!isLoading"
v-show="!searchValue"
class="c-tree-and-search__tree c-tree"
>
<tree-item
v-for="treeItem in allTreeItems"
:key="treeItem.id"
:node="treeItem"
/>
</ul>
<!-- end main tree -->
<!-- search tree -->
<ul
v-if="searchValue"
class="c-tree-and-search__tree c-tree"
>
<tree-item
v-for="treeItem in filteredTreeItems"
:key="treeItem.id"
:node="treeItem"
/>
</ul>
<!-- end search tree -->
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -185,81 +197,81 @@
</style> </style>
<script> <script>
import treeItem from './tree-item.vue' import treeItem from './tree-item.vue'
import search from '../components/search.vue'; import search from '../components/search.vue';
export default {
inject: ['openmct'],
name: 'mct-tree',
components: {
search,
treeItem
},
data() {
return {
searchValue: '',
allTreeItems: [],
filteredTreeItems: [],
isLoading: false
}
},
methods: {
getAllChildren() {
this.isLoading = true;
this.openmct.objects.get('ROOT')
.then(root => {
return this.openmct.composition.get(root).load()
})
.then(children => {
this.isLoading = false;
this.allTreeItems = children.map(c => {
return {
id: this.openmct.objects.makeKeyString(c.identifier),
object: c,
objectPath: [c],
navigateToParent: '/browse'
};
});
});
},
getFilteredChildren() {
this.searchService.query(this.searchValue).then(children => {
this.filteredTreeItems = children.hits.map(child => {
let context = child.object.getCapability('context'),
object = child.object.useCapability('adapter'),
objectPath = [],
navigateToParent;
if (context) {
objectPath = context.getPath().slice(1)
.map(oldObject => oldObject.useCapability('adapter'))
.reverse();
navigateToParent = '/browse/' + objectPath.slice(1)
.map((parent) => this.openmct.objects.makeKeyString(parent.identifier))
.join('/');
}
export default {
inject: ['openmct'],
name: 'MctTree',
components: {
search,
treeItem
},
data() {
return {
searchValue: '',
allTreeItems: [],
filteredTreeItems: [],
isLoading: false
}
},
mounted() {
this.searchService = this.openmct.$injector.get('searchService');
this.getAllChildren();
},
methods: {
getAllChildren() {
this.isLoading = true;
this.openmct.objects.get('ROOT')
.then(root => {
return this.openmct.composition.get(root).load()
})
.then(children => {
this.isLoading = false;
this.allTreeItems = children.map(c => {
return { return {
id: this.openmct.objects.makeKeyString(object.identifier), id: this.openmct.objects.makeKeyString(c.identifier),
object, object: c,
objectPath, objectPath: [c],
navigateToParent navigateToParent: '/browse'
} };
}); });
}); });
},
searchTree(value) {
this.searchValue = value;
if (this.searchValue !== '') {
this.getFilteredChildren();
}
}
}, },
mounted() { getFilteredChildren() {
this.searchService = this.openmct.$injector.get('searchService'); this.searchService.query(this.searchValue).then(children => {
this.getAllChildren(); this.filteredTreeItems = children.hits.map(child => {
let context = child.object.getCapability('context'),
object = child.object.useCapability('adapter'),
objectPath = [],
navigateToParent;
if (context) {
objectPath = context.getPath().slice(1)
.map(oldObject => oldObject.useCapability('adapter'))
.reverse();
navigateToParent = '/browse/' + objectPath.slice(1)
.map((parent) => this.openmct.objects.makeKeyString(parent.identifier))
.join('/');
}
return {
id: this.openmct.objects.makeKeyString(object.identifier),
object,
objectPath,
navigateToParent
}
});
});
},
searchTree(value) {
this.searchValue = value;
if (this.searchValue !== '') {
this.getFilteredChildren();
}
} }
} }
}
</script> </script>

View File

@ -1,11 +1,13 @@
<template> <template>
<div class="l-multipane" <div
:class="{ class="l-multipane"
'l-multipane--vertical': type === 'vertical', :class="{
'l-multipane--horizontal': type === 'horizontal' 'l-multipane--vertical': type === 'vertical',
}"> 'l-multipane--horizontal': type === 'horizontal'
<slot></slot> }"
</div> >
<slot></slot>
</div>
</template> </template>
<script> <script>
@ -13,6 +15,7 @@ export default {
props: { props: {
type: { type: {
type: String, type: String,
required: true,
validator: function (value) { validator: function (value) {
return ['vertical', 'horizontal'].indexOf(value) !== -1; return ['vertical', 'horizontal'].indexOf(value) !== -1;
} }

View File

@ -1,29 +1,36 @@
<template> <template>
<div class="l-pane" <div
:class="{ class="l-pane"
'l-pane--horizontal-handle-before': type === 'horizontal' && handle === 'before', :class="{
'l-pane--horizontal-handle-after': type === 'horizontal' && handle === 'after', 'l-pane--horizontal-handle-before': type === 'horizontal' && handle === 'before',
'l-pane--vertical-handle-before': type === 'vertical' && handle === 'before', 'l-pane--horizontal-handle-after': type === 'horizontal' && handle === 'after',
'l-pane--vertical-handle-after': type === 'vertical' && handle === 'after', 'l-pane--vertical-handle-before': type === 'vertical' && handle === 'before',
'l-pane--collapsed': collapsed, 'l-pane--vertical-handle-after': type === 'vertical' && handle === 'after',
'l-pane--reacts': !handle, 'l-pane--collapsed': collapsed,
'l-pane--resizing': resizing === true 'l-pane--reacts': !handle,
}"> 'l-pane--resizing': resizing === true
<div v-if="handle" }"
class="l-pane__handle" >
@mousedown="start"> <div
</div> v-if="handle"
<div class="l-pane__header" class="l-pane__handle"
v-if="label"> @mousedown="start"
<span class="l-pane__label">{{ label }}</span> ></div>
<button class="l-pane__collapse-button c-button" <div
v-if="collapsable" v-if="label"
@click="toggleCollapse"></button> class="l-pane__header"
</div> >
<div class="l-pane__contents"> <span class="l-pane__label">{{ label }}</span>
<slot></slot> <button
</div> v-if="collapsable"
class="l-pane__collapse-button c-button"
@click="toggleCollapse"
></button>
</div> </div>
<div class="l-pane__contents">
<slot></slot>
</div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -350,15 +357,19 @@ export default {
props: { props: {
handle: { handle: {
type: String, type: String,
default: '',
validator: function (value) { validator: function (value) {
return ['before', 'after'].indexOf(value) !== -1; return ['', 'before', 'after'].indexOf(value) !== -1;
} }
}, },
collapsable: { collapsable: {
type: Boolean, type: Boolean,
default: false default: false
}, },
label: String label: {
type: String,
default: ''
}
}, },
data() { data() {
return { return {
@ -384,7 +395,7 @@ export default {
delete this.dragCollapse; delete this.dragCollapse;
} }
}, },
trackSize: function() { trackSize: function () {
if (!this.dragCollapse === true) { if (!this.dragCollapse === true) {
if (this.type === 'vertical') { if (this.type === 'vertical') {
this.initial = this.$el.offsetHeight; this.initial = this.$el.offsetHeight;

View File

@ -17,6 +17,7 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<template> <template>
<div></div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -152,13 +153,13 @@
</style> </style>
<script> <script>
export default { export default {
inject: ['openmct'], inject: ['openmct'],
mounted() { mounted() {
this.openmct.indicators.indicatorObjects.forEach((indicator) => { this.openmct.indicators.indicatorObjects.forEach((indicator) => {
this.$el.appendChild(indicator.element); this.$el.appendChild(indicator.element);
}); });
}
} }
}
</script> </script>

View File

@ -17,22 +17,27 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<template> <template>
<div class="c-message-banner" <div
v-if="activeModel.message"
class="c-message-banner"
:class="[ :class="[
activeModel.severity, activeModel.severity,
{ {
'minimized': activeModel.minimized, 'minimized': activeModel.minimized,
'new': !activeModel.minimized 'new': !activeModel.minimized
}]" }]"
@click="maximize()" @click="maximize()"
v-if="activeModel.message"> >
<span class="c-message-banner__message">{{activeModel.message}}</span> <span class="c-message-banner__message">{{ activeModel.message }}</span>
<progress-bar <progress-bar
class="c-message-banner__progress-bar" v-if="activeModel.progressPerc !== undefined"
v-if="activeModel.progressPerc !== undefined" :model="activeModel"> class="c-message-banner__progress-bar"
</progress-bar> :model="activeModel"
<button class="c-message-banner__close-button c-click-icon icon-x-in-circle" />
@click.stop="dismiss()"></button> <button
class="c-message-banner__close-button c-click-icon icon-x-in-circle"
@click.stop="dismiss()"
></button>
</div> </div>
</template> </template>
@ -116,127 +121,126 @@
</style> </style>
<script> <script>
import ProgressBar from '../../components/ProgressBar.vue'; import ProgressBar from '../../components/ProgressBar.vue';
let activeNotification = undefined; let activeNotification = undefined;
let dialogService = undefined; let maximizedDialog = undefined;
let maximizedDialog = undefined; let minimizeButton = {
let minimizeButton = { label: 'Dismiss',
label: 'Dismiss', callback: dismissMaximizedDialog
callback: dismissMaximizedDialog }
}
function dismissMaximizedDialog() { function dismissMaximizedDialog() {
if (maximizedDialog) { if (maximizedDialog) {
maximizedDialog.dismiss(); maximizedDialog.dismiss();
maximizedDialog = undefined; maximizedDialog = undefined;
}
}
function updateMaxProgressBar(progressPerc, progressText) {
if (maximizedDialog) {
maximizedDialog.updateProgress(progressPerc, progressText);
if (progressPerc >= 100) {
dismissMaximizedDialog();
} }
} }
}
function updateMaxProgressBar(progressPerc, progressText) { export default {
if (maximizedDialog) { inject: ['openmct'],
maximizedDialog.updateProgress(progressPerc, progressText); components: {
ProgressBar: ProgressBar
if (progressPerc >= 100) { },
dismissMaximizedDialog(); data() {
return {
activeModel: {
message: undefined,
progressPerc: undefined,
progressText: undefined,
minimized: undefined
} }
} }
} },
computed: {
export default { progressWidth() {
inject: ['openmct'],
components: {
ProgressBar: ProgressBar
},
data() {
return { return {
activeModel: { width: this.activeModel.progress + '%'
message: undefined, };
progressPerc: undefined, }
progressText: undefined, },
minimized: undefined mounted() {
} this.openmct.notifications.on('notification', this.showNotification);
} },
}, methods: {
methods: { showNotification(notification) {
showNotification(notification) { if (activeNotification) {
if (activeNotification) {
activeNotification.off('progress', this.updateProgress);
activeNotification.off('minimized', this.minimized);
activeNotification.off('destroy', this.destroyActiveNotification);
}
activeNotification = notification;
this.clearModel();
this.applyModel(notification.model);
activeNotification.once('destroy', this.destroyActiveNotification);
activeNotification.on('progress', this.updateProgress);
activeNotification.on('minimized', this.minimized);
},
isEqual(modelA, modelB) {
return modelA.message === modelB.message &&
modelA.timestamp === modelB.timestamp;
},
applyModel(model) {
Object.keys(model).forEach((key) => this.activeModel[key] = model[key]);
},
clearModel() {
Object.keys(this.activeModel).forEach((key) => this.activeModel[key] = undefined);
},
updateProgress(progressPerc, progressText) {
this.activeModel.progressPerc = progressPerc;
this.activeModel.progressText = progressText;
},
destroyActiveNotification() {
this.clearModel();
activeNotification.off('destroy', this.destroyActiveNotification);
activeNotification = undefined;
},
dismiss() {
if (activeNotification.model.severity === 'info') {
activeNotification.dismiss();
} else {
this.openmct.notifications._minimize(activeNotification);
}
},
minimized() {
this.activeModel.minimized = true;
activeNotification.off('progress', this.updateProgress); activeNotification.off('progress', this.updateProgress);
activeNotification.off('minimized', this.minimized); activeNotification.off('minimized', this.minimized);
activeNotification.off('destroy', this.destroyActiveNotification);
}
activeNotification = notification;
this.clearModel();
this.applyModel(notification.model);
activeNotification.off('progress', updateMaxProgressBar); activeNotification.once('destroy', this.destroyActiveNotification);
activeNotification.off('minimized', dismissMaximizedDialog); activeNotification.on('progress', this.updateProgress);
activeNotification.off('destroy', dismissMaximizedDialog); activeNotification.on('minimized', this.minimized);
}, },
maximize() { isEqual(modelA, modelB) {
if (this.activeModel.progressPerc !== undefined) { return modelA.message === modelB.message &&
maximizedDialog = this.openmct.overlays.progressDialog({ modelA.timestamp === modelB.timestamp;
buttons: [minimizeButton], },
...this.activeModel applyModel(model) {
}); Object.keys(model).forEach((key) => this.activeModel[key] = model[key]);
},
activeNotification.on('progress', updateMaxProgressBar); clearModel() {
activeNotification.on('minimized', dismissMaximizedDialog); Object.keys(this.activeModel).forEach((key) => this.activeModel[key] = undefined);
activeNotification.on('destroy', dismissMaximizedDialog); },
updateProgress(progressPerc, progressText) {
} else { this.activeModel.progressPerc = progressPerc;
maximizedDialog = this.openmct.overlays.dialog({ this.activeModel.progressText = progressText;
iconClass: this.activeModel.severity, },
buttons: [minimizeButton], destroyActiveNotification() {
...this.activeModel this.clearModel();
}) activeNotification.off('destroy', this.destroyActiveNotification);
} activeNotification = undefined;
},
dismiss() {
if (activeNotification.model.severity === 'info') {
activeNotification.dismiss();
} else {
this.openmct.notifications._minimize(activeNotification);
} }
}, },
computed: { minimized() {
progressWidth() { this.activeModel.minimized = true;
return { activeNotification.off('progress', this.updateProgress);
width: this.activeModel.progress + '%' activeNotification.off('minimized', this.minimized);
};
} activeNotification.off('progress', updateMaxProgressBar);
activeNotification.off('minimized', dismissMaximizedDialog);
activeNotification.off('destroy', dismissMaximizedDialog);
}, },
mounted() { maximize() {
this.openmct.notifications.on('notification', this.showNotification); if (this.activeModel.progressPerc !== undefined) {
maximizedDialog = this.openmct.overlays.progressDialog({
buttons: [minimizeButton],
...this.activeModel
});
activeNotification.on('progress', updateMaxProgressBar);
activeNotification.on('minimized', dismissMaximizedDialog);
activeNotification.on('destroy', dismissMaximizedDialog);
} else {
maximizedDialog = this.openmct.overlays.dialog({
iconClass: this.activeModel.severity,
buttons: [minimizeButton],
...this.activeModel
})
}
} }
} }
}
</script> </script>

View File

@ -1,136 +1,149 @@
<template> <template>
<li class="c-tree__item-h"> <li class="c-tree__item-h">
<div class="c-tree__item" <div
:class="{ 'is-alias': isAlias, 'is-navigated-object': isNavigated }"> class="c-tree__item"
<view-control class="c-tree__item__view-control" :class="{ 'is-alias': isAlias, 'is-navigated-object': isNavigated }"
:enabled="hasChildren" >
v-model="expanded"> <view-control
</view-control> v-model="expanded"
<object-label :domainObject="node.object" class="c-tree__item__view-control"
:objectPath="node.objectPath" :enabled="hasChildren"
:navigateToPath="navigateToPath"> />
</object-label> <object-label
</div> :domain-object="node.object"
<ul v-if="expanded" class="c-tree"> :object-path="node.objectPath"
<li class="c-tree__item-h" :navigate-to-path="navigateToPath"
v-if="isLoading && !loaded"> />
<div class="c-tree__item loading"> </div>
<span class="c-tree__item__label">Loading...</span> <ul
</div> v-if="expanded"
</li> class="c-tree"
<tree-item v-for="child in children" >
:key="child.id" <li
:node="child"> v-if="isLoading && !loaded"
</tree-item> class="c-tree__item-h"
</ul> >
</li> <div class="c-tree__item loading">
<span class="c-tree__item__label">Loading...</span>
</div>
</li>
<tree-item
v-for="child in children"
:key="child.id"
:node="child"
/>
</ul>
</li>
</template> </template>
<script> <script>
import viewControl from '../components/viewControl.vue'; import viewControl from '../components/viewControl.vue';
import ObjectLabel from '../components/ObjectLabel.vue'; import ObjectLabel from '../components/ObjectLabel.vue';
export default { export default {
name: 'tree-item', name: 'TreeItem',
inject: ['openmct'], inject: ['openmct'],
props: { components: {
node: Object viewControl,
}, ObjectLabel
data() { },
this.navigateToPath = this.buildPathString(this.node.navigateToParent) props: {
return { node: {
hasChildren: false, type: Object,
isLoading: false, required: true
loaded: false, }
isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path, },
children: [], data() {
expanded: false this.navigateToPath = this.buildPathString(this.node.navigateToParent)
return {
hasChildren: false,
isLoading: false,
loaded: false,
isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path,
children: [],
expanded: false
}
},
computed: {
isAlias() {
let parent = this.node.objectPath[1];
if (!parent) {
return false;
} }
}, let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
computed: { return parentKeyString !== this.node.object.location;
isAlias() { }
let parent = this.node.objectPath[1]; },
if (!parent) { watch: {
return false; expanded(isExpanded) {
} if (!this.hasChildren) {
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier); return;
return parentKeyString !== this.node.object.location;
} }
}, if (!this.loaded && !this.isLoading) {
mounted() { this.composition = this.openmct.composition.get(this.domainObject);
// TODO: should update on mutation. this.composition.on('add', this.addChild);
// TODO: click navigation should not fubar hash quite so much. this.composition.on('remove', this.removeChild);
// TODO: should highlight if navigated to. this.composition.load().then(this.finishLoading);
// TODO: should have context menu. this.isLoading = true;
// TODO: should support drag/drop composition }
// TODO: set isAlias per tree-item }
},
mounted() {
// TODO: should update on mutation.
// TODO: click navigation should not fubar hash quite so much.
// TODO: should highlight if navigated to.
// TODO: should have context menu.
// TODO: should support drag/drop composition
// TODO: set isAlias per tree-item
this.domainObject = this.node.object; this.domainObject = this.node.object;
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => { let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
this.domainObject = newObject; this.domainObject = newObject;
});
this.$once('hook:destroyed', removeListener);
if (this.openmct.composition.get(this.node.object)) {
this.hasChildren = true;
}
this.openmct.router.on('change:path', this.highlightIfNavigated);
},
destroyed() {
this.openmct.router.off('change:path', this.highlightIfNavigated);
if (this.composition) {
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
delete this.composition;
}
},
methods: {
addChild(child) {
this.children.push({
id: this.openmct.objects.makeKeyString(child.identifier),
object: child,
objectPath: [child].concat(this.node.objectPath),
navigateToParent: this.navigateToPath
}); });
this.$once('hook:destroyed', removeListener);
if (this.openmct.composition.get(this.node.object)) {
this.hasChildren = true;
}
this.openmct.router.on('change:path', this.highlightIfNavigated);
}, },
destroyed() { removeChild(identifier) {
this.openmct.router.off('change:path', this.highlightIfNavigated); let removeId = this.openmct.objects.makeKeyString(identifier);
if (this.composition) { this.children = this.children
this.composition.off('add', this.addChild); .filter(c => c.id !== removeId);
this.composition.off('remove', this.removeChild);
delete this.composition;
}
}, },
watch: { finishLoading() {
expanded(isExpanded) { this.isLoading = false;
if (!this.hasChildren) { this.loaded = true;
return;
}
if (!this.loaded && !this.isLoading) {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);
this.composition.load().then(this.finishLoading);
this.isLoading = true;
}
}
}, },
methods: { buildPathString(parentPath) {
addChild (child) { return [parentPath, this.openmct.objects.makeKeyString(this.node.object.identifier)].join('/');
this.children.push({
id: this.openmct.objects.makeKeyString(child.identifier),
object: child,
objectPath: [child].concat(this.node.objectPath),
navigateToParent: this.navigateToPath
});
},
removeChild(identifier) {
let removeId = this.openmct.objects.makeKeyString(identifier);
this.children = this.children
.filter(c => c.id !== removeId);
},
finishLoading () {
this.isLoading = false;
this.loaded = true;
},
buildPathString(parentPath) {
return [parentPath, this.openmct.objects.makeKeyString(this.node.object.identifier)].join('/');
},
highlightIfNavigated(newPath, oldPath){
if (newPath === this.navigateToPath) {
this.isNavigated = true;
} else if (oldPath === this.navigateToPath) {
this.isNavigated = false;
}
}
}, },
components: { highlightIfNavigated(newPath, oldPath) {
viewControl, if (newPath === this.navigateToPath) {
ObjectLabel this.isNavigated = true;
} else if (oldPath === this.navigateToPath) {
this.isNavigated = false;
}
} }
} }
}
</script> </script>

View File

@ -1,12 +1,15 @@
<template> <template>
<div></div> <div></div>
</template> </template>
<script> <script>
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
templateKey: String templateKey: {
type: String,
default: undefined
}
}, },
mounted() { mounted() {
let openmct = this.openmct; let openmct = this.openmct;

View File

@ -19,37 +19,40 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div class="l-preview-window"> <div class="l-preview-window">
<div class="l-browse-bar"> <div class="l-browse-bar">
<div class="l-browse-bar__start"> <div class="l-browse-bar__start">
<div class="l-browse-bar__object-name--w" <div
:class="type.cssClass"> class="l-browse-bar__object-name--w"
<span class="l-browse-bar__object-name"> :class="type.cssClass"
{{ domainObject.name }} >
</span> <span class="l-browse-bar__object-name">
<context-menu-drop-down :object-path="objectPath"></context-menu-drop-down> {{ domainObject.name }}
</div> </span>
</div> <context-menu-drop-down :object-path="objectPath" />
<div class="l-browse-bar__end">
<div class="l-browse-bar__actions">
<view-switcher
:views="views"
:currentView="currentView"
@setView="setView">
</view-switcher>
<button v-if="notebookEnabled"
class="l-browse-bar__actions__edit c-button icon-notebook"
title="New Notebook entry"
@click="snapshot">
</button>
</div>
</div> </div>
</div> </div>
<div class="l-preview-window__object-view"> <div class="l-browse-bar__end">
<div ref="objectView"></div> <div class="l-browse-bar__actions">
<view-switcher
:views="views"
:current-view="currentView"
@setView="setView"
/>
<button
v-if="notebookEnabled"
class="l-browse-bar__actions__edit c-button icon-notebook"
title="New Notebook entry"
@click="snapshot"
></button>
</div>
</div> </div>
</div> </div>
<div class="l-preview-window__object-view">
<div ref="objectView"></div>
</div>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
@import '~styles/sass-base'; @import '~styles/sass-base';
@ -83,78 +86,78 @@
} }
</style> </style>
<script> <script>
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue'; import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue'; import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
import NotebookSnapshot from '../utils/notebook-snapshot'; import NotebookSnapshot from '../utils/notebook-snapshot';
export default { export default {
components: { components: {
ContextMenuDropDown, ContextMenuDropDown,
ViewSwitcher ViewSwitcher
},
inject: [
'openmct',
'objectPath'
],
data() {
let domainObject = this.objectPath[0];
let type = this.openmct.types.get(domainObject.type);
return {
domainObject: domainObject,
type: type,
notebookEnabled: false,
viewKey: undefined
};
},
computed: {
views() {
return this
.openmct
.objectViews
.get(this.domainObject);
}, },
inject: [ currentView() {
'openmct', return this.views.filter(v => v.key === this.viewKey)[0] || {};
'objectPath' }
], },
computed: { mounted() {
views() { let view = this.openmct.objectViews.get(this.domainObject)[0];
return this this.setView(view);
.openmct
.objectViews if (this.openmct.types.get('notebook')) {
.get(this.domainObject); this.notebookSnapshot = new NotebookSnapshot(this.openmct);
}, this.notebookEnabled = true;
currentView() { }
return this.views.filter(v => v.key === this.viewKey)[0] || {}; },
destroyed() {
this.view.destroy();
},
methods: {
snapshot() {
let element = document.getElementsByClassName("l-preview-window__object-view")[0];
this.notebookSnapshot.capture(this.domainObject, element);
},
clear() {
if (this.view) {
this.view.destroy();
this.$refs.objectView.innerHTML = '';
} }
delete this.view;
delete this.viewContainer;
}, },
methods: { setView(view) {
snapshot() { this.clear();
let element = document.getElementsByClassName("l-preview-window__object-view")[0];
this.notebookSnapshot.capture(this.domainObject, element);
},
clear() {
if (this.view) {
this.view.destroy();
this.$refs.objectView.innerHTML = '';
}
delete this.view;
delete this.viewContainer;
},
setView(view) {
this.clear();
this.viewKey = view.key; this.viewKey = view.key;
this.viewContainer = document.createElement('div'); this.viewContainer = document.createElement('div');
this.viewContainer.classList.add('c-object-view','u-contents'); this.viewContainer.classList.add('c-object-view','u-contents');
this.$refs.objectView.append(this.viewContainer); this.$refs.objectView.append(this.viewContainer);
this.view = this.currentView.view(this.domainObject, this.objectPath); this.view = this.currentView.view(this.domainObject, this.objectPath);
this.view.show(this.viewContainer, false); this.view.show(this.viewContainer, false);
}
},
data() {
let domainObject = this.objectPath[0];
let type = this.openmct.types.get(domainObject.type);
return {
domainObject: domainObject,
type: type,
notebookEnabled: false,
viewKey: undefined
};
},
mounted() {
let view = this.openmct.objectViews.get(this.domainObject)[0];
this.setView(view);
if (this.openmct.types.get('notebook')) {
this.notebookSnapshot = new NotebookSnapshot(this.openmct);
this.notebookEnabled = true;
}
},
destroyed() {
this.view.destroy();
} }
} }
</script> }
</script>

View File

@ -1,267 +1,270 @@
<template> <template>
<div class="c-toolbar"> <div class="c-toolbar">
<component v-for="item in structure" <component
:is="item.control" :is="item.control"
:options="item" v-for="(item, index) in structure"
@click="triggerMethod(item, $event)" :key="index"
@change="updateObjectValue"></component> :options="item"
</div> @click="triggerMethod(item, $event)"
@change="updateObjectValue"
/>
</div>
</template> </template>
<script> <script>
import toolbarButton from './components/toolbar-button.vue'; import toolbarButton from './components/toolbar-button.vue';
import toolbarColorPicker from './components/toolbar-color-picker.vue'; import toolbarColorPicker from './components/toolbar-color-picker.vue';
import toolbarCheckbox from './components/toolbar-checkbox.vue'; import toolbarCheckbox from './components/toolbar-checkbox.vue';
import toolbarInput from './components/toolbar-input.vue'; import toolbarInput from './components/toolbar-input.vue';
import toolbarMenu from './components/toolbar-menu.vue'; import toolbarMenu from './components/toolbar-menu.vue';
import toolbarSelectMenu from './components/toolbar-select-menu.vue'; import toolbarSelectMenu from './components/toolbar-select-menu.vue';
import toolbarSeparator from './components/toolbar-separator.vue'; import toolbarSeparator from './components/toolbar-separator.vue';
import toolbarToggleButton from './components/toolbar-toggle-button.vue'; import toolbarToggleButton from './components/toolbar-toggle-button.vue';
import _ from 'lodash'; import _ from 'lodash';
export default { export default {
inject: ['openmct'], inject: ['openmct'],
components: { components: {
toolbarButton, toolbarButton,
toolbarColorPicker, toolbarColorPicker,
toolbarCheckbox, toolbarCheckbox,
toolbarInput, toolbarInput,
toolbarMenu, toolbarMenu,
toolbarSelectMenu, toolbarSelectMenu,
toolbarSeparator, toolbarSeparator,
toolbarToggleButton toolbarToggleButton
}, },
data: function () { data: function () {
return { return {
structure: [] structure: []
}; };
}, },
methods: { mounted() {
handleSelection(selection) { this.openmct.selection.on('change', this.handleSelection);
this.removeListeners(); this.handleSelection(this.openmct.selection.get());
this.domainObjectsById = {};
if (selection.length === 0 || !selection[0][0]) { // Toolbars may change when edit mode is enabled/disabled, so listen
this.structure = []; // for edit mode changes and update toolbars if necessary.
return; this.openmct.editor.on('isEditing', this.handleEditing);
} },
methods: {
handleSelection(selection) {
this.removeListeners();
this.domainObjectsById = {};
let structure = this.openmct.toolbars.get(selection) || []; if (selection.length === 0 || !selection[0][0]) {
this.structure = structure.map(toolbarItem => { this.structure = [];
let domainObject = toolbarItem.domainObject; return;
let formKeys = []; }
toolbarItem.control = "toolbar-" + toolbarItem.control;
if (toolbarItem.dialog) { let structure = this.openmct.toolbars.get(selection) || [];
toolbarItem.dialog.sections.forEach(section => { this.structure = structure.map(toolbarItem => {
section.rows.forEach(row => { let domainObject = toolbarItem.domainObject;
formKeys.push(row.key); let formKeys = [];
}) toolbarItem.control = "toolbar-" + toolbarItem.control;
});
toolbarItem.formKeys = formKeys;
}
if (domainObject) { if (toolbarItem.dialog) {
toolbarItem.value = this.getValue(domainObject, toolbarItem); toolbarItem.dialog.sections.forEach(section => {
this.registerListener(domainObject); section.rows.forEach(row => {
} formKeys.push(row.key);
})
return toolbarItem;
});
},
registerListener(domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.domainObjectsById[id]) {
this.domainObjectsById[id] = {
domainObject: domainObject
}
this.observeObject(domainObject, id);
}
},
observeObject(domainObject, id) {
let unobserveObject = this.openmct.objects.observe(domainObject, '*', function(newObject) {
this.domainObjectsById[id].newObject = JSON.parse(JSON.stringify(newObject));
this.updateToolbarAfterMutation();
}.bind(this));
this.unObserveObjects.push(unobserveObject);
},
updateToolbarAfterMutation() {
this.structure = this.structure.map(toolbarItem => {
let domainObject = toolbarItem.domainObject;
if (domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
let newObject = this.domainObjectsById[id].newObject;
if (newObject) {
toolbarItem.domainObject = newObject;
toolbarItem.value = this.getValue(newObject, toolbarItem);
}
}
return toolbarItem;
});
Object.values(this.domainObjectsById).forEach(function (tracker) {
if (tracker.newObject) {
tracker.domainObject = tracker.newObject;
delete tracker.newObject;
}
});
},
getValue(domainObject, toolbarItem) {
let value = undefined;
let applicableSelectedItems = toolbarItem.applicableSelectedItems;
if (!applicableSelectedItems && !toolbarItem.property) {
return value;
}
if (toolbarItem.formKeys) {
value = this.getFormValue(domainObject, toolbarItem);
} else {
let values = [];
if (applicableSelectedItems) {
applicableSelectedItems.forEach(selectionPath => {
values.push(this.getPropertyValue(domainObject, toolbarItem, selectionPath));
});
} else {
values.push(this.getPropertyValue(domainObject, toolbarItem));
}
// If all values are the same, use it, otherwise mark the item as non-specific.
if (values.every(value => value === values[0])) {
value = values[0];
toolbarItem.nonSpecific = false;
} else {
toolbarItem.nonSpecific = true;
}
}
return value;
},
getPropertyValue(domainObject, toolbarItem, selectionPath, formKey) {
let property = this.getItemProperty(toolbarItem, selectionPath);
if (formKey) {
property = property + "." + formKey;
}
return _.get(domainObject, property);
},
getFormValue(domainObject, toolbarItem) {
let value = {};
let values = {};
toolbarItem.formKeys.map(key => {
values[key] = [];
if (toolbarItem.applicableSelectedItems) {
toolbarItem.applicableSelectedItems.forEach(selectionPath => {
values[key].push(this.getPropertyValue(domainObject, toolbarItem, selectionPath, key));
});
} else {
values[key].push(this.getPropertyValue(domainObject, toolbarItem, undefined, key));
}
});
for (const key in values) {
if (values[key].every(value => value === values[key][0])) {
value[key] = values[key][0];
toolbarItem.nonSpecific = false;
} else {
toolbarItem.nonSpecific = true;
return {};
}
}
return value;
},
getItemProperty(item, selectionPath) {
return (typeof item.property === "function") ? item.property(selectionPath) : item.property;
},
removeListeners() {
if (this.unObserveObjects) {
this.unObserveObjects.forEach((unObserveObject) => {
unObserveObject();
}); });
} toolbarItem.formKeys = formKeys;
this.unObserveObjects = [];
},
updateObjectValue(value, item) {
let changedItemId = this.openmct.objects.makeKeyString(item.domainObject.identifier);
this.structure = this.structure.map(toolbarItem => {
if (toolbarItem.domainObject) {
let id = this.openmct.objects.makeKeyString(toolbarItem.domainObject.identifier);
if (changedItemId === id && _.isEqual(toolbarItem, item)) {
toolbarItem.value = value;
}
}
return toolbarItem;
});
// If value is an object, iterate the toolbar structure and mutate all keys in form.
// Otherwise, mutate the property.
if (value === Object(value)) {
this.structure.map(s => {
if (s.formKeys) {
s.formKeys.forEach(key => {
if (item.applicableSelectedItems) {
item.applicableSelectedItems.forEach(selectionPath => {
this.mutateObject(item, value[key], selectionPath, key);
});
} else {
this.mutateObject(item, value[key], undefined, key);
}
});
}
});
} else {
if (item.applicableSelectedItems) {
item.applicableSelectedItems.forEach(selectionPath => {
this.mutateObject(item, value, selectionPath);
});
} else {
this.mutateObject(item, value);
}
}
},
mutateObject(item, value, selectionPath, formKey) {
let property = this.getItemProperty(item, selectionPath);
if (formKey) {
property = property + "." + formKey;
} }
this.openmct.objects.mutate(item.domainObject, property, value); if (domainObject) {
}, toolbarItem.value = this.getValue(domainObject, toolbarItem);
triggerMethod(item, event) { this.registerListener(domainObject);
if (item.method) {
item.method({...event});
} }
},
handleEditing(isEditing) { return toolbarItem;
this.handleSelection(this.openmct.selection.get()); });
},
registerListener(domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.domainObjectsById[id]) {
this.domainObjectsById[id] = {
domainObject: domainObject
}
this.observeObject(domainObject, id);
} }
}, },
mounted() { observeObject(domainObject, id) {
this.openmct.selection.on('change', this.handleSelection); let unobserveObject = this.openmct.objects.observe(domainObject, '*', function (newObject) {
this.handleSelection(this.openmct.selection.get()); this.domainObjectsById[id].newObject = JSON.parse(JSON.stringify(newObject));
this.updateToolbarAfterMutation();
// Toolbars may change when edit mode is enabled/disabled, so listen }.bind(this));
// for edit mode changes and update toolbars if necessary. this.unObserveObjects.push(unobserveObject);
this.openmct.editor.on('isEditing', this.handleEditing);
}, },
detroyed() { updateToolbarAfterMutation() {
this.openmct.selection.off('change', this.handleSelection); this.structure = this.structure.map(toolbarItem => {
this.openmct.editor.off('isEditing', this.handleEditing); let domainObject = toolbarItem.domainObject;
this.removeListeners();
if (domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
let newObject = this.domainObjectsById[id].newObject;
if (newObject) {
toolbarItem.domainObject = newObject;
toolbarItem.value = this.getValue(newObject, toolbarItem);
}
}
return toolbarItem;
});
Object.values(this.domainObjectsById).forEach(function (tracker) {
if (tracker.newObject) {
tracker.domainObject = tracker.newObject;
delete tracker.newObject;
}
});
},
getValue(domainObject, toolbarItem) {
let value = undefined;
let applicableSelectedItems = toolbarItem.applicableSelectedItems;
if (!applicableSelectedItems && !toolbarItem.property) {
return value;
}
if (toolbarItem.formKeys) {
value = this.getFormValue(domainObject, toolbarItem);
} else {
let values = [];
if (applicableSelectedItems) {
applicableSelectedItems.forEach(selectionPath => {
values.push(this.getPropertyValue(domainObject, toolbarItem, selectionPath));
});
} else {
values.push(this.getPropertyValue(domainObject, toolbarItem));
}
// If all values are the same, use it, otherwise mark the item as non-specific.
if (values.every(val => val === values[0])) {
value = values[0];
toolbarItem.nonSpecific = false;
} else {
toolbarItem.nonSpecific = true;
}
}
return value;
},
getPropertyValue(domainObject, toolbarItem, selectionPath, formKey) {
let property = this.getItemProperty(toolbarItem, selectionPath);
if (formKey) {
property = property + "." + formKey;
}
return _.get(domainObject, property);
},
getFormValue(domainObject, toolbarItem) {
let value = {};
let values = {};
toolbarItem.formKeys.map(key => {
values[key] = [];
if (toolbarItem.applicableSelectedItems) {
toolbarItem.applicableSelectedItems.forEach(selectionPath => {
values[key].push(this.getPropertyValue(domainObject, toolbarItem, selectionPath, key));
});
} else {
values[key].push(this.getPropertyValue(domainObject, toolbarItem, undefined, key));
}
});
for (const key in values) {
if (values[key].every(val => val === values[key][0])) {
value[key] = values[key][0];
toolbarItem.nonSpecific = false;
} else {
toolbarItem.nonSpecific = true;
return {};
}
}
return value;
},
getItemProperty(item, selectionPath) {
return (typeof item.property === "function") ? item.property(selectionPath) : item.property;
},
removeListeners() {
if (this.unObserveObjects) {
this.unObserveObjects.forEach((unObserveObject) => {
unObserveObject();
});
}
this.unObserveObjects = [];
},
updateObjectValue(value, item) {
let changedItemId = this.openmct.objects.makeKeyString(item.domainObject.identifier);
this.structure = this.structure.map(toolbarItem => {
if (toolbarItem.domainObject) {
let id = this.openmct.objects.makeKeyString(toolbarItem.domainObject.identifier);
if (changedItemId === id && _.isEqual(toolbarItem, item)) {
toolbarItem.value = value;
}
}
return toolbarItem;
});
// If value is an object, iterate the toolbar structure and mutate all keys in form.
// Otherwise, mutate the property.
if (value === Object(value)) {
this.structure.map(s => {
if (s.formKeys) {
s.formKeys.forEach(key => {
if (item.applicableSelectedItems) {
item.applicableSelectedItems.forEach(selectionPath => {
this.mutateObject(item, value[key], selectionPath, key);
});
} else {
this.mutateObject(item, value[key], undefined, key);
}
});
}
});
} else {
if (item.applicableSelectedItems) {
item.applicableSelectedItems.forEach(selectionPath => {
this.mutateObject(item, value, selectionPath);
});
} else {
this.mutateObject(item, value);
}
}
},
mutateObject(item, value, selectionPath, formKey) {
let property = this.getItemProperty(item, selectionPath);
if (formKey) {
property = property + "." + formKey;
}
this.openmct.objects.mutate(item.domainObject, property, value);
},
triggerMethod(item, event) {
if (item.method) {
item.method({...event});
}
},
handleEditing(isEditing) {
this.handleSelection(this.openmct.selection.get());
} }
},
detroyed() {
this.openmct.selection.off('change', this.handleSelection);
this.openmct.editor.off('isEditing', this.handleEditing);
this.removeListeners();
} }
}
</script> </script>

View File

@ -1,26 +1,33 @@
<template> <template>
<div class="c-ctrl-wrapper"> <div class="c-ctrl-wrapper">
<div class="c-icon-button" <div
:title="options.title" class="c-icon-button"
:class="{ :title="options.title"
[options.icon]: true, :class="{
'c-icon-button--caution': options.modifier === 'caution', [options.icon]: true,
'c-icon-button--mixed': nonSpecific 'c-icon-button--caution': options.modifier === 'caution',
}" 'c-icon-button--mixed': nonSpecific
@click="onClick"> }"
<div class="c-icon-button__label" @click="onClick"
v-if="options.label"> >
{{ options.label }} <div
</div> v-if="options.label"
class="c-icon-button__label"
>
{{ options.label }}
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
export default { export default {
inject: ['openmct'], inject: ['openmct'],
props: { props: {
options: Object options: {
type: Object,
required: true
}
}, },
computed: { computed: {
nonSpecific() { nonSpecific() {
@ -31,10 +38,10 @@ export default {
onClick(event) { onClick(event) {
if (this.options.dialog) { if (this.options.dialog) {
this.openmct.$injector.get('dialogService') this.openmct.$injector.get('dialogService')
.getUserInput(this.options.dialog, this.options.value) .getUserInput(this.options.dialog, this.options.value)
.then(value => { .then(value => {
this.$emit('change', {...value}, this.options); this.$emit('change', {...value}, this.options);
}); });
} }
this.$emit('click', this.options); this.$emit('click', this.options);
} }

View File

@ -1,19 +1,21 @@
<template> <template>
<div class="c-custom-checkbox"> <div class="c-custom-checkbox">
<input type="checkbox" <input
:id="uid" :id="uid"
:name="options.name" type="checkbox"
:checked="options.value" :name="options.name"
:disabled="options.disabled" :checked="options.value"
@change="onChange"> :disabled="options.disabled"
@change="onChange"
>
<label :for="uid"> <label :for="uid">
<div class="c-custom-checkbox__box"></div> <div class="c-custom-checkbox__box"></div>
<div class="c-custom-checkbox__label-text"> <div class="c-custom-checkbox__label-text">
{{options.name}} {{ options.name }}
</div> </div>
</label> </label>
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -72,7 +74,10 @@ let uniqueId = 100;
export default { export default {
props: { props: {
options: Object options: {
type: Object,
required: true
}
}, },
data() { data() {
uniqueId++; uniqueId++;

View File

@ -1,30 +1,41 @@
<template> <template>
<div class="c-ctrl-wrapper"> <div class="c-ctrl-wrapper">
<div class="c-icon-button c-icon-button--swatched" <div
:class="[options.icon, {'c-icon-button--mixed': nonSpecific}]" class="c-icon-button c-icon-button--swatched"
:title="options.title" :class="[options.icon, {'c-icon-button--mixed': nonSpecific}]"
@click="toggle"> :title="options.title"
<div class="c-swatch" :style="{ @click="toggle"
>
<div
class="c-swatch"
:style="{
background: options.value background: options.value
}"></div> }"
></div>
</div>
<div
v-if="open"
class="c-menu c-palette c-palette--color"
>
<div
v-if="!options.preventNone"
class="c-palette__item-none"
@click="select({value: 'transparent'})"
>
<div class="c-palette__item"></div>
None
</div> </div>
<div class="c-menu c-palette c-palette--color" <div class="c-palette__items">
v-if="open"> <div
<div class="c-palette__item-none" v-for="(color, index) in colorPalette"
v-if="!this.options.preventNone" :key="index"
@click="select({value: 'transparent'})"> class="c-palette__item"
<div class="c-palette__item"></div> :style="{ background: color.value }"
None @click="select(color)"
</div> ></div>
<div class="c-palette__items">
<div class="c-palette__item"
v-for="color in colorPalette"
:style="{ background: color.value }"
@click="select(color)"
></div>
</div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -34,18 +45,9 @@ import toggleMixin from '../../mixins/toggle-mixin';
export default { export default {
mixins: [toggleMixin], mixins: [toggleMixin],
props: { props: {
options: Object options: {
}, type: Object,
computed: { required: true
nonSpecific() {
return this.options.nonSpecific === true;
}
},
methods: {
select(color) {
if (color.value !== this.options.value) {
this.$emit('change', color.value, this.options);
}
} }
}, },
data() { data() {
@ -133,6 +135,18 @@ export default {
{ value: '#4c1130' } { value: '#4c1130' }
] ]
}; };
},
computed: {
nonSpecific() {
return this.options.nonSpecific === true;
}
},
methods: {
select(color) {
if (color.value !== this.options.value) {
this.$emit('change', color.value, this.options);
}
}
} }
} }
</script> </script>

View File

@ -1,14 +1,18 @@
<template> <template>
<div class="c-labeled-input" <div
:title="options.title"> class="c-labeled-input"
<label :for="uid"> :title="options.title"
<div class="c-labeled-input__label">{{ options.label }}</div> >
</label> <label :for="uid">
<input :id="uid" <div class="c-labeled-input__label">{{ options.label }}</div>
:type="options.type" </label>
:value="options.value" <input
v-bind="options.attrs"/> :id="uid"
</div> :type="options.type"
:value="options.value"
v-bind="options.attrs"
>
</div>
</template> </template>
<script> <script>
@ -19,6 +23,7 @@ export default {
props: { props: {
options: { options: {
type: Object, type: Object,
required: true,
validator(value) { validator(value) {
return ['number', 'text'].indexOf(value.type) !== -1; return ['number', 'text'].indexOf(value.type) !== -1;
} }

View File

@ -1,24 +1,34 @@
<template> <template>
<div class="c-ctrl-wrapper"> <div class="c-ctrl-wrapper">
<div class="c-icon-button c-icon-button--menu" <div
:class="options.icon" class="c-icon-button c-icon-button--menu"
:title="options.title" :class="options.icon"
@click="toggle"> :title="options.title"
<div class="c-icon-button__label" @click="toggle"
v-if="options.label"> >
{{ options.label }} <div
</div> v-if="options.label"
</div> class="c-icon-button__label"
<div class="c-menu" v-if="open"> >
<ul> {{ options.label }}
<li v-for="option in options.options"
@click="onClick(option)"
:class="option.class">
{{ option.name }}
</li>
</ul>
</div> </div>
</div> </div>
<div
v-if="open"
class="c-menu"
>
<ul>
<li
v-for="(option, index) in options.options"
:key="index"
:class="option.class"
@click="onClick(option)"
>
{{ option.name }}
</li>
</ul>
</div>
</div>
</template> </template>
<script> <script>
@ -28,6 +38,7 @@ export default {
props: { props: {
options: { options: {
type: Object, type: Object,
required: true,
validator(value) { validator(value) {
// must pass valid options array. // must pass valid options array.
return Array.isArray(value.options) && return Array.isArray(value.options) &&

View File

@ -1,21 +1,30 @@
<template> <template>
<div class="c-ctrl-wrapper"> <div class="c-ctrl-wrapper">
<div class="c-icon-button c-icon-button--menu" <div
:class="[options.icon, {'c-click-icon--mixed': nonSpecific}]" class="c-icon-button c-icon-button--menu"
:title="options.title" :class="[options.icon, {'c-click-icon--mixed': nonSpecific}]"
@click="toggle"> :title="options.title"
<div class="c-button__label">{{ selectedName }}</div> @click="toggle"
</div> >
<div class="c-menu" v-if="open"> <div class="c-button__label">
<ul> {{ selectedName }}
<li v-for="option in options.options"
:key="option.value"
@click="select(option)">
{{ option.name || option.value }}
</li>
</ul>
</div> </div>
</div> </div>
<div
v-if="open"
class="c-menu"
>
<ul>
<li
v-for="option in options.options"
:key="option.value"
@click="select(option)"
>
{{ option.name || option.value }}
</li>
</ul>
</div>
</div>
</template> </template>
<script> <script>
@ -26,6 +35,7 @@ export default {
props: { props: {
options: { options: {
type: Object, type: Object,
required: true,
validator(value) { validator(value) {
// must pass valid options array. // must pass valid options array.
return Array.isArray(value.options) && return Array.isArray(value.options) &&
@ -33,14 +43,6 @@ export default {
} }
} }
}, },
methods: {
select(option) {
if (this.options.value === option.value) {
return;
}
this.$emit('change', option.value, this.options);
}
},
computed: { computed: {
selectedName() { selectedName() {
let selectedOption = this.options.options.filter((o) => o.value === this.options.value)[0]; let selectedOption = this.options.options.filter((o) => o.value === this.options.value)[0];
@ -53,6 +55,14 @@ export default {
nonSpecific() { nonSpecific() {
return this.options.nonSpecific === true; return this.options.nonSpecific === true;
} }
},
methods: {
select(option) {
if (this.options.value === option.value) {
return;
}
this.$emit('change', option.value, this.options);
}
} }
} }
</script> </script>

View File

@ -1,11 +1,14 @@
<template> <template>
<div class="c-toolbar__separator"></div> <div class="c-toolbar__separator"></div>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
options: Object options: {
type: Object,
required: true
}
} }
} }
</script> </script>

View File

@ -1,18 +1,20 @@
<template> <template>
<div class="c-ctrl-wrapper"> <div class="c-ctrl-wrapper">
<div class="c-icon-button" <div
:title="nextValue.title" class="c-icon-button"
:class="[nextValue.icon, {'c-icon-button--mixed': nonSpecific}]" :title="nextValue.title"
@click="cycle"> :class="[nextValue.icon, {'c-icon-button--mixed': nonSpecific}]"
</div> @click="cycle"
</div> ></div>
</div>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
options: { options: {
type: Object type: Object,
required: true
} }
}, },
computed: { computed: {