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,
"amd": true
},
"extends": "eslint:recommended",
"parser": "babel-eslint",
"globals": {
"_": "readonly"
},
"extends": [
"eslint:recommended",
"plugin:vue/recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "babel-eslint",
"allowImportExportEverywhere": true,
"ecmaVersion": 2015,
"ecmaFeatures": {
@ -58,7 +65,38 @@ module.exports = {
}
],
"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": [
{

View File

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

View File

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

View File

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

View File

@ -1,28 +1,57 @@
<template>
<div class="c-overlay">
<div class="c-overlay__blocker"
@click="destroy">
</div>
<div class="c-overlay__outer">
<button class="c-click-icon c-overlay__close-button icon-x-in-circle"
v-if="dismissable"
@click="destroy">
<div class="c-overlay">
<div
class="c-overlay__blocker"
@click="destroy"
></div>
<div class="c-overlay__outer">
<button
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>
<div class="c-overlay__contents" ref="element" tabindex="0"></div>
<div class="c-overlay__button-bar" v-if="buttons">
<button class="c-button"
tabindex="0"
ref="buttons"
v-for="(button, index) in buttons"
:key="index"
@focus="focusIndex=index"
:class="{'c-button--major': focusIndex===index}"
@click="buttonClickHandler(button.callback)">
{{button.label}}
<div
ref="element"
class="c-overlay__contents"
tabindex="0"
></div>
<div
v-if="buttons"
class="c-overlay__button-bar"
>
<button
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>
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
@ -138,7 +167,7 @@
box-shadow: rgba(black, 0.5) 0 2px 25px;
}
}
.l-overlay-fullscreen {
// Used by About > Licenses display
.c-overlay__outer {
@ -174,50 +203,50 @@
</style>
<script>
export default {
data: function () {
return {
focusIndex: -1
};
},
inject: ['dismiss', 'element', 'buttons', 'dismissable'],
mounted() {
const element = this.$refs.element;
element.appendChild(this.element);
const elementForFocus = this.getElementForFocus() || element;
this.$nextTick(() => {
elementForFocus.focus();
});
},
methods: {
destroy: function () {
if (this.dismissable) {
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];
export default {
data: function () {
return {
focusIndex: -1
};
},
inject: ['dismiss', 'element', 'buttons', 'dismissable'],
mounted() {
const element = this.$refs.element;
element.appendChild(this.element);
const elementForFocus = this.getElementForFocus() || element;
this.$nextTick(() => {
elementForFocus.focus();
});
},
methods: {
destroy: function () {
if (this.dismissable) {
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];
}
}
}
</script>

View File

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

View File

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

View File

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

View File

@ -22,13 +22,13 @@
*****************************************************************************/
<template>
<tr @contextmenu.prevent="showContextMenu">
<td>{{name}}</td>
<td>{{timestamp}}</td>
<td :class="valueClass">
{{value}}
</td>
</tr>
<tr @contextmenu.prevent="showContextMenu">
<td>{{ name }}</td>
<td>{{ timestamp }}</td>
<td :class="valueClass">
{{ value }}
</td>
</tr>
</template>
<style lang="scss">
@ -44,7 +44,12 @@ const CONTEXT_MENU_ACTIONS = [
export default {
inject: ['openmct', 'objectPath'],
props: ['domainObject'],
props: {
domainObject: {
type: Object,
required: true
}
},
data() {
let currentObjectPath = this.objectPath.slice();
currentObjectPath.unshift(this.domainObject);
@ -57,48 +62,16 @@ export default {
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() {
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.limitEvaluator = openmct
this.limitEvaluator = this.openmct
.telemetry
.limitEvaluator(this.domainObject);
this.stopWatchingMutation = openmct
this.stopWatchingMutation = this.openmct
.objects
.observe(
this.domainObject,
@ -129,6 +102,38 @@ export default {
this.stopWatchingMutation();
this.unsubscribe();
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>

View File

@ -30,17 +30,16 @@
</tr>
</thead>
<tbody>
<lad-row
<lad-row
v-for="item in items"
:key="item.key"
:domainObject="item.domainObject">
</lad-row>
:domain-object="item.domainObject"
/>
</tbody>
</table>
</template>
<script>
import lodash from 'lodash';
import LadRow from './LADRow.vue';
export default {
@ -53,6 +52,18 @@ export default {
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: {
addItem(domainObject) {
let item = {};
@ -72,18 +83,6 @@ export default {
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>

View File

@ -21,29 +21,34 @@
*****************************************************************************/
<template>
<table class="c-table c-lad-table">
<thead>
<tr>
<th>Name</th>
<th>Timestamp</th>
<th>Value</th>
<table class="c-table c-lad-table">
<thead>
<tr>
<th>Name</th>
<th>Timestamp</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>
</thead>
<tbody>
<template
v-for="primary in primaryTelemetryObjects">
<tr class="c-table__group-header"
:key="primary.key">
<td colspan="10">{{primary.domainObject.name}}</td>
</tr>
<lad-row
v-for="secondary in secondaryTelemetryObjects[primary.key]"
:key="secondary.key"
:domainObject="secondary.domainObject">
</lad-row>
</template>
</tbody>
</table>
<lad-row
v-for="secondary in secondaryTelemetryObjects[primary.key]"
:key="secondary.key"
:domain-object="secondary.domainObject"
/>
</template>
</tbody>
</table>
</template>
<style lang="scss">
@ -51,10 +56,9 @@
</style>
<script>
import lodash from 'lodash';
import LadRow from './LADRow.vue';
import LadRow from './LADRow.vue';
export default {
export default {
inject: ['openmct', 'domainObject'],
components: {
LadRow
@ -66,6 +70,22 @@
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: {
addPrimary(domainObject) {
let primary = {};
@ -75,7 +95,7 @@
this.$set(this.secondaryTelemetryObjects, primary.key, []);
this.primaryTelemetryObjects.push(primary);
let composition = openmct.composition.get(primary.domainObject),
let composition = this.openmct.composition.get(primary.domainObject),
addCallback = this.addSecondary(primary),
removeCallback = this.removeSecondary(primary);
@ -88,7 +108,7 @@
removePrimary(identifier) {
let index = _.findIndex(this.primaryTelemetryObjects, (primary) => this.openmct.objects.makeKeyString(identifier) === primary.key),
primary = this.primaryTelemetryObjects[index];
this.$set(this.secondaryTelemetryObjects, primary.key, undefined);
this.primaryTelemetryObjects.splice(index,1);
primary = undefined;
@ -107,7 +127,7 @@
let array = this.secondaryTelemetryObjects[primary.key];
array.push(secondary);
this.$set(this.secondaryTelemetryObjects, primary.key, array);
}
},
@ -121,23 +141,6 @@
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>

View File

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

View File

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

View File

@ -21,70 +21,78 @@
*****************************************************************************/
<template>
<div class="c-properties" v-if="isEditing">
<div class="c-properties__header">Alphanumeric Format</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"
@change="formatTelemetry"
:value="telemetryFormat"
:placeholder="nonMixedFormat ? '' : 'Mixed'"
>
</div>
</li>
</ul>
<div
v-if="isEditing"
class="c-properties"
>
<div class="c-properties__header">
Alphanumeric Format
</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>
<script>
export default {
inject: ['openmct'],
data() {
let selectionPath = this.openmct.selection.get()[0];
return {
isEditing: this.openmct.editor.isEditing(),
telemetryFormat: undefined,
nonMixedFormat: false
export default {
inject: ['openmct'],
data() {
return {
isEditing: this.openmct.editor.isEditing(),
telemetryFormat: undefined,
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;
this.nonMixedFormat = selection.every(selectionPath => {
return selectionPath[0].context.layoutItem.format === format;
});
let format = selection[0][0].context.layoutItem.format;
this.nonMixedFormat = selection.every(selectionPath => {
return selectionPath[0].context.layoutItem.format === 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);
this.telemetryFormat = this.nonMixedFormat ? format : '';
}
}
}
</script>
</script>

View File

@ -21,15 +21,18 @@
*****************************************************************************/
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<div class="c-box-view"
:style="style">
</div>
</layout-frame>
</template>
<layout-frame
:item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
<div
class="c-box-view"
:style="style"
></div>
</layout-frame>
</template>
<style lang="scss">
@import '~styles/sass-base';
@ -44,60 +47,70 @@
}
</style>
<script>
import LayoutFrame from './LayoutFrame.vue'
<script>
import LayoutFrame from './LayoutFrame.vue'
export default {
makeDefinition() {
export default {
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 {
fill: '#717171',
stroke: 'transparent',
x: 1,
y: 1,
width: 10,
height: 5
backgroundColor: this.item.fill,
border: '1px solid ' + this.item.stroke
};
},
inject: ['openmct'],
components: {
LayoutFrame
},
props: {
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;
}
},
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();
}
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,42 +21,49 @@
*****************************************************************************/
<template>
<div class="l-layout"
@dragover="handleDragOver"
@click.capture="bypassSelection"
@drop="handleDrop"
:class="{
'is-multi-selected': selectedLayoutItems.length > 1
}">
<!-- Background grid -->
<div class="l-layout__grid-holder c-grid">
<div class="c-grid__x l-grid l-grid-x"
v-if="gridSize[0] >= 3"
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
</div>
<div class="c-grid__y l-grid l-grid-y"
v-if="gridSize[1] >= 3"
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
</div>
<component v-for="(item, index) in layoutItems"
:is="item.type"
:item="item"
:key="item.id"
: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
class="l-layout"
:class="{
'is-multi-selected': selectedLayoutItems.length > 1
}"
@dragover="handleDragOver"
@click.capture="bypassSelection"
@drop="handleDrop"
>
<!-- Background grid -->
<div class="l-layout__grid-holder c-grid">
<div
v-if="gridSize[0] >= 3"
class="c-grid__x l-grid l-grid-x"
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]"
></div>
<div
v-if="gridSize[1] >= 3"
class="c-grid__y l-grid l-grid-y"
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"
></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>
<style lang="scss">
@ -135,452 +142,455 @@
</style>
<script>
import uuid from 'uuid';
import uuid from 'uuid';
import SubobjectView from './SubobjectView.vue'
import TelemetryView from './TelemetryView.vue'
import BoxView from './BoxView.vue'
import TextView from './TextView.vue'
import LineView from './LineView.vue'
import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue'
import SubobjectView from './SubobjectView.vue'
import TelemetryView from './TelemetryView.vue'
import BoxView from './BoxView.vue'
import TextView from './TextView.vue'
import LineView from './LineView.vue'
import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue'
const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView,
'telemetry-view': TelemetryView,
'box-view': BoxView,
'line-view': LineView,
'text-view': TextView,
'image-view': ImageView
};
const ORDERS = {
top: Number.POSITIVE_INFINITY,
up: 1,
down: -1,
bottom: Number.NEGATIVE_INFINITY
};
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView,
'telemetry-view': TelemetryView,
'box-view': BoxView,
'line-view': LineView,
'text-view': TextView,
'image-view': ImageView
};
const ORDERS = {
top: Number.POSITIVE_INFINITY,
up: 1,
down: -1,
bottom: Number.NEGATIVE_INFINITY
};
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
let components = ITEM_TYPE_VIEW_MAP;
components['edit-marquee'] = EditMarquee;
let components = ITEM_TYPE_VIEW_MAP;
components['edit-marquee'] = EditMarquee;
function getItemDefinition(itemType, ...options) {
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
function getItemDefinition(itemType, ...options) {
let itemView = ITEM_TYPE_VIEW_MAP[itemType];
if (!itemView) {
throw `Invalid itemType: ${itemType}`;
}
return itemView.makeDefinition(...options);
if (!itemView) {
throw `Invalid itemType: ${itemType}`;
}
export default {
data() {
let domainObject = JSON.parse(JSON.stringify(this.domainObject));
return {
internalDomainObject: domainObject,
initSelectIndex: undefined,
selection: []
};
return itemView.makeDefinition(...options);
}
export default {
components: components,
props: {
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: {
gridSize() {
return this.internalDomainObject.configuration.layoutGrid;
},
layoutItems() {
return this.internalDomainObject.configuration.items;
},
selectedLayoutItems() {
return this.layoutItems.filter(item => {
return this.itemIsInCurrentSelection(item);
});
},
showMarquee() {
let selectionPath = this.selection[0];
let singleSelectedLine = this.selection.length === 1 &&
layoutItems() {
return this.internalDomainObject.configuration.items;
},
selectedLayoutItems() {
return this.layoutItems.filter(item => {
return this.itemIsInCurrentSelection(item);
});
},
showMarquee() {
let selectionPath = this.selection[0];
let singleSelectedLine = this.selection.length === 1 &&
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'],
props: ['domainObject'],
components: components,
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;
}
},
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;
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 itemYInMarqueeSpaceAfterScale = Math.round(itemYInMarqueeSpace * scaleHeight);
item.y = itemYInMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
let itemYInMarqueeSpace = item.y - marqueeStart.y;
let itemYInMarqueeSpaceAfterScale = Math.round(itemYInMarqueeSpace * scaleHeight);
item.y = itemYInMarqueeSpaceAfterScale + marqueeOffset.y + marqueeStart.y;
if (item.x2) {
let itemX2InMarqueeSpace = item.x2 - marqueeStart.x;
let itemX2InMarqueeSpaceAfterScale = Math.round(itemX2InMarqueeSpace * scaleWidth);
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];
}
if (item.x2) {
let itemX2InMarqueeSpace = item.x2 - marqueeStart.x;
let itemX2InMarqueeSpaceAfterScale = Math.round(itemX2InMarqueeSpace * scaleWidth);
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;
});
}
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];
}
} else {
item.x = startingX + gridDelta[0];
}
}
if (this.startingMinY + gridDelta[1] >= 0) {
if (item.y2 !== undefined) {
if (this.startingMinY2 + gridDelta[1] >= 0) {
item.y = startingY + gridDelta[1];
}
} else {
if (this.startingMinY + gridDelta[1] >= 0) {
if (item.y2 !== undefined) {
if (this.startingMinY2 + gridDelta[1] >= 0) {
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 {
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
// potentially allow drop. Display layouts allow drag drop of duplicate telemetry objects.
if (this.containsObject(draggedKeyString)){
$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;
item.y = startingY + gridDelta[1];
}
}
let keyString = this.openmct.objects.makeKeyString(item.identifier);
if (item.x2 !== undefined && this.startingMinX2 + gridDelta[0] >= 0 && this.startingMinX + gridDelta[0] >= 0) {
item.x2 = startingX2 + gridDelta[0];
}
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);
if (item.y2 !== undefined && this.startingMinY2 + gridDelta[1] >= 0 && this.startingMinY + gridDelta[1] >= 0) {
item.y2 = startingY2 + gridDelta[1];
}
},
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();
endMove() {
this.mutate('configuration.items', this.layoutItems);
this.initialPositions = undefined;
this.startingMinX = undefined;
this.startingMinY = undefined;
this.startingMinX2 = undefined;
this.startingMinY2 = undefined;
},
destroyed: function () {
this.openmct.selection.off('change', this.setSelection);
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
this.unlisten();
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
// potentially allow drop. Display layouts allow drag drop of duplicate telemetry objects.
if (this.containsObject(draggedKeyString)) {
$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 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>

View File

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

View File

@ -21,15 +21,18 @@
*****************************************************************************/
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<div class="c-image-view"
:style="style">
</div>
</layout-frame>
</template>
<layout-frame
:item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
<div
class="c-image-view"
:style="style"
></div>
</layout-frame>
</template>
<style lang="scss">
@import '~styles/sass-base';
@ -46,59 +49,70 @@
}
</style>
<script>
import LayoutFrame from './LayoutFrame.vue'
<script>
import LayoutFrame from './LayoutFrame.vue'
export default {
makeDefinition(openmct, gridSize, element) {
export default {
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 {
stroke: 'transparent',
x: 1,
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();
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();
}
}
</script>
}
</script>

View File

@ -21,19 +21,21 @@
*****************************************************************************/
<template>
<div class="l-layout__frame c-frame"
:class="{
'no-frame': !item.hasFrame,
'u-inspectable': inspectable
}"
:style="style">
<div
class="l-layout__frame c-frame"
:class="{
'no-frame': !item.hasFrame,
'u-inspectable': inspectable
}"
:style="style"
>
<slot></slot>
<slot></slot>
<div class="c-frame-edit__move"
@mousedown="startMove([1,1], [0,0], $event)">
</div>
</div>
<div
class="c-frame-edit__move"
@mousedown="startMove([1,1], [0,0], $event)"
></div>
</div>
</template>
<style lang="scss">
@ -173,73 +175,81 @@
</style>
<script>
import LayoutDrag from './../LayoutDrag'
import LayoutDrag from './../LayoutDrag'
export default {
inject: ['openmct'],
props: {
item: Object,
gridSize: Array
export default {
inject: ['openmct'],
props: {
item: {
type: Object,
required: true
},
computed: {
style() {
let {x, y, width, height} = this.item;
return {
left: (this.gridSize[0] * x) + 'px',
top: (this.gridSize[1] * y) + 'px',
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';
gridSize: {
type: Array,
required: true,
validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number')
}
},
computed: {
style() {
let {x, y, width, height} = this.item;
return {
left: (this.gridSize[0] * x) + 'px',
top: (this.gridSize[1] * y) + 'px',
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: {
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));
}
},
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]);
});
}
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>

View File

@ -21,244 +21,253 @@
*****************************************************************************/
<template>
<div class="l-layout__frame c-frame no-frame"
:style="style">
<svg width="100%" height="100%">
<line v-bind="linePosition"
:stroke="item.stroke"
stroke-width="2">
</line>
</svg>
<div
class="l-layout__frame c-frame no-frame"
:style="style"
>
<svg
width="100%"
height="100%"
>
<line
v-bind="linePosition"
:stroke="item.stroke"
stroke-width="2"
/>
</svg>
<div class="c-frame-edit__move"
@mousedown="startDrag($event)"></div>
<div class="c-frame-edit" v-if="showFrameEdit">
<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
class="c-frame-edit__move"
@mousedown="startDrag($event)"
></div>
<div
v-if="showFrameEdit"
class="c-frame-edit"
>
<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>
</template>
<script>
<script>
const START_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--sw',
2: 'c-frame-edit__handle--se',
3: 'c-frame-edit__handle--ne',
4: 'c-frame-edit__handle--nw'
};
const START_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--sw',
2: 'c-frame-edit__handle--se',
3: 'c-frame-edit__handle--ne',
4: 'c-frame-edit__handle--nw'
};
const END_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--ne',
2: 'c-frame-edit__handle--nw',
3: 'c-frame-edit__handle--sw',
4: 'c-frame-edit__handle--se'
};
const END_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--ne',
2: 'c-frame-edit__handle--nw',
3: 'c-frame-edit__handle--sw',
4: 'c-frame-edit__handle--se'
};
export default {
makeDefinition() {
export default {
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 {
x: 5,
y: 10,
x2: 10,
y2: 5,
stroke: '#717171'
left: `${left}px`,
top: `${top}px`,
width: `${width}px`,
height: `${height}px`
};
},
inject: ['openmct'],
props: {
item: Object,
gridSize: Array,
initSelect: Boolean,
index: Number,
multiSelect: Boolean
startHandleClass() {
return START_HANDLE_QUADRANTS[this.vectorQuadrant];
},
data() {
return {
dragPosition: undefined,
dragging: undefined,
selection: []
};
endHandleClass() {
return END_HANDLE_QUADRANTS[this.vectorQuadrant];
},
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 {
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;
}
vectorQuadrant() {
let {x, y, x2, y2} = this.position;
if (x2 > x) {
if (y2 < y) {
return 2;
}
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 1;
}
return 4;
}
if (y2 < y) {
return 2;
}
return 3;
},
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
linePosition() {
return this.vectorQuadrant % 2 !== 0
// odd vectorQuadrant slopes up
? {
x1: '0%',
y1: '100%',
x2: '100%',
y2: '0%'
}
// even vectorQuadrant slopes down
: {
x1: '0%',
y1: '0%',
x2: '100%',
y2: '100%'
};
event.preventDefault();
},
continueDrag(event) {
event.preventDefault();
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;
}
},
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;
}
},
watch: {
index(newIndex) {
if (!this.context) {
return;
}
this.context.index = newIndex;
}
},
mounted() {
this.openmct.selection.on('change', this.setSelection);
this.context = {
layoutItem: this.item,
index: this.index
this.context.index = newIndex;
}
},
mounted() {
this.openmct.selection.on('change', this.setSelection);
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();
}
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(
this.$el, this.context, this.initSelect);
event.preventDefault();
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
continueDrag(event) {
event.preventDefault();
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.
*****************************************************************************/
<template>
<layout-frame :item="item"
:grid-size="gridSize"
:title="domainObject && domainObject.name"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<object-frame v-if="domainObject"
:domain-object="domainObject"
:object-path="currentObjectPath"
:has-frame="item.hasFrame"
:show-edit-view="false"
ref="objectFrame">
</object-frame>
</layout-frame>
<layout-frame
:item="item"
:grid-size="gridSize"
:title="domainObject && domainObject.name"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
<object-frame
v-if="domainObject"
ref="objectFrame"
:domain-object="domainObject"
:object-path="currentObjectPath"
:has-frame="item.hasFrame"
:show-edit-view="false"
/>
</layout-frame>
</template>
<script>
import ObjectFrame from '../../../ui/components/ObjectFrame.vue'
import LayoutFrame from './LayoutFrame.vue'
import ObjectFrame from '../../../ui/components/ObjectFrame.vue'
import LayoutFrame from './LayoutFrame.vue'
const MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_DIMENSIONS = [10, 10],
DEFAULT_POSITION = [1, 1],
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
const MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_DIMENSIONS = [10, 10],
DEFAULT_POSITION = [1, 1],
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
function getDefaultDimensions(gridSize) {
return MINIMUM_FRAME_SIZE.map((min, index) => {
return Math.max(
Math.ceil(min / gridSize[index]),
DEFAULT_DIMENSIONS[index]
);
});
}
function getDefaultDimensions(gridSize) {
return MINIMUM_FRAME_SIZE.map((min, index) => {
return Math.max(
Math.ceil(min / gridSize[index]),
DEFAULT_DIMENSIONS[index]
);
});
}
function hasFrameByDefault(type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
}
function hasFrameByDefault(type) {
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
}
export default {
makeDefinition(openmct, gridSize, domainObject, position) {
let defaultDimensions = getDefaultDimensions(gridSize);
position = position || DEFAULT_POSITION;
export default {
makeDefinition(openmct, gridSize, domainObject, position) {
let defaultDimensions = getDefaultDimensions(gridSize);
position = position || DEFAULT_POSITION;
return {
width: defaultDimensions[0],
height: defaultDimensions[1],
x: position[0],
y: position[1],
identifier: domainObject.identifier,
hasFrame: hasFrameByDefault(domainObject.type)
};
return {
width: defaultDimensions[0],
height: defaultDimensions[1],
x: position[0],
y: position[1],
identifier: domainObject.identifier,
hasFrame: hasFrameByDefault(domainObject.type)
};
},
inject: ['openmct', 'objectPath'],
components: {
ObjectFrame,
LayoutFrame
},
props: {
item: {
type: Object,
required: true
},
inject: ['openmct', 'objectPath'],
props: {
item: Object,
gridSize: Array,
initSelect: Boolean,
index: Number
gridSize: {
type: Array,
required: true,
validator: (arr) => arr && arr.length === 2
&& arr.every(el => typeof el === 'number')
},
data() {
return {
domainObject: undefined,
currentObjectPath: []
initSelect: Boolean,
index: {
type: Number,
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;
}
},
methods: {
setObject(domainObject) {
this.domainObject = domainObject;
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
this.$nextTick(function () {
let childContext = this.$refs.objectFrame.getSelectionContext();
childContext.item = domainObject;
childContext.layoutItem = this.item;
childContext.index = this.index;
this.context = childContext;
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
});
}
},
mounted() {
this.openmct.objects.get(this.item.identifier)
.then(this.setObject);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
this.context.index = newIndex;
}
},
mounted() {
this.openmct.objects.get(this.item.identifier)
.then(this.setObject);
},
destroyed() {
if (this.removeSelectable) {
this.removeSelectable();
}
},
methods: {
setObject(domainObject) {
this.domainObject = domainObject;
this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice());
this.$nextTick(function () {
let childContext = this.$refs.objectFrame.getSelectionContext();
childContext.item = domainObject;
childContext.layoutItem = this.item;
childContext.index = this.index;
this.context = childContext;
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
});
}
}
}
</script>

View File

@ -20,29 +20,41 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<div class="c-telemetry-view"
:style="styleObject"
v-if="domainObject"
@contextmenu.prevent="showContextMenu">
<div v-if="showLabel"
class="c-telemetry-view__label">
<div class="c-telemetry-view__label-text">{{ domainObject.name }}</div>
</div>
<div v-if="showValue"
:title="fieldName"
class="c-telemetry-view__value"
:class="[telemetryClass]">
<div class="c-telemetry-view__value-text">{{ telemetryValue }}</div>
<template>
<layout-frame
:item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
<div
v-if="domainObject"
class="c-telemetry-view"
:style="styleObject"
@contextmenu.prevent="showContextMenu"
>
<div
v-if="showLabel"
class="c-telemetry-view__label"
>
<div class="c-telemetry-view__label-text">
{{ domainObject.name }}
</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">
@import '~styles/sass-base';
@ -78,189 +90,200 @@
}
</style>
<script>
import LayoutFrame from './LayoutFrame.vue'
import printj from 'printj'
<script>
import LayoutFrame from './LayoutFrame.vue'
import printj from 'printj'
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
DEFAULT_POSITION = [1, 1],
CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
DEFAULT_POSITION = [1, 1],
CONTEXT_MENU_ACTIONS = ['viewHistoricalData'];
export default {
makeDefinition(openmct, gridSize, domainObject, position) {
let metadata = openmct.telemetry.getMetadata(domainObject);
position = position || DEFAULT_POSITION;
export default {
makeDefinition(openmct, gridSize, domainObject, position) {
let metadata = openmct.telemetry.getMetadata(domainObject);
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 {
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"
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;
}
},
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'
};
},
inject: ['openmct', 'objectPath'],
props: {
item: Object,
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);
this.openmct.telemetry.request(this.domainObject, options)
.then(data => {
if (data.length > 0) {
this.updateView(data[data.length - 1]);
}
}.bind(this));
},
updateView(datum) {
this.datum = datum;
},
removeSubscription() {
if (this.subscription) {
this.subscription();
this.subscription = undefined;
});
},
subscribeToObject() {
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
if (this.openmct.time.clock() !== undefined) {
this.updateView(datum);
}
},
refreshData(bounds, isTick) {
if (!isTick) {
this.datum = undefined;
this.requestHistoricalData(this.domainObject);
}
},
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.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);
}.bind(this));
},
updateView(datum) {
this.datum = datum;
},
removeSubscription() {
if (this.subscription) {
this.subscription();
this.subscription = undefined;
}
},
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();
refreshData(bounds, isTick) {
if (!isTick) {
this.datum = undefined;
this.requestHistoricalData(this.domainObject);
}
},
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.
*****************************************************************************/
<template>
<layout-frame :item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')">
<div class="c-text-view"
:style="style">
{{ item.text }}
</div>
</layout-frame>
</template>
<template>
<layout-frame
:item="item"
:grid-size="gridSize"
@move="(gridDelta) => $emit('move', gridDelta)"
@endMove="() => $emit('endMove')"
>
<div
class="c-text-view"
:style="style"
>
{{ item.text }}
</div>
</layout-frame>
</template>
<style lang="scss">
@import '~styles/sass-base';
@ -46,65 +50,75 @@
}
</style>
<script>
import LayoutFrame from './LayoutFrame.vue'
<script>
import LayoutFrame from './LayoutFrame.vue'
export default {
makeDefinition(openmct, gridSize, element) {
export default {
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 {
fill: 'transparent',
stroke: 'transparent',
size: '13px',
color: '',
x: 1,
y: 1,
width: 10,
height: 5,
text: element.text
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
fontSize: this.item.size
};
},
inject: ['openmct'],
props: {
item: Object,
gridSize: Array,
index: Number,
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;
}
},
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();
}
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

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

View File

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

View File

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

View File

@ -1,50 +1,62 @@
<template>
<li class="c-tree__item-h">
<div class="c-tree__item menus-to-left"
@click="toggleExpanded">
<div class="c-filter-tree-item__filter-indicator"
:class="{'icon-filter': hasActiveFilters }"></div>
<span 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"
:class="objectCssClass">
</div>
<div class="c-object-label__name flex-elem grows">{{ filterObject.name }}</div>
<li class="c-tree__item-h">
<div
class="c-tree__item menus-to-left"
@click="toggleExpanded"
>
<div
class="c-filter-tree-item__filter-indicator"
:class="{'icon-filter': hasActiveFilters }"
></div>
<span
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"
:class="objectCssClass"
></div>
<div class="c-object-label__name flex-elem grows">
{{ filterObject.name }}
</div>
</div>
</div>
</div>
<div v-if="expanded">
<ul class="c-properties">
<div class="c-properties__label span-all"
v-if="!isEditing && persistedFilters.useGlobal">
Uses global filter
</div>
<div v-if="expanded">
<ul class="c-properties">
<div
v-if="!isEditing && persistedFilters.useGlobal"
class="c-properties__label span-all"
>
Uses global filter
</div>
<div class="c-properties__label span-all"
v-if="isEditing">
<toggle-switch
:id="keyString"
@change="useGlobalFilter"
:checked="persistedFilters.useGlobal">
</toggle-switch>
Use global filter
</div>
<filter-field
v-if="(!persistedFilters.useGlobal && !isEditing) || isEditing"
v-for="metadatum in filterObject.metadataWithFilters"
:key="metadatum.key"
:filterField="metadatum"
:useGlobal="persistedFilters.useGlobal"
:persistedFilters="updatedFilters[metadatum.key]"
@filterSelected="updateFiltersWithSelectedValue"
@filterTextValueChanged="updateFiltersWithTextValue">
</filter-field>
</ul>
</div>
</li>
<div
v-if="isEditing"
class="c-properties__label span-all"
>
<toggle-switch
:id="keyString"
:checked="persistedFilters.useGlobal"
@change="useGlobalFilter"
/>
Use global filter
</div>
<filter-field
v-for="metadatum in activeFilters"
:key="metadatum.key"
:filter-field="metadatum"
:use-global="persistedFilters.useGlobal"
:persisted-filters="updatedFilters[metadatum.key]"
@filterSelected="updateFiltersWithSelectedValue"
@filterTextValueChanged="updateFiltersWithTextValue"
/>
</ul>
</div>
</li>
</template>
<script>
@ -58,7 +70,10 @@ export default {
ToggleSwitch
},
props: {
filterObject: Object,
filterObject: {
type: Object,
required: true
},
persistedFilters: {
type: Object,
default: () => {
@ -74,6 +89,23 @@ export default {
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: {
persistedFilters: {
handler: function checkFilters(newpersistedFilters) {
@ -82,13 +114,14 @@ export default {
deep: true
}
},
computed: {
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));
});
}
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);
},
methods: {
toggleExpanded() {
@ -128,16 +161,7 @@ export default {
},
toggleIsEditing(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>

View File

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

View File

@ -1,29 +1,40 @@
<template>
<li class="c-tree__item-h">
<div class="c-tree__item menus-to-left"
@click="toggleExpanded">
<div class="c-filter-tree-item__filter-indicator"
:class="{'icon-filter': hasActiveGlobalFilters }"></div>
<span 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>
<li class="c-tree__item-h">
<div
class="c-tree__item menus-to-left"
@click="toggleExpanded"
>
<div
class="c-filter-tree-item__filter-indicator"
:class="{'icon-filter': hasActiveGlobalFilters }"
></div>
<span
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>
<ul class="c-properties" v-if="expanded">
<filter-field
v-for="metadatum in globalMetadata"
:key="metadatum.key"
:filterField="metadatum"
:persistedFilters="updatedFilters[metadatum.key]"
@filterSelected="updateFiltersWithSelectedValue"
@filterTextValueChanged="updateFiltersWithTextValue">
</filter-field>
</ul>
</li>
</div>
<ul
v-if="expanded"
class="c-properties"
>
<filter-field
v-for="metadatum in globalMetadata"
:key="metadatum.key"
:filter-field="metadatum"
:persisted-filters="updatedFilters[metadatum.key]"
@filterSelected="updateFiltersWithSelectedValue"
@filterTextValueChanged="updateFiltersWithTextValue"
/>
</ul>
</li>
</template>
<style lang="scss">
@ -59,77 +70,80 @@
</style>
<script>
import FilterField from './FilterField.vue';
import FilterField from './FilterField.vue';
export default {
inject: ['openmct'],
components: {
FilterField
export default {
inject: ['openmct'],
components: {
FilterField
},
props: {
globalMetadata: {
type: Object,
required: true
},
props: {
globalMetadata: Object,
globalFilters: {
type: Object,
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);
globalFilters: {
type: Object,
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);
}
}
}
</script>

View File

@ -21,62 +21,65 @@
*****************************************************************************/
<template>
<div class="c-fl-container"
:style="[{'flex-basis': sizeString}]"
:class="{'is-empty': !frames.length}">
<div class="c-fl-container__header"
v-show="isEditing"
draggable="true"
@dragstart="startContainerDrag">
<span class="c-fl-container__size-indicator">{{ sizeString }}</span>
</div>
<drop-hint
class="c-fl-frame__drop-hint"
: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
class="c-fl-container"
:style="[{'flex-basis': sizeString}]"
:class="{'is-empty': !frames.length}"
>
<div
v-show="isEditing"
class="c-fl-container__header"
draggable="true"
@dragstart="startContainerDrag"
>
<span class="c-fl-container__size-indicator">{{ sizeString }}</span>
</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>
<script>
import FrameComponent from './frame.vue';
import Frame from '../utils/frame';
import ResizeHandle from './resizeHandle.vue';
import DropHint from './dropHint.vue';
@ -84,12 +87,26 @@ const MIN_FRAME_SIZE = 5;
export default {
inject:['openmct'],
props: ['container', 'index', 'rowsLayout', 'isEditing'],
components: {
FrameComponent,
ResizeHandle,
DropHint
},
props: {
container: {
type: Object,
required: true
},
index: {
type: Number,
required: true
},
rowsLayout: Boolean,
isEditing: {
type: Boolean,
default: false
}
},
computed: {
frames() {
return this.container.frames;
@ -98,6 +115,19 @@ export default {
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: {
allowDrop(event, index) {
if (event.dataTransfer.types.includes('openmct/domain-object-path')) {
@ -131,7 +161,7 @@ export default {
insertIndex
);
return;
};
}
// move frame.
let frameId = event.dataTransfer.getData('frameid');
let containerIndex = Number(event.dataTransfer.getData('containerIndex'));
@ -182,19 +212,6 @@ export default {
startContainerDrag(event) {
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>

View File

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

View File

@ -21,72 +21,74 @@
*****************************************************************************/
<template>
<div class="c-fl">
<div
id="js-fl-drag-ghost"
class="c-fl__drag-ghost">
</div>
<div class="c-fl">
<div
id="js-fl-drag-ghost"
class="c-fl__drag-ghost"
></div>
<div class="c-fl__empty"
v-if="areAllContainersEmpty()">
<span class="c-fl__empty-message">This Flexible Layout is currently empty</span>
</div>
<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
v-if="areAllContainersEmpty()"
class="c-fl__empty"
>
<span class="c-fl__empty-message">This Flexible Layout is currently empty</span>
</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>
<style lang="scss">
@import '~styles/sass-base';
@mixin containerGrippy($headerSize, $dir) {
position: absolute;
$h: 6px;
@ -465,15 +467,15 @@ export default {
ResizeHandle,
DropHint
},
props: {
isEditing: Boolean
},
data() {
return {
domainObject: this.layoutObject,
newFrameLocation: []
}
},
props: {
isEditing: Boolean
},
computed: {
layoutDirectionStr() {
if (this.rowsLayout) {
@ -489,9 +491,24 @@ export default {
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: {
areAllContainersEmpty() {
return !!!this.containers.filter(container => container.frames.length).length;
return !this.containers.filter(container => container.frames.length).length;
},
addContainer() {
let container = new Container();
@ -515,7 +532,7 @@ export default {
/*
add a container when there are no containers in the FL,
to prevent user from not being able to add a frame via
drag and drop.
drag and drop.
*/
if (this.containers.length === 0) {
this.containers.push(new Container(100));
@ -589,7 +606,7 @@ export default {
return containerPos !== index && (containerPos - 1) !== index
}
},
persist(index){
persist(index) {
if (index) {
this.openmct.objects.mutate(this.domainObject, `configuration.containers[${index}]`, this.containers[index]);
} else {
@ -650,28 +667,13 @@ export default {
this.containers.forEach(container => {
container.frames = container.frames.filter(frame => {
let frameIdentifier = this.openmct.objects.makeKeyString(frame.domainObjectIdentifier);
return removeIdentifier !== frameIdentifier;
});
});
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>

View File

@ -21,56 +21,89 @@
*****************************************************************************/
<template>
<div class="c-fl-frame"
:style="{
'flex-basis': `${frame.size}%`
}">
<div
class="c-fl-frame"
: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"
draggable="true"
@dragstart="initDrag"
ref="frame">
<object-frame
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
v-if="isEditing"
v-show="frame.size && frame.size < 100"
class="c-fl-frame__size-indicator"
>
{{ frame.size }}%
</div>
</div>
</div>
</template>
<script>
import ResizeHandle from './resizeHandle.vue';
import ObjectFrame from '../../../ui/components/ObjectFrame.vue';
export default {
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() {
return {
domainObject: undefined,
objectPath: undefined
}
},
components: {
ResizeHandle,
ObjectFrame
},
computed: {
hasFrame() {
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: {
setDomainObject(object) {
this.domainObject = object;
@ -92,7 +125,7 @@ export default {
initDrag(event) {
let type = this.openmct.types.get(this.domainObject.type),
iconClass = type.definition ? type.definition.cssClass : 'icon-object-unknown';
if (this.dragGhost) {
let originalClassName = this.dragGhost.classList[0];
this.dragGhost.className = '';
@ -105,20 +138,6 @@ export default {
event.dataTransfer.setData('frameid', this.frame.id);
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>

View File

@ -21,22 +21,46 @@
*****************************************************************************/
<template>
<div class="c-fl-frame__resize-handle"
:class="[orientation]"
v-show="isEditing && !isDragging"
@mousedown="mousedown">
</div>
<div
v-show="isEditing && !isDragging"
class="c-fl-frame__resize-handle"
:class="[orientation]"
@mousedown="mousedown"
></div>
</template>
<script>
export default {
props: ['orientation', 'index', 'isEditing'],
props: {
orientation: {
type: String,
required: true
},
index: {
type: Number,
required: true
},
isEditing: {
type: Boolean,
default: false
}
},
data() {
return {
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: {
mousedown(event) {
event.preventDefault();
@ -75,16 +99,6 @@ export default {
unsetDragging(event) {
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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,31 @@
<template>
<tr class="c-list-item"
:class="{ 'is-alias': item.isAlias === true }"
@click="navigate">
<td class="c-list-item__name">
<a :href="objectLink" ref="objectLink">
<div class="c-list-item__type-icon" :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>
<tr
class="c-list-item"
:class="{ 'is-alias': item.isAlias === true }"
@click="navigate"
>
<td class="c-list-item__name">
<a
ref="objectLink"
:href="objectLink"
>
<div
class="c-list-item__type-icon"
: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>
<style lang="scss">
@ -64,7 +78,12 @@ import objectLink from '../../../ui/mixins/object-link';
export default {
mixins: [contextMenuGesture, objectLink],
props: ['item'],
props: {
item: {
type: Object,
required: true
}
},
methods: {
formatTime(timestamp, format) {
return moment(timestamp).format(format);

View File

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

View File

@ -20,21 +20,30 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-about c-about--licenses">
<h1>Open MCT Third Party Licenses</h1>
<p>This software includes components released under the following licenses:</p>
<div v-for="(pkg, key) in packages" :key="key" class="c-license">
<h2 class="c-license__name">{{key}}</h2>
<div class="c-license__details">
<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 class="c-about c-about--licenses">
<h1>Open MCT Third Party Licenses</h1>
<p>This software includes components released under the following licenses:</p>
<div
v-for="(pkg, key) in packages"
:key="key"
class="c-license"
>
<h2 class="c-license__name">
{{ key }}
</h2>
<div class="c-license__details">
<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>
</template>
<style lang="sass">
</style>
@ -49,4 +58,3 @@ export default {
}
}
</script>

View File

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

View File

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

View File

@ -1,44 +1,58 @@
<template>
<div class="c-tabs-view">
<div class="c-tabs-view__tabs-holder c-tabs"
:class="{
'is-dragging': isDragging,
'is-mouse-over': allowDrop
}">
<div class="c-drop-hint"
@drop="onDrop"
@dragenter="dragenter"
@dragleave="dragleave">
</div>
<div class="c-tabs-view__empty-message"
v-if="!tabsList.length > 0">Drag objects here to add them to this view.</div>
<button class="c-tabs-view__tab c-tab"
v-for="(tab,index) in tabsList"
:key="index"
:class="[
{'is-current': isCurrent(tab)},
tab.type.definition.cssClass
]"
@click="showTab(tab)">
<span class="c-button__label">{{tab.domainObject.name}}</span>
</button>
<div class="c-tabs-view">
<div
class="c-tabs-view__tabs-holder c-tabs"
:class="{
'is-dragging': isDragging,
'is-mouse-over': allowDrop
}"
>
<div
class="c-drop-hint"
@drop="onDrop"
@dragenter="dragenter"
@dragleave="dragleave"
></div>
<div
v-if="!tabsList.length > 0"
class="c-tabs-view__empty-message"
>
Drag objects here to add them to this view.
</div>
<div class="c-tabs-view__object-holder"
v-for="(tab, index) in tabsList"
<button
v-for="(tab,index) in tabsList"
:key="index"
: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">
</object-view>
</div>
class="c-tabs-view__tab c-tab"
:class="[
{'is-current': isCurrent(tab)},
tab.type.definition.cssClass
]"
@click="showTab(tab)"
>
<span class="c-button__label">{{ tab.domainObject.name }}</span>
</button>
</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>
<style lang="scss">
@ -129,6 +143,25 @@ export default {
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:{
showTab(tab) {
this.currentTab = tab;
@ -148,7 +181,7 @@ export default {
}
},
removeItem(identifier) {
let pos = this.tabsList.findIndex(tab =>
let pos = this.tabsList.findIndex(tab =>
tab.domainObject.identifier.namespace === identifier.namespace && tab.domainObject.identifier.key === identifier.key
),
tabToBeRemoved = this.tabsList[pos];
@ -169,7 +202,7 @@ export default {
onDrop(e) {
this.setCurrentTab = true;
},
dragstart (e) {
dragstart(e) {
if (e.dataTransfer.types.includes('openmct/domain-object-path')) {
this.isDragging = true;
}
@ -187,25 +220,6 @@ export default {
isCurrent(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>

View File

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

View File

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

View File

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

View File

@ -1,14 +1,19 @@
<template>
<div v-if="filterNames.length > 0"
:title=title
class="c-filter-indication"
:class="{ 'c-filter-indication--mixed': hasMixedFilters }">
<span class="c-filter-indication__mixed">{{ label }}</span>
<span v-for="(name, index) in filterNames"
class="c-filter-indication__label">
{{ name }}
</span>
</div>
<div
v-if="filterNames.length > 0"
:title="title"
class="c-filter-indication"
:class="{ 'c-filter-indication--mixed': hasMixedFilters }"
>
<span class="c-filter-indication__mixed">{{ label }}</span>
<span
v-for="(name, index) in filterNames"
:key="index"
class="c-filter-indication__label"
>
{{ name }}
</span>
</div>
</template>
<style lang="scss">
@ -53,113 +58,113 @@
</style>
<script>
const FILTER_INDICATOR_LABEL = 'Filters:';
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
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 USE_GLOBAL = 'useGlobal';
const FILTER_INDICATOR_LABEL = 'Filters:';
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
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 USE_GLOBAL = 'useGlobal';
export default {
inject: ['openmct', 'table'],
data() {
return {
filterNames: [],
filteredTelemetry: {}
}
export default {
inject: ['openmct', 'table'],
data() {
return {
filterNames: [],
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]));
});
},
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;
}
},
title() {
if (this.hasMixedFilters) {
return FILTER_INDICATOR_TITLE_MIXED;
} else {
return FILTER_INDICATOR_TITLE;
}
label() {
if (this.hasMixedFilters) {
return FILTER_INDICATOR_LABEL_MIXED;
} else {
return FILTER_INDICATOR_LABEL;
}
},
methods: {
setFilterNames() {
let names = [];
let composition = this.openmct.composition.get(this.table.configuration.domainObject);
title() {
if (this.hasMixedFilters) {
return FILTER_INDICATOR_TITLE_MIXED;
} else {
return FILTER_INDICATOR_TITLE;
}
}
},
mounted() {
let filters = this.table.configuration.getConfiguration().filters || {};
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) => {
domainObjects.forEach(telemetryObject => {
let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier);
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
let filters = this.filteredTelemetry[keyString];
composition && composition.load().then((domainObjects) => {
domainObjects.forEach(telemetryObject => {
let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier);
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
let filters = this.filteredTelemetry[keyString];
if (filters !== undefined) {
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);
}
}
});
if (filters !== undefined) {
names.push(this.getFilterNamesFromMetadata(filters, metadataValues));
}
});
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);
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);
},
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;
},
handleConfigurationChanges(configuration) {
if (!_.eq(this.filteredTelemetry, configuration.filters)) {
this.updateFilters(configuration.filters || {});
}
},
updateFilters(filters) {
this.filteredTelemetry = JSON.parse(JSON.stringify(filters));
this.setFilterNames();
return filterLabels;
},
handleConfigurationChanges(configuration) {
if (!_.eq(this.filteredTelemetry, configuration.filters)) {
this.updateFilters(configuration.filters || {});
}
},
mounted() {
let filters = this.table.configuration.getConfiguration().filters || {};
this.table.configuration.on('change', this.handleConfigurationChanges);
this.updateFilters(filters);
},
destroyed() {
this.table.configuration.off('change', this.handleConfigurationChanges);
updateFilters(filters) {
this.filteredTelemetry = JSON.parse(JSON.stringify(filters));
this.setFilterNames();
}
}
}
</script>

View File

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

View File

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

View File

@ -1,18 +1,50 @@
<template>
<div class="c-properties">
<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">
<li class="c-properties__row">
<div class="c-properties__label" title="Auto-size table"><label for="AutoSizeControl">Auto-size</label></div>
<div class="c-properties__value"><input type="checkbox" id="AutoSizeControl" :checked="configuration.autosize !== false" @change="toggleAutosize()"></div>
<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>
</ul>
<div class="c-properties__header">Table Column Visibility</div>
<div class="c-properties__header">
Table Column Visibility
</div>
<ul class="c-properties__section">
<li class="c-properties__row" v-for="(title, key) in headers">
<div class="c-properties__label" title="Show or hide column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
<div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
<li
v-for="(title, key) in headers"
: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>
</ul>
</template>
@ -34,6 +66,28 @@ export default {
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: {
updateHeaders(headers) {
this.headers = headers;
@ -45,7 +99,7 @@ export default {
this.tableConfiguration.updateConfiguration(this.configuration);
},
addObject(domainObject) {
this.addColumnsForObject(domainObject, true);
this.addColumnsForObject(domainObject, true);
this.updateHeaders(this.tableConfiguration.getAllHeaders());
},
removeObject(objectIdentifier) {
@ -70,28 +124,6 @@ export default {
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>

View File

@ -20,22 +20,25 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<tr :style="{ top: rowTop }"
<tr
:style="{ top: rowTop }"
class="noselect"
:class="[
rowClass,
{'is-selected': marked}
]"
v-on="listeners">
<component v-for="(title, key) in headers"
:key="key"
v-on="listeners"
>
<component
: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'}"
:class="[cellLimitClasses[key], selectableColumns[key] ? 'is-selectable' : '']"
:objectPath="objectPath"
:row="row">
</component>
:object-path="objectPath"
:row="row"
/>
</tr>
</template>
@ -55,21 +58,9 @@
import TableCell from './table-cell.vue';
export default {
inject: ['openmct', 'objectPath'],
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;
}, {})
}
inject: ['openmct'],
components: {
TableCell
},
props: {
headers: {
@ -86,7 +77,7 @@ export default {
},
objectPath: {
type: Array,
required: false
required: true
},
rowIndex: {
type: Number,
@ -109,6 +100,42 @@ export default {
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: {
calculateRowTop: function (rowOffset) {
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
@ -136,7 +163,7 @@ export default {
},
selectCell(element, 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([{
element: element,
context: {
@ -153,7 +180,7 @@ export default {
event.stopPropagation();
}
},
showContextMenu: function (event) {
showContextMenu: function (event) {
event.preventDefault();
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());
});
}
},
// 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>

View File

@ -22,131 +22,178 @@
<template>
<div class="c-table-wrapper">
<div class="c-table-control-bar c-control-bar">
<button class="c-button icon-download labeled"
v-if="allowExport"
v-on:click="exportAllDataAsCSV()"
title="Export This View's Data">
<button
v-if="allowExport"
class="c-button icon-download labeled"
title="Export This View's Data"
@click="exportAllDataAsCSV()"
>
<span class="c-button__label">Export Table Data</span>
</button>
<button class="c-button icon-download labeled"
v-if="allowExport"
v-show="markedRows.length"
v-on:click="exportMarkedDataAsCSV()"
title="Export Marked Rows As CSV">
<button
v-if="allowExport"
v-show="markedRows.length"
class="c-button icon-download labeled"
title="Export Marked Rows As CSV"
@click="exportMarkedDataAsCSV()"
>
<span class="c-button__label">Export Marked Rows</span>
</button>
<button class="c-button icon-x labeled"
v-show="markedRows.length"
v-on:click="unmarkAllRows()"
title="Unmark All Rows">
<button
v-show="markedRows.length"
class="c-button icon-x labeled"
title="Unmark All Rows"
@click="unmarkAllRows()"
>
<span class="c-button__label">Unmark All Rows</span>
</button>
<div v-if="enableMarking"
class="c-separator">
</div>
<button v-if="enableMarking"
class="c-button icon-pause pause-play labeled"
:class=" paused ? 'icon-play is-paused' : 'icon-pause'"
v-on:click="togglePauseByButton()"
:title="paused ? 'Continue Data Flow' : 'Pause Data Flow'">
<span class="c-button__label">
{{paused ? 'Play' : 'Pause'}}
</span>
<div
v-if="enableMarking"
class="c-separator"
></div>
<button
v-if="enableMarking"
class="c-button icon-pause pause-play labeled"
:class=" paused ? 'icon-play is-paused' : 'icon-pause'"
:title="paused ? 'Continue Data Flow' : 'Pause Data Flow'"
@click="togglePauseByButton()"
>
<span class="c-button__label">
{{ paused ? 'Play' : 'Pause' }}
</span>
</button>
<slot name="buttons"></slot>
</div>
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
:class="{
'loading': loading,
'paused' : paused
}">
<div
class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
:class="{
'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" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div>
<div
v-if="isDropTargetActive"
class="c-telemetry-table__drop-target"
:style="dropTargetStyle"
></div>
<!-- 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">
<thead>
<tr class="c-telemetry-table__headers__labels">
<table-column-header
v-for="(title, key, headerIndex) in headers"
:key="key"
:headerKey="key"
:headerIndex="headerIndex"
:header-key="key"
:header-index="headerIndex"
:column-width="columnWidths[key]"
:sort-options="sortOptions"
:is-editing="isEditing"
@sort="allowSorting && sortBy(key)"
@resizeColumn="resizeColumn"
@dropTargetOffsetChanged="setDropTargetOffset"
@dropTargetActive="dropTargetActive"
@reorderColumn="reorderColumn"
@resizeColumnEnd="updateConfiguredColumnWidths"
:columnWidth="columnWidths[key]"
:sortOptions="sortOptions"
:isEditing="isEditing"
><span class="c-telemetry-table__headers__label">{{title}}</span>
>
<span class="c-telemetry-table__headers__label">{{ title }}</span>
</table-column-header>
</tr>
<tr v-if="allowFiltering" class="c-telemetry-table__headers__filter">
<tr
v-if="allowFiltering"
class="c-telemetry-table__headers__filter"
>
<table-column-header
v-for="(title, key, headerIndex) in headers"
:key="key"
:headerKey="key"
:headerIndex="headerIndex"
:header-key="key"
:header-index="headerIndex"
:column-width="columnWidths[key]"
:is-editing="isEditing"
@resizeColumn="resizeColumn"
@dropTargetOffsetChanged="setDropTargetOffset"
@dropTargetActive="dropTargetActive"
@reorderColumn="reorderColumn"
@resizeColumnEnd="updateConfiguredColumnWidths"
:columnWidth="columnWidths[key]"
:isEditing="isEditing"
>
<search class="c-table__search"
>
<search
v-model="filters[key]"
v-on:input="filterChanged(key)"
v-on:clear="clearFilter(key)" />
class="c-table__search"
@input="filterChanged(key)"
@clear="clearFilter(key)"
/>
</table-column-header>
</tr>
</thead>
</table>
</div>
<!-- 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 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'}">
<div
class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w"
:style="{ 'max-width': widthWithScroll}"
@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>
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
<telemetry-table-row
v-for="(row, rowIndex) in visibleRows"
:key="rowIndex"
:headers="headers"
:columnWidths="columnWidths"
:rowIndex="rowIndex"
:objectPath="objectPath"
:rowOffset="rowOffset"
:rowHeight="rowHeight"
:column-widths="columnWidths"
:row-index="rowIndex"
:object-path="objectPath"
:row-offset="rowOffset"
:row-height="rowHeight"
:row="row"
:marked="row.marked"
@mark="markRow"
@unmark="unmarkRow"
@markMultipleConcurrent="markMultipleConcurrentRows">
</telemetry-table-row>
@markMultipleConcurrent="markMultipleConcurrentRows"
/>
</tbody>
</table>
</div>
<!-- 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>
<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>
</tr>
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
<telemetry-table-row
v-for="(sizingRowData, objectKeyString) in sizingRows"
:key="objectKeyString"
:headers="headers"
:columnWidths="configuredColumnWidths"
:row="sizingRowData">
</telemetry-table-row>
:column-widths="configuredColumnWidths"
:row="sizingRowData"
:object-path="objectPath"
/>
</table>
<telemetry-filter-indicator></telemetry-filter-indicator>
<telemetry-filter-indicator />
</div>
</div><!-- closes c-table-wrapper -->
</template>
@ -343,9 +390,6 @@ const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17;
const RESIZE_POLL_INTERVAL = 200;
const AUTO_SCROLL_TRIGGER_HEIGHT = 100;
const RESIZE_HOT_ZONE = 10;
const MOVE_TRIGGER_WAIT = 500;
const VERTICAL_SCROLL_WIDTH = 30;
export default {
components: {
@ -440,6 +484,59 @@ export default {
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: {
updateVisibleRows() {
if (!this.updatingView) {
@ -527,7 +624,7 @@ export default {
}
this.table.sortBy(this.sortOptions);
},
scroll () {
scroll() {
this.updateVisibleRows();
this.synchronizeScrollX();
@ -557,7 +654,7 @@ export default {
this.table.filteredRows.setColumnFilter(columnKey, '');
this.setHeight();
},
rowsAdded (rows) {
rowsAdded(rows) {
this.setHeight();
let sizingRow;
@ -578,7 +675,7 @@ export default {
this.updateVisibleRows();
},
rowsRemoved (rows) {
rowsRemoved(rows) {
this.setHeight();
this.updateVisibleRows();
},
@ -588,9 +685,9 @@ export default {
setHeight() {
let filteredRowsLength = this.table.filteredRows.getRows().length;
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
// Set element height directly to avoid having to wait for Vue to update DOM
// Set element height directly to avoid having to wait for Vue to update DOM
// which causes subsequent scroll to use an out of date height.
this.contentTable.style.height = this.totalHeight + 'px';
this.contentTable.style.height = this.totalHeight + 'px';
},
exportAsCSV(data) {
const headerKeys = Object.keys(this.headers);
@ -725,7 +822,7 @@ export default {
this.markedRows = [];
}
}
},
togglePauseByButton() {
if (this.paused) {
@ -744,7 +841,7 @@ export default {
positionInMarkedArray = this.markedRows.indexOf(row);
row.marked = false;
this.markedRows.splice(positionInMarkedArray, 1);
this.markedRows.splice(positionInMarkedArray, 1);
if (this.markedRows.length === 0) {
this.unpause();
@ -804,73 +901,19 @@ export default {
if (lastRowIndex < firstRowIndex) {
[firstRowIndex, lastRowIndex] = [lastRowIndex, firstRowIndex];
}
let baseRow = this.markedRows[0];
for (var i = firstRowIndex; i <= lastRowIndex; i++) {
let row = allRows[i];
row.marked = true;
if (row !== baseRow){
if (row !== baseRow) {
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>

View File

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

View File

@ -20,10 +20,11 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-conductor-axis"
ref="axisHolder"
@mousedown="dragStart($event)">
</div>
<div
ref="axisHolder"
class="c-conductor-axis"
@mousedown="dragStart($event)"
></div>
</template>
<style lang="scss">
@ -126,106 +127,9 @@ const PIXELS_PER_TICK_WIDE = 200;
export default {
inject: ['openmct'],
props: {
bounds: Object
},
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) {
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();
}
bounds: {
type: Object,
required: true
}
},
watch: {
@ -259,6 +163,103 @@ export default {
setInterval(this.resize, RESIZE_POLL_INTERVAL);
},
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.
*****************************************************************************/
<template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
<button class="c-button--menu c-mode-button"
@click.prevent="toggle">
<span class="c-button__label">{{selectedMode.name}}</span>
</button>
<div class="c-menu c-super-menu c-conductor__mode-menu"
v-if="open">
<div class="c-super-menu__menu">
<ul>
<li v-for="mode in modes"
:key="mode.key"
@click="setOption(mode)"
@mouseover="hoveredMode = mode"
@mouseleave="hoveredMode = {}"
class="menu-item-a"
:class="mode.cssClass">
{{mode.name}}
</li>
</ul>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
<button
class="c-button--menu c-mode-button"
@click.prevent="toggle"
>
<span class="c-button__label">{{ selectedMode.name }}</span>
</button>
<div
v-if="open"
class="c-menu c-super-menu c-conductor__mode-menu"
>
<div class="c-super-menu__menu">
<ul>
<li
v-for="mode in modes"
:key="mode.key"
class="menu-item-a"
:class="mode.cssClass"
@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 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 class="l-item-description__description">{{hoveredMode.description}}</div>
<div class="l-item-description__description">
{{ hoveredMode.description }}
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
@ -87,6 +97,14 @@ export default {
hoveredMode: {}
};
},
mounted: function () {
this.loadClocksFromConfiguration();
this.openmct.time.on('clock', this.setViewFromClock);
},
destroyed: function () {
this.openmct.time.off('clock', this.setViewFromClock);
},
methods: {
loadClocksFromConfiguration() {
let clocks = this.configuration.menuOptions
@ -139,7 +157,7 @@ export default {
}
let configuration = this.getMatchingConfig({
clock: clockKey,
clock: clockKey,
timeSystem: this.openmct.time.timeSystem().key
});
@ -147,10 +165,10 @@ export default {
configuration = this.getMatchingConfig({
clock: clockKey
});
this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds);
}
if (clockKey === undefined) {
this.openmct.time.stopClock();
} else {
@ -180,14 +198,6 @@ export default {
setViewFromClock(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.
*****************************************************************************/
<template>
<div class="c-clock-symbol">
<div class="hand-little"></div>
<div class="hand-big"></div>
</div>
<div class="c-clock-symbol">
<div class="hand-little"></div>
<div class="hand-big"></div>
</div>
</template>
<style lang="scss">

View File

@ -20,24 +20,33 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
v-if="selectedTimeSystem.name">
<button class="c-button--menu c-time-system-button"
:class="selectedTimeSystem.cssClass"
@click.prevent="toggle">
<span class="c-button__label">{{selectedTimeSystem.name}}</span>
</button>
<div class="c-menu" v-if="open">
<ul>
<li @click="setTimeSystemFromView(timeSystem)"
v-for="timeSystem in timeSystems"
:key="timeSystem.key"
:class="timeSystem.cssClass">
{{timeSystem.name}}
</li>
</ul>
</div>
<div
v-if="selectedTimeSystem.name"
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
>
<button
class="c-button--menu c-time-system-button"
:class="selectedTimeSystem.cssClass"
@click.prevent="toggle"
>
<span class="c-button__label">{{ selectedTimeSystem.name }}</span>
</button>
<div
v-if="open"
class="c-menu"
>
<ul>
<li
v-for="timeSystem in timeSystems"
:key="timeSystem.key"
:class="timeSystem.cssClass"
@click="setTimeSystemFromView(timeSystem)"
>
{{ timeSystem.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
@ -54,6 +63,14 @@ export default {
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: {
getValidTimesystemsForClock(clock) {
return this.configuration.menuOptions
@ -64,7 +81,7 @@ export default {
if (timeSystem.key !== this.selectedTimeSystem.key) {
let activeClock = this.openmct.time.clock();
let configuration = this.getMatchingConfig({
clock: activeClock && activeClock.key,
clock: activeClock && activeClock.key,
timeSystem: timeSystem.key
});
if (activeClock === undefined) {
@ -111,14 +128,6 @@ export default {
let activeClock = this.openmct.time.clock();
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.
*****************************************************************************/
<template>
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up c-datetime-picker__wrapper" ref="calendarHolder">
<a class="c-icon-button icon-calendar"
@click="toggle"></a>
<div class="c-menu c-menu--mobile-modal c-datetime-picker"
v-if="open">
<div class="c-datetime-picker__close-button">
<button class="c-click-icon icon-x-in-circle"
@click="toggle"></button>
</div>
<div class="c-datetime-picker__pager c-pager l-month-year-pager">
<div class="c-pager__prev c-icon-button icon-arrow-left"
@click.stop="changeMonth(-1)"></div>
<div class="c-pager__month-year">{{model.month}} {{model.year}}</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 class="c-calendar__row--body"
v-for="(row, index) in table"
:key="index">
<li v-for="(cell, index) in row"
: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
ref="calendarHolder"
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up c-datetime-picker__wrapper"
>
<a
class="c-icon-button icon-calendar"
@click="toggle"
></a>
<div
v-if="open"
class="c-menu c-menu--mobile-modal c-datetime-picker"
>
<div class="c-datetime-picker__close-button">
<button
class="c-click-icon icon-x-in-circle"
@click="toggle"
></button>
</div>
<div class="c-datetime-picker__pager c-pager l-month-year-pager">
<div
class="c-pager__prev c-icon-button icon-arrow-left"
@click.stop="changeMonth(-1)"
></div>
<div class="c-pager__month-year">
{{ model.month }} {{ model.year }}
</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>
</template>
<style lang="scss">
@ -163,10 +190,10 @@ import moment from 'moment';
import toggleMixin from '../../ui/mixins/toggle-mixin';
const TIME_NAMES = {
'hours': "Hour",
'minutes': "Minute",
'seconds': "Second"
};
'hours': "Hour",
'minutes': "Minute",
'seconds': "Second"
};
const MONTHS = moment.months();
const TIME_OPTIONS = (function makeRanges() {
let arr = [];
@ -184,8 +211,14 @@ export default {
inject: ['openmct'],
mixins: [toggleMixin],
props: {
defaultDateTime: String,
formatter: Object
defaultDateTime: {
type: String,
default: undefined
},
formatter: {
type: Object,
required: true
}
},
data: function () {
return {
@ -196,13 +229,17 @@ export default {
},
model: {
year: undefined,
month: undefined,
month: undefined
},
table: undefined,
date: undefined,
time: undefined
}
},
mounted: function () {
this.updateFromModel(this.defaultDateTime);
this.updateViewForMonth();
},
methods: {
generateTable() {
let m = moment.utc({ year: this.picker.year, month: this.picker.month }).day(0),
@ -233,9 +270,7 @@ export default {
},
updateFromModel(defaultDateTime) {
let m;
m = moment.utc(defaultDateTime);
let m = moment.utc(defaultDateTime);
this.date = {
year: m.year(),
@ -314,11 +349,7 @@ export default {
optionsFor(key) {
return TIME_OPTIONS[key];
},
},
mounted: function () {
this.updateFromModel(this.defaultDateTime);
this.updateViewForMonth();
}
}
}
</script>

View File

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

View File

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

View File

@ -20,34 +20,40 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-so-view has-local-controls"
:class="{
'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">
{{ domainObject && domainObject.name }}
</div>
<context-menu-drop-down
:object-path="objectPath">
</context-menu-drop-down>
<div
class="c-so-view has-local-controls"
:class="{
'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">
{{ domainObject && domainObject.name }}
</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
class="c-so-view__object-view"
ref="objectView"
:object="domainObject"
:show-edit-view="showEditView"
:object-path="objectPath">
</object-view>
<context-menu-drop-down
:object-path="objectPath"
/>
</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>
<style lang="scss">
@ -131,58 +137,64 @@
</style>
<script>
import ObjectView from './ObjectView.vue'
import ContextMenuDropDown from './contextMenuDropDown.vue';
import ObjectView from './ObjectView.vue'
import ContextMenuDropDown from './contextMenuDropDown.vue';
const SIMPLE_CONTENT_TYPES = [
'clock',
'timer',
'summary-widget',
'hyperlink'
];
const SIMPLE_CONTENT_TYPES = [
'clock',
'timer',
'summary-widget',
'hyperlink'
];
export default {
inject: ['openmct'],
props: {
domainObject: Object,
objectPath: Array,
hasFrame: Boolean,
showEditView: {
type: Boolean,
default: () => true
}
export default {
inject: ['openmct'],
components: {
ObjectView,
ContextMenuDropDown
},
props: {
domainObject: {
type: Object,
required: true
},
components: {
ObjectView,
ContextMenuDropDown,
objectPath: {
type: Array,
required: true
},
methods: {
expand() {
let objectView = this.$refs.objectView,
parentElement = objectView.$el,
childElement = parentElement.children[0];
hasFrame: Boolean,
showEditView: {
type: Boolean,
default: true
}
},
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({
element: childElement,
size: 'large',
onDestroy() {
parentElement.append(childElement);
}
});
},
getSelectionContext() {
return this.$refs.objectView.getSelectionContext();
}
return {
cssClass,
complexContent
}
},
methods: {
expand() {
let objectView = this.$refs.objectView,
parentElement = objectView.$el,
childElement = parentElement.children[0];
this.openmct.overlays.overlay({
element: childElement,
size: 'large',
onDestroy() {
parentElement.append(childElement);
}
});
},
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);
return {
cssClass,
complexContent
}
getSelectionContext() {
return this.$refs.objectView.getSelectionContext();
}
}
}
</script>

View File

@ -1,11 +1,15 @@
<template>
<a class="c-tree__item__label c-object-label"
<a
class="c-tree__item__label c-object-label"
draggable="true"
:href="objectLink"
@dragstart="dragStart"
@click="navigateOrPreview"
:href="objectLink">
<div class="c-tree__item__type-icon c-object-label__type-icon"
:class="typeClass"></div>
>
<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>
</a>
</template>
@ -53,29 +57,24 @@ export default {
mixins: [ObjectLink, ContextMenuGesture],
inject: ['openmct'],
props: {
domainObject: Object,
domainObject: {
type: Object,
required: true
},
objectPath: {
type: Array,
default() {
return [];
}
required: true
},
navigateToPath: String
navigateToPath: {
type: String,
default: undefined
}
},
data() {
return {
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: {
typeClass() {
let type = this.openmct.types.get(this.observedObject.type);
@ -85,15 +84,24 @@ export default {
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: {
navigateOrPreview(event) {
if (this.openmct.editor.isEditing()){
if (this.openmct.editor.isEditing()) {
event.preventDefault();
this.preview();
}
},
preview() {
if (this.previewAction.appliesTo(this.objectPath)){
if (this.previewAction.appliesTo(this.objectPath)) {
this.previewAction.invoke(this.objectPath);
}
},
@ -104,13 +112,13 @@ export default {
/*
* Cannot inspect data transfer objects on dragover/dragenter so impossible to determine composability at
* that point. If dragged object can be composed by navigated object, then indicate with presence of
* that point. If dragged object can be composed by navigated object, then indicate with presence of
* 'composable-domain-object' in data transfer
*/
if (this.openmct.composition.checkPolicy(navigatedObject, this.observedObject)) {
event.dataTransfer.setData("openmct/composable-domain-object", JSON.stringify(this.domainObject));
}
// serialize domain object anyway, because some views can drag-and-drop objects without composition
// serialize domain object anyway, because some views can drag-and-drop objects without composition
// (eg. notabook.)
event.dataTransfer.setData("openmct/domain-object-path", serializedPath);
event.dataTransfer.setData(`openmct/domain-object/${keyString}`, this.domainObject);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,25 +1,27 @@
<template>
<span class="c-disclosure-triangle"
<span
class="c-disclosure-triangle"
:class="{
'c-disclosure-triangle--expanded' : value,
'is-enabled' : enabled
}"
@click="$emit('input', !value)"></span>
@click="$emit('input', !value)"
></span>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
default: false
},
enabled: {
// 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.
type: Boolean,
default: false
}
export default {
props: {
value: {
type: Boolean,
default: false
},
enabled: {
// 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.
type: Boolean,
default: false
}
}
}
</script>

View File

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

View File

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

View File

@ -1,46 +1,43 @@
<template>
<div>
</div>
<div></div>
</template>
<style>
</style>
<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 {
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);
if (this.selectedViews) {
this.selectedViews.forEach(selectedView => {
let viewContainer = document.createElement('div');
this.$el.append(viewContainer)
selectedView.show(viewContainer);
selectedView.destroy();
});
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>

View File

@ -1,21 +1,39 @@
<template>
<div class="c-properties c-properties--location">
<div class="c-properties__header" title="The location of this linked object.">Original Location</div>
<ul class="c-properties__section" v-if="!multiSelect">
<li class="c-properties__row" v-if="originalPath.length">
<div
class="c-properties__header"
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">
<li v-for="pathObject in orderedOriginalPath"
<li
v-for="pathObject in orderedOriginalPath"
:key="pathObject.key"
class="c-location__item"
:key="pathObject.key">
>
<object-label
:domainObject="pathObject.domainObject"
:objectPath="pathObject.objectPath">
</object-label>
:domain-object="pathObject.domainObject"
:object-path="pathObject.objectPath"
/>
</li>
</ul>
</li>
</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>
</template>
@ -78,6 +96,11 @@ export default {
keyString: ''
}
},
computed: {
orderedOriginalPath() {
return this.originalPath.slice().reverse();
}
},
mounted() {
this.openmct.selection.on('change', this.updateSelection);
this.updateSelection(this.openmct.selection.get());
@ -111,7 +134,7 @@ export default {
if (!selection.length || !selection[0].length) {
this.clearData();
return;
}
}
if (selection.length > 1) {
this.multiSelect = true;
@ -119,7 +142,7 @@ export default {
} else {
this.multiSelect = false;
}
this.domainObject = selection[0][0].context.item;
let parentObject = selection[0][1];
@ -139,11 +162,6 @@ export default {
.then(this.setOriginalPath);
}
}
},
computed: {
orderedOriginalPath() {
return this.originalPath.reverse();
}
}
}
</script>

View File

@ -1,32 +1,75 @@
<template>
<div class="c-properties c-properties--properties">
<div class="c-properties__header">Properties</div>
<ul class="c-properties__section" v-if="!multiSelect && !singleSelectNonObject">
<div class="c-properties__header">
Properties
</div>
<ul
v-if="!multiSelect && !singleSelectNonObject"
class="c-properties__section"
>
<li class="c-properties__row">
<div class="c-properties__label">Title</div>
<div class="c-properties__value">{{ item.name }}</div>
<div class="c-properties__label">
Title
</div>
<div class="c-properties__value">
{{ item.name }}
</div>
</li>
<li class="c-properties__row">
<div class="c-properties__label">Type</div>
<div class="c-properties__value">{{ typeName }}</div>
<div class="c-properties__label">
Type
</div>
<div class="c-properties__value">
{{ typeName }}
</div>
</li>
<li class="c-properties__row" v-if="item.created">
<div class="c-properties__label">Created</div>
<div class="c-properties__value c-ne__text">{{ formatTime(item.created) }}</div>
<li
v-if="item.created"
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 class="c-properties__row" v-if="item.modified">
<div class="c-properties__label">Modified</div>
<div class="c-properties__value c-ne__text">{{ formatTime(item.modified) }}</div>
<li
v-if="item.modified"
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 class="c-properties__row"
<li
v-for="prop in typeProperties"
:key="prop.name">
<div class="c-properties__label">{{ prop.name }}</div>
<div class="c-properties__value">{{ prop.value }}</div>
:key="prop.name"
class="c-properties__row"
>
<div class="c-properties__label">
{{ prop.name }}
</div>
<div class="c-properties__value">
{{ prop.value }}
</div>
</li>
</ul>
<div class="c-properties__row--span-all" v-if="multiSelect">No properties to display for multiple items</div>
<div class="c-properties__row--span-all" v-if="singleSelectNonObject">No properties to display for this item</div>
<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>
</template>
@ -77,8 +120,8 @@ export default {
.map((field) => {
return {
name: field.name,
value: field.path.reduce((object, field) => {
return object[field];
value: field.path.reduce((object, key) => {
return object[key];
}, this.item)
};
});
@ -108,7 +151,7 @@ export default {
} else {
this.multiSelect = false;
this.domainObject = selection[0][0].context.item;
}
}
},
formatTime(unixTime) {
return Moment.utc(unixTime).format('YYYY-MM-DD[\n]HH:mm:ss') + ' UTC';

View File

@ -1,22 +1,34 @@
<template>
<!-- eslint-disable vue/no-v-html -->
<div class="c-about c-about--splash">
<div class="c-about__image c-splash-image"></div>
<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">
<h1 class="l-title s-title">Open MCT</h1>
<h1 class="l-title s-title">
Open MCT
</h1>
<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 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>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>
<h2>Version Information</h2>
<ul class="t-info l-info s-info">
<li>Version: {{buildInfo.version || 'Unknown'}}</li>
<li>Build Date: {{buildInfo.buildDate || 'Unknown'}}</li>
<li>Revision: {{buildInfo.revision || 'Unknown'}}</li>
<li>Branch: {{buildInfo.branch || 'Unknown'}}</li>
<li>Version: {{ buildInfo.version || 'Unknown' }}</li>
<li>Build Date: {{ buildInfo.buildDate || 'Unknown' }}</li>
<li>Revision: {{ buildInfo.revision || 'Unknown' }}</li>
<li>Branch: {{ buildInfo.branch || 'Unknown' }}</li>
</ul>
</div>
</div>
@ -37,4 +49,4 @@ export default {
}
}
}
</script>
</script>

View File

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

View File

@ -1,61 +1,93 @@
<template>
<div class="l-browse-bar">
<div class="l-browse-bar__start">
<button v-if="hasParent"
class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-pointer-left"
@click="goToParent"></button>
<div class="l-browse-bar__object-name--w"
:class="type.cssClass">
<span
class="l-browse-bar__object-name c-input-inline"
@blur="updateName"
@keydown.enter.prevent
@keyup.enter.prevent="updateNameOnEnterKeyPress"
contenteditable>
{{ domainObject.name }}
</span>
</div>
<div class="l-browse-bar__context-actions c-disclosure-button" @click.prevent.stop="showContextMenu"></div>
<div class="l-browse-bar">
<div class="l-browse-bar__start">
<button
v-if="hasParent"
class="l-browse-bar__nav-to-parent-button c-icon-button c-icon-button--major icon-pointer-left"
@click="goToParent"
></button>
<div
class="l-browse-bar__object-name--w"
:class="type.cssClass"
>
<span
class="l-browse-bar__object-name c-input-inline"
contenteditable
@blur="updateName"
@keydown.enter.prevent
@keyup.enter.prevent="updateNameOnEnterKeyPress"
>
{{ domainObject.name }}
</span>
</div>
<div
class="l-browse-bar__context-actions c-disclosure-button"
@click.prevent.stop="showContextMenu"
></div>
</div>
<div class="l-browse-bar__end">
<view-switcher
:currentView="currentView"
:views="views"
@setView="setView">
</view-switcher>
<!-- Action buttons -->
<div class="l-browse-bar__actions">
<button v-if="notebookEnabled"
class="l-browse-bar__actions__notebook-entry c-button icon-notebook"
title="New Notebook entry"
@click="snapshot()">
</button>
<button class="l-browse-bar__actions__edit c-button c-button--major icon-pencil" title="Edit" v-if="isViewEditable & !isEditing" @click="edit()"></button>
<div class="l-browse-bar__end">
<view-switcher
:current-view="currentView"
:views="views"
@setView="setView"
/>
<!-- Action buttons -->
<div class="l-browse-bar__actions">
<button
v-if="notebookEnabled"
class="l-browse-bar__actions__notebook-entry c-button icon-notebook"
title="New Notebook entry"
@click="snapshot()"
></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"
v-if="isEditing">
<button class="c-button--menu c-button--major icon-save" title="Save" @click.stop="toggleSaveMenu"></button>
<div class="c-menu" v-show="showSaveMenu">
<ul>
<li @click="saveAndFinishEditing"
class="icon-save"
title="Save and Finish Editing">
Save and Finish Editing
</li>
<li @click="saveAndContinueEditing"
class="icon-save"
title="Save and Continue Editing">
Save and Continue Editing
</li>
</ul>
</div>
<div
v-if="isEditing"
class="l-browse-bar__view-switcher c-ctrl-wrapper c-ctrl-wrapper--menus-left"
>
<button
class="c-button--menu c-button--major icon-save"
title="Save"
@click.stop="toggleSaveMenu"
></button>
<div
v-show="showSaveMenu"
class="c-menu"
>
<ul>
<li
class="icon-save"
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>
<button class="l-browse-bar__actions c-button icon-x" title="Cancel Editing" v-if="isEditing" @click="promptUserandCancelEditing()"></button>
</div>
<button
v-if="isEditing"
class="l-browse-bar__actions c-button icon-x"
title="Cancel Editing"
@click="promptUserandCancelEditing()"
></button>
</div>
</div>
</div>
</template>
<script>
@ -63,187 +95,186 @@ import NotebookSnapshot from '../utils/notebook-snapshot';
import ViewSwitcher from './ViewSwitcher.vue';
const PLACEHOLDER_OBJECT = {};
export default {
inject: ['openmct'],
components: {
ViewSwitcher
export default {
inject: ['openmct'],
components: {
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: {
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
views() {
return this
.openmct
.objectViews
.get(this.domainObject)
.map((p) => {
return {
key: p.key,
cssClass: p.cssClass,
name: p.name
};
});
},
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 () {
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] || {};
},
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 &&
hasParent() {
return this.domainObject !== PLACEHOLDER_OBJECT &&
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 () {
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;
});
parentUrl() {
let objectKeyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
let hash = window.location.hash;
return hash.slice(0, hash.lastIndexOf('/' + objectKeyString));
},
watch: {
domainObject() {
if (this.mutationObserver) {
this.mutationObserver();
}
this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
this.domainObject = domainObject;
});
type() {
let objectType = this.openmct.types.get(this.domainObject.type);
if (!objectType) {
return {}
}
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) {
this.mutationObserver();
}
document.removeEventListener('click', this.closeViewAndSaveMenu);
window.removeEventListener('click', this.promptUserbeforeNavigatingAway);
this.mutationObserver = this.openmct.objects.observe(this.domainObject, '*', (domainObject) => {
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>
<style lang="scss">

View File

@ -1,30 +1,40 @@
<template>
<div class="c-create-button--w">
<button class="c-create-button c-button--menu c-button--major icon-plus"
@click="open">
<span class="c-button__label">Create</span>
</button>
<div class="c-create-menu c-super-menu"
v-if="opened">
<div class="c-super-menu__menu">
<ul>
<li v-for="(item, index) in sortedItems"
:key="index"
:class="item.class"
:title="item.title"
@mouseover="showItemDescription(item)"
@click="create(item)">
{{ item.name }}
</li>
</ul>
<div class="c-create-button--w">
<button
class="c-create-button c-button--menu c-button--major icon-plus"
@click="open"
>
<span class="c-button__label">Create</span>
</button>
<div
v-if="opened"
class="c-create-menu c-super-menu"
>
<div class="c-super-menu__menu">
<ul>
<li
v-for="(item, index) in sortedItems"
:key="index"
:class="item.class"
: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 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 class="l-item-description__description">{{selectedMenuItem.title}}</div>
<div class="l-item-description__description">
{{ selectedMenuItem.title }}
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
@ -62,102 +72,97 @@
</style>
<script>
import CreateAction from '../../../platform/commonUI/edit/src/creation/CreateAction';
import objectUtils from '../../api/objects/object-utils';
import CreateAction from '../../../platform/commonUI/edit/src/creation/CreateAction';
import objectUtils from '../../api/objects/object-utils';
function convertToLegacyObject(domainObject) {
let keyString = objectUtils.makeKeyString(domainObject.identifier);
let oldModel = objectUtils.toOldFormat(domainObject);
return instantiate(oldModel, keyString);
}
export default {
inject: ['openmct'],
methods: {
open: function () {
if (this.opened) {
return;
}
this.opened = true;
setTimeout(() => document.addEventListener('click', this.close));
},
close: function () {
if (!this.opened) {
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);
export default {
inject: ['openmct'],
data: function () {
let items = [];
this.openmct.types.listKeys().forEach(key => {
let menuItem = this.openmct.types.get(key).definition;
if (menuItem.creatable) {
let menuItemTemplate = {
key: key,
name: menuItem.name,
class: menuItem.cssClass,
title: menuItem.description
};
items.push(menuItemTemplate);
}
},
destroyed () {
document.removeEventListener('click', this.close);
},
data: function() {
let items = [];
});
this.openmct.types.listKeys().forEach(key => {
let menuItem = this.openmct.types.get(key).definition;
if (menuItem.creatable) {
let menuItemTemplate = {
key: key,
name: menuItem.name,
class: menuItem.cssClass,
title: menuItem.description
};
items.push(menuItemTemplate);
return {
items: items,
selectedMenuItem: {},
opened: false
}
},
computed: {
sortedItems() {
return this.items.slice().sort((a,b) => {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
return {
items: items,
selectedMenuItem: {},
opened: false
}
},
destroyed() {
document.removeEventListener('click', this.close);
},
methods: {
open: function () {
if (this.opened) {
return;
}
this.opened = true;
setTimeout(() => document.addEventListener('click', this.close));
},
computed: {
sortedItems () {
return this.items.sort((a,b) => {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
close: function () {
if (!this.opened) {
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);
}
}
}
</script>

View File

@ -1,60 +1,84 @@
<template>
<div class="l-shell" :class="{
'is-editing': isEditing
}">
<div class="l-shell__head" :class="{
<div
class="l-shell"
:class="{
'is-editing': isEditing
}"
>
<div
class="l-shell__head"
:class="{
'l-shell__head--expanded': headExpanded,
'l-shell__head--minify-indicators': !headExpanded
}">
<CreateButton class="l-shell__create-button"></CreateButton>
<indicators class="l-shell__head-section l-shell__indicators">
</indicators>
<button class="l-shell__head__collapse-button c-button"
@click="toggleShellHead"></button>
<notification-banner></notification-banner>
<div class="l-shell__head-section l-shell__controls">
<button class="c-icon-button c-icon-button--major icon-new-window" title="Open in a new browser tab"
@click="openInNewTab"
target="_blank">
</button>
<button v-bind:class="['c-icon-button c-icon-button--major', fullScreen ? 'icon-fullscreen-collapse' : 'icon-fullscreen-expand']"
v-bind:title="`${fullScreen ? 'Exit' : 'Enable'} full screen mode`"
@click="fullScreenToggle">
</button>
</div>
<app-logo></app-logo>
}"
>
<CreateButton class="l-shell__create-button" />
<indicators class="l-shell__head-section l-shell__indicators" />
<button
class="l-shell__head__collapse-button c-button"
@click="toggleShellHead"
></button>
<notification-banner />
<div class="l-shell__head-section l-shell__controls">
<button
class="c-icon-button c-icon-button--major icon-new-window"
title="Open in a new browser tab"
target="_blank"
@click="openInNewTab"
></button>
<button
: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>
<multipane class="l-shell__main"
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>
<app-logo />
</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>
<style lang="scss">
@ -302,126 +326,126 @@
</style>
<script>
import Inspector from '../inspector/Inspector.vue';
import MctTree from './mct-tree.vue';
import ObjectView from '../components/ObjectView.vue';
import MctTemplate from '../legacy/mct-template.vue';
import CreateButton from './CreateButton.vue';
import multipane from './multipane.vue';
import pane from './pane.vue';
import BrowseBar from './BrowseBar.vue';
import Toolbar from '../toolbar/Toolbar.vue';
import AppLogo from './AppLogo.vue';
import Indicators from './status-bar/Indicators.vue';
import NotificationBanner from './status-bar/NotificationBanner.vue';
import Inspector from '../inspector/Inspector.vue';
import MctTree from './mct-tree.vue';
import ObjectView from '../components/ObjectView.vue';
import MctTemplate from '../legacy/mct-template.vue';
import CreateButton from './CreateButton.vue';
import multipane from './multipane.vue';
import pane from './pane.vue';
import BrowseBar from './BrowseBar.vue';
import Toolbar from '../toolbar/Toolbar.vue';
import AppLogo from './AppLogo.vue';
import Indicators from './status-bar/Indicators.vue';
import NotificationBanner from './status-bar/NotificationBanner.vue';
var enterFullScreen = () => {
var docElm = document.documentElement;
var enterFullScreen = () => {
var docElm = document.documentElement;
if (docElm.requestFullscreen) {
docElm.requestFullscreen();
} else if (docElm.mozRequestFullScreen) { /* Firefox */
docElm.mozRequestFullScreen();
} else if (docElm.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
docElm.webkitRequestFullscreen();
} else if (docElm.msRequestFullscreen) { /* IE/Edge */
docElm.msRequestFullscreen();
if (docElm.requestFullscreen) {
docElm.requestFullscreen();
} else if (docElm.mozRequestFullScreen) { /* Firefox */
docElm.mozRequestFullScreen();
} else if (docElm.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
docElm.webkitRequestFullscreen();
} else if (docElm.msRequestFullscreen) { /* IE/Edge */
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 = () => {
if (document.exitFullscreen) {
document.exitFullscreen();
return {
fullScreen: false,
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();
}
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
},
mounted() {
this.openmct.editor.on('isEditing', (isEditing)=>{
this.isEditing = isEditing;
});
this.openmct.selection.on('change', this.toggleHasToolbar);
},
data: function () {
let storedHeadProps = window.localStorage.getItem('openmct-shell-head');
let headExpanded = true;
if (storedHeadProps) {
headExpanded = JSON.parse(storedHeadProps).expanded;
}
return {
fullScreen: false,
conductorComponent: undefined,
isEditing: false,
hasToolbar: false,
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;
}
},
mounted() {
this.openmct.editor.on('isEditing', (isEditing)=>{
this.isEditing = isEditing;
});
this.openmct.selection.on('change', this.toggleHasToolbar);
},
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>

View File

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

View File

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

View File

@ -1,44 +1,56 @@
<template>
<div class="c-tree-and-search">
<div class="c-tree-and-search__search">
<search class="c-search" ref="shell-search"
:value="searchValue"
@input="searchTree"
@clear="searchTree">
</search>
</div>
<!-- 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 class="c-tree-and-search">
<div class="c-tree-and-search__search">
<search
ref="shell-search"
class="c-search"
:value="searchValue"
@input="searchTree"
@clear="searchTree"
/>
</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>
<style lang="scss">
@ -185,81 +197,81 @@
</style>
<script>
import treeItem from './tree-item.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('/');
}
import treeItem from './tree-item.vue'
import search from '../components/search.vue';
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 {
id: this.openmct.objects.makeKeyString(object.identifier),
object,
objectPath,
navigateToParent
}
id: this.openmct.objects.makeKeyString(c.identifier),
object: c,
objectPath: [c],
navigateToParent: '/browse'
};
});
});
},
searchTree(value) {
this.searchValue = value;
if (this.searchValue !== '') {
this.getFilteredChildren();
}
}
},
mounted() {
this.searchService = this.openmct.$injector.get('searchService');
this.getAllChildren();
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('/');
}
return {
id: this.openmct.objects.makeKeyString(object.identifier),
object,
objectPath,
navigateToParent
}
});
});
},
searchTree(value) {
this.searchValue = value;
if (this.searchValue !== '') {
this.getFilteredChildren();
}
}
}
}
</script>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,136 +1,149 @@
<template>
<li class="c-tree__item-h">
<div class="c-tree__item"
:class="{ 'is-alias': isAlias, 'is-navigated-object': isNavigated }">
<view-control class="c-tree__item__view-control"
:enabled="hasChildren"
v-model="expanded">
</view-control>
<object-label :domainObject="node.object"
:objectPath="node.objectPath"
:navigateToPath="navigateToPath">
</object-label>
</div>
<ul v-if="expanded" class="c-tree">
<li class="c-tree__item-h"
v-if="isLoading && !loaded">
<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">
</tree-item>
</ul>
</li>
<li class="c-tree__item-h">
<div
class="c-tree__item"
:class="{ 'is-alias': isAlias, 'is-navigated-object': isNavigated }"
>
<view-control
v-model="expanded"
class="c-tree__item__view-control"
:enabled="hasChildren"
/>
<object-label
:domain-object="node.object"
:object-path="node.objectPath"
:navigate-to-path="navigateToPath"
/>
</div>
<ul
v-if="expanded"
class="c-tree"
>
<li
v-if="isLoading && !loaded"
class="c-tree__item-h"
>
<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>
<script>
import viewControl from '../components/viewControl.vue';
import ObjectLabel from '../components/ObjectLabel.vue';
import viewControl from '../components/viewControl.vue';
import ObjectLabel from '../components/ObjectLabel.vue';
export default {
name: 'tree-item',
inject: ['openmct'],
props: {
node: Object
},
data() {
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
export default {
name: 'TreeItem',
inject: ['openmct'],
components: {
viewControl,
ObjectLabel
},
props: {
node: {
type: Object,
required: true
}
},
data() {
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;
}
},
computed: {
isAlias() {
let parent = this.node.objectPath[1];
if (!parent) {
return false;
}
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
return parentKeyString !== this.node.object.location;
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
return parentKeyString !== this.node.object.location;
}
},
watch: {
expanded(isExpanded) {
if (!this.hasChildren) {
return;
}
},
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
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;
}
}
},
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;
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
this.domainObject = newObject;
this.domainObject = this.node.object;
let removeListener = this.openmct.objects.observe(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() {
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;
}
removeChild(identifier) {
let removeId = this.openmct.objects.makeKeyString(identifier);
this.children = this.children
.filter(c => c.id !== removeId);
},
watch: {
expanded(isExpanded) {
if (!this.hasChildren) {
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;
}
}
finishLoading() {
this.isLoading = false;
this.loaded = true;
},
methods: {
addChild (child) {
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;
}
}
buildPathString(parentPath) {
return [parentPath, this.openmct.objects.makeKeyString(this.node.object.identifier)].join('/');
},
components: {
viewControl,
ObjectLabel
highlightIfNavigated(newPath, oldPath) {
if (newPath === this.navigateToPath) {
this.isNavigated = true;
} else if (oldPath === this.navigateToPath) {
this.isNavigated = false;
}
}
}
}
</script>

View File

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

View File

@ -19,37 +19,40 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="l-preview-window">
<div class="l-browse-bar">
<div class="l-browse-bar__start">
<div class="l-browse-bar__object-name--w"
:class="type.cssClass">
<span class="l-browse-bar__object-name">
{{ domainObject.name }}
</span>
<context-menu-drop-down :object-path="objectPath"></context-menu-drop-down>
</div>
</div>
<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>
<template>
<div class="l-preview-window">
<div class="l-browse-bar">
<div class="l-browse-bar__start">
<div
class="l-browse-bar__object-name--w"
:class="type.cssClass"
>
<span class="l-browse-bar__object-name">
{{ domainObject.name }}
</span>
<context-menu-drop-down :object-path="objectPath" />
</div>
</div>
<div class="l-preview-window__object-view">
<div ref="objectView"></div>
<div class="l-browse-bar__end">
<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 class="l-preview-window__object-view">
<div ref="objectView"></div>
</div>
</div>
</template>
<style lang="scss">
@import '~styles/sass-base';
@ -83,78 +86,78 @@
}
</style>
<script>
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
import NotebookSnapshot from '../utils/notebook-snapshot';
<script>
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
import NotebookSnapshot from '../utils/notebook-snapshot';
export default {
components: {
ContextMenuDropDown,
ViewSwitcher
export default {
components: {
ContextMenuDropDown,
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: [
'openmct',
'objectPath'
],
computed: {
views() {
return this
.openmct
.objectViews
.get(this.domainObject);
},
currentView() {
return this.views.filter(v => v.key === this.viewKey)[0] || {};
currentView() {
return this.views.filter(v => v.key === this.viewKey)[0] || {};
}
},
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();
},
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: {
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;
},
setView(view) {
this.clear();
setView(view) {
this.clear();
this.viewKey = view.key;
this.viewContainer = document.createElement('div');
this.viewContainer.classList.add('c-object-view','u-contents');
this.$refs.objectView.append(this.viewContainer);
this.viewKey = view.key;
this.viewContainer = document.createElement('div');
this.viewContainer.classList.add('c-object-view','u-contents');
this.$refs.objectView.append(this.viewContainer);
this.view = this.currentView.view(this.domainObject, this.objectPath);
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();
this.view = this.currentView.view(this.domainObject, this.objectPath);
this.view.show(this.viewContainer, false);
}
}
</script>
}
</script>

View File

@ -1,267 +1,270 @@
<template>
<div class="c-toolbar">
<component v-for="item in structure"
:is="item.control"
:options="item"
@click="triggerMethod(item, $event)"
@change="updateObjectValue"></component>
</div>
<div class="c-toolbar">
<component
:is="item.control"
v-for="(item, index) in structure"
:key="index"
:options="item"
@click="triggerMethod(item, $event)"
@change="updateObjectValue"
/>
</div>
</template>
<script>
import toolbarButton from './components/toolbar-button.vue';
import toolbarColorPicker from './components/toolbar-color-picker.vue';
import toolbarCheckbox from './components/toolbar-checkbox.vue';
import toolbarInput from './components/toolbar-input.vue';
import toolbarMenu from './components/toolbar-menu.vue';
import toolbarSelectMenu from './components/toolbar-select-menu.vue';
import toolbarSeparator from './components/toolbar-separator.vue';
import toolbarToggleButton from './components/toolbar-toggle-button.vue';
import toolbarButton from './components/toolbar-button.vue';
import toolbarColorPicker from './components/toolbar-color-picker.vue';
import toolbarCheckbox from './components/toolbar-checkbox.vue';
import toolbarInput from './components/toolbar-input.vue';
import toolbarMenu from './components/toolbar-menu.vue';
import toolbarSelectMenu from './components/toolbar-select-menu.vue';
import toolbarSeparator from './components/toolbar-separator.vue';
import toolbarToggleButton from './components/toolbar-toggle-button.vue';
import _ from 'lodash';
import _ from 'lodash';
export default {
inject: ['openmct'],
components: {
toolbarButton,
toolbarColorPicker,
toolbarCheckbox,
toolbarInput,
toolbarMenu,
toolbarSelectMenu,
toolbarSeparator,
toolbarToggleButton
},
data: function () {
return {
structure: []
};
},
methods: {
handleSelection(selection) {
this.removeListeners();
this.domainObjectsById = {};
export default {
inject: ['openmct'],
components: {
toolbarButton,
toolbarColorPicker,
toolbarCheckbox,
toolbarInput,
toolbarMenu,
toolbarSelectMenu,
toolbarSeparator,
toolbarToggleButton
},
data: function () {
return {
structure: []
};
},
mounted() {
this.openmct.selection.on('change', this.handleSelection);
this.handleSelection(this.openmct.selection.get());
if (selection.length === 0 || !selection[0][0]) {
this.structure = [];
return;
}
// Toolbars may change when edit mode is enabled/disabled, so listen
// for edit mode changes and update toolbars if necessary.
this.openmct.editor.on('isEditing', this.handleEditing);
},
methods: {
handleSelection(selection) {
this.removeListeners();
this.domainObjectsById = {};
let structure = this.openmct.toolbars.get(selection) || [];
this.structure = structure.map(toolbarItem => {
let domainObject = toolbarItem.domainObject;
let formKeys = [];
toolbarItem.control = "toolbar-" + toolbarItem.control;
if (selection.length === 0 || !selection[0][0]) {
this.structure = [];
return;
}
if (toolbarItem.dialog) {
toolbarItem.dialog.sections.forEach(section => {
section.rows.forEach(row => {
formKeys.push(row.key);
})
});
toolbarItem.formKeys = formKeys;
}
let structure = this.openmct.toolbars.get(selection) || [];
this.structure = structure.map(toolbarItem => {
let domainObject = toolbarItem.domainObject;
let formKeys = [];
toolbarItem.control = "toolbar-" + toolbarItem.control;
if (domainObject) {
toolbarItem.value = this.getValue(domainObject, toolbarItem);
this.registerListener(domainObject);
}
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();
if (toolbarItem.dialog) {
toolbarItem.dialog.sections.forEach(section => {
section.rows.forEach(row => {
formKeys.push(row.key);
})
});
}
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;
toolbarItem.formKeys = formKeys;
}
this.openmct.objects.mutate(item.domainObject, property, value);
},
triggerMethod(item, event) {
if (item.method) {
item.method({...event});
if (domainObject) {
toolbarItem.value = this.getValue(domainObject, toolbarItem);
this.registerListener(domainObject);
}
},
handleEditing(isEditing) {
this.handleSelection(this.openmct.selection.get());
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);
}
},
mounted() {
this.openmct.selection.on('change', this.handleSelection);
this.handleSelection(this.openmct.selection.get());
// Toolbars may change when edit mode is enabled/disabled, so listen
// for edit mode changes and update toolbars if necessary.
this.openmct.editor.on('isEditing', this.handleEditing);
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);
},
detroyed() {
this.openmct.selection.off('change', this.handleSelection);
this.openmct.editor.off('isEditing', this.handleEditing);
this.removeListeners();
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(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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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