/***************************************************************************** * Open MCT, Copyright (c) 2014-2020, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * Open MCT is licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * Open MCT includes source code licensed under additional open source * licenses. See the Open Source Licenses file (LICENSES.md) included with * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ define(['lodash'], function (_) { function DisplayLayoutToolbar(openmct) { return { name: "Display Layout Toolbar", key: "layout", description: "A toolbar for objects inside a display layout.", forSelection: function (selection) { if (!selection || selection.length === 0) { return false; } let selectionPath = selection[0]; let selectedObject = selectionPath[0]; let selectedParent = selectionPath[1]; // Apply the layout toolbar if the selected object is inside a layout, or the main layout is selected. return (selectedParent && selectedParent.context.item && selectedParent.context.item.type === 'layout') || (selectedObject.context.item && selectedObject.context.item.type === 'layout'); }, toolbar: function (selectedObjects) { const DIALOG_FORM = { 'text': { name: "Text Element Properties", sections: [ { rows: [ { key: "text", control: "textfield", name: "Text", required: true } ] } ] }, 'image': { name: "Image Properties", sections: [ { rows: [ { key: "url", control: "textfield", name: "Image URL", "cssClass": "l-input-lg", required: true } ] } ] } }, VIEW_TYPES = { 'telemetry-view': { value: 'telemetry-view', name: 'Alphanumeric', class: 'icon-alphanumeric' }, 'telemetry.plot.overlay': { value: 'telemetry.plot.overlay', name: 'Overlay Plot', class: "icon-plot-overlay" }, 'telemetry.plot.stacked': { value: "telemetry.plot.stacked", name: "Stacked Plot", class: "icon-plot-stacked" }, 'table': { value: 'table', name: 'Table', class: 'icon-tabular-realtime' } }, APPLICABLE_VIEWS = { 'telemetry-view': [ VIEW_TYPES['telemetry.plot.overlay'], VIEW_TYPES['telemetry.plot.stacked'], VIEW_TYPES.table ], 'telemetry.plot.overlay': [ VIEW_TYPES['telemetry.plot.stacked'], VIEW_TYPES.table, VIEW_TYPES['telemetry-view'] ], 'telemetry.plot.stacked': [ VIEW_TYPES['telemetry.plot.overlay'], VIEW_TYPES.table, VIEW_TYPES['telemetry-view'] ], 'table': [ VIEW_TYPES['telemetry.plot.overlay'], VIEW_TYPES['telemetry.plot.stacked'], VIEW_TYPES['telemetry-view'] ], 'telemetry-view-multi': [ VIEW_TYPES['telemetry.plot.overlay'], VIEW_TYPES['telemetry.plot.stacked'], VIEW_TYPES.table ], 'telemetry.plot.overlay-multi': [ VIEW_TYPES['telemetry.plot.stacked'] ] }; function getUserInput(form) { return openmct.$injector.get('dialogService').getUserInput(form, {}); } function getPath(selectionPath) { return `configuration.items[${selectionPath[0].context.index}]`; } function getAllTypes(selection) { return selection.filter(selectionPath => { let type = selectionPath[0].context.layoutItem.type; return type === 'text-view' || type === 'telemetry-view' || type === 'box-view' || type === 'image-view' || type === 'line-view' || type === 'subobject-view'; }); } function getAddButton(selection, selectionPath) { if (selection.length === 1) { selectionPath = selectionPath || selection[0]; return { control: "menu", domainObject: selectionPath[0].context.item, method: function (option) { let name = option.name.toLowerCase(); let form = DIALOG_FORM[name]; if (form) { getUserInput(form) .then(element => selectionPath[0].context.addElement(name, element)); } else { selectionPath[0].context.addElement(name); } }, key: "add", icon: "icon-plus", label: "Add", options: [ { "name": "Box", "class": "icon-box-round-corners" }, { "name": "Line", "class": "icon-line-horz" }, { "name": "Text", "class": "icon-font" }, { "name": "Image", "class": "icon-image" } ] }; } } function getToggleFrameButton(selectedParent, selection) { return { control: "toggle-button", domainObject: selectedParent, applicableSelectedItems: selection.filter(selectionPath => selectionPath[0].context.layoutItem.type === 'subobject-view' ), property: function (selectionPath) { return getPath(selectionPath) + ".hasFrame"; }, options: [ { value: false, icon: 'icon-frame-show', title: "Frame visible" }, { value: true, icon: 'icon-frame-hide', title: "Frame hidden" } ] }; } function getRemoveButton(selectedParent, selectionPath, selection) { return { control: "button", domainObject: selectedParent, icon: "icon-trash", title: "Delete the selected object", method: function () { let removeItem = selectionPath[1].context.removeItem; let prompt = openmct.overlays.dialog({ iconClass: 'alert', message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`, buttons: [ { label: 'Ok', emphasis: 'true', callback: function () { removeItem(getAllTypes(selection)); prompt.dismiss(); } }, { label: 'Cancel', callback: function () { prompt.dismiss(); } } ] }); } }; } function getStackOrder(selectedParent, selectionPath) { return { control: "menu", domainObject: selectedParent, icon: "icon-layers", title: "Move the selected object above or below other objects", options: [ { name: "Move to Top", value: "top", class: "icon-arrow-double-up" }, { name: "Move Up", value: "up", class: "icon-arrow-up" }, { name: "Move Down", value: "down", class: "icon-arrow-down" }, { name: "Move to Bottom", value: "bottom", class: "icon-arrow-double-down" } ], method: function (option) { selectionPath[1].context.orderItem(option.value, getAllTypes(selectedObjects)); } }; } function getXInput(selectedParent, selection) { if (selection.length === 1) { return { control: "input", type: "number", domainObject: selectedParent, applicableSelectedItems: getAllTypes(selection), property: function (selectionPath) { return getPath(selectionPath) + ".x"; }, label: "X:", title: "X position" }; } } function getYInput(selectedParent, selection) { if (selection.length === 1) { return { control: "input", type: "number", domainObject: selectedParent, applicableSelectedItems: getAllTypes(selection), property: function (selectionPath) { return getPath(selectionPath) + ".y"; }, label: "Y:", title: "Y position" }; } } function getWidthInput(selectedParent, selection) { if (selection.length === 1) { return { control: 'input', type: 'number', domainObject: selectedParent, applicableSelectedItems: getAllTypes(selection), property: function (selectionPath) { return getPath(selectionPath) + ".width"; }, label: 'W:', title: 'Resize object width' }; } } function getHeightInput(selectedParent, selection) { if (selection.length === 1) { return { control: 'input', type: 'number', domainObject: selectedParent, applicableSelectedItems: getAllTypes(selection), property: function (selectionPath) { return getPath(selectionPath) + ".height"; }, label: 'H:', title: 'Resize object height' }; } } function getX2Input(selectedParent, selection) { if (selection.length === 1) { return { control: "input", type: "number", domainObject: selectedParent, applicableSelectedItems: selection.filter(selectionPath => { return selectionPath[0].context.layoutItem.type === 'line-view'; }), property: function (selectionPath) { return getPath(selectionPath) + ".x2"; }, label: "X2:", title: "X2 position" }; } } function getY2Input(selectedParent, selection) { if (selection.length === 1) { return { control: "input", type: "number", domainObject: selectedParent, applicableSelectedItems: selection.filter(selectionPath => { return selectionPath[0].context.layoutItem.type === 'line-view'; }), property: function (selectionPath) { return getPath(selectionPath) + ".y2"; }, label: "Y2:", title: "Y2 position" }; } } function getTextSizeMenu(selectedParent, selection) { const TEXT_SIZE = [8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96, 128]; return { control: "select-menu", domainObject: selectedParent, applicableSelectedItems: selection.filter(selectionPath => { let type = selectionPath[0].context.layoutItem.type; return type === 'text-view' || type === 'telemetry-view'; }), property: function (selectionPath) { return getPath(selectionPath) + ".size"; }, title: "Set text size", options: TEXT_SIZE.map(size => { return { value: size + "px" }; }) }; } function getTextButton(selectedParent, selection) { return { control: "button", domainObject: selectedParent, applicableSelectedItems: selection.filter(selectionPath => { return selectionPath[0].context.layoutItem.type === 'text-view'; }), property: function (selectionPath) { return getPath(selectionPath); }, icon: "icon-font", title: "Edit text properties", dialog: DIALOG_FORM.text }; } function getTelemetryValueMenu(selectionPath, selection) { if (selection.length === 1) { return { control: "select-menu", domainObject: selectionPath[1].context.item, applicableSelectedItems: selection.filter(path => { return path[0].context.layoutItem.type === 'telemetry-view'; }), property: function (path) { return getPath(path) + ".value"; }, title: "Set value", options: openmct.telemetry.getMetadata(selectionPath[0].context.item).values().map(value => { return { name: value.name, value: value.key } }) }; } } function getDisplayModeMenu(selectedParent, selection) { if (selection.length === 1) { return { control: "select-menu", domainObject: selectedParent, applicableSelectedItems: selection.filter(selectionPath => { return selectionPath[0].context.layoutItem.type === 'telemetry-view'; }), property: function (selectionPath) { return getPath(selectionPath) + ".displayMode"; }, title: "Set display mode", options: [ { name: 'Label + Value', value: 'all' }, { name: "Label only", value: "label" }, { name: "Value only", value: "value" } ] }; } } function getDuplicateButton(selectedParent, selectionPath, selection) { return { control: "button", domainObject: selectedParent, icon: "icon-duplicate", title: "Duplicate the selected object", method: function () { let duplicateItem = selectionPath[1].context.duplicateItem; duplicateItem(selection); } }; } function getPropertyFromPath(object, path) { let splitPath = path.split('.'), property = Object.assign({}, object); while (splitPath.length && property) { property = property[splitPath.shift()]; } return property; } function areAllViews(type, path, selection) { let allTelemetry = true; selection.forEach(selectedItem => { let selectedItemContext = selectedItem[0].context; if (getPropertyFromPath(selectedItemContext, path) !== type) { allTelemetry = false; } }); return allTelemetry; } function getViewSwitcherMenu(selectedParent, selectionPath, selection) { if (selection.length === 1) { let displayLayoutContext = selectionPath[1].context, selectedItemContext = selectionPath[0].context, selectedItemType = selectedItemContext.item.type; if (selectedItemContext.layoutItem.type === 'telemetry-view') { selectedItemType = 'telemetry-view'; } let viewOptions = APPLICABLE_VIEWS[selectedItemType]; if (viewOptions) { return { control: "menu", domainObject: selectedParent, icon: "icon-object", title: "Switch the way this telemetry is displayed", options: viewOptions, method: function (option) { displayLayoutContext.switchViewType(selectedItemContext, option.value, selection); } }; } } else if (selection.length > 1) { if (areAllViews('telemetry-view', 'layoutItem.type', selection)) { let displayLayoutContext = selectionPath[1].context; return { control: "menu", domainObject: selectedParent, icon: "icon-object", title: "Merge into a telemetry table or plot", options: APPLICABLE_VIEWS['telemetry-view-multi'], method: function (option) { displayLayoutContext.mergeMultipleTelemetryViews(selection, option.value); } }; } else if (areAllViews('telemetry.plot.overlay', 'item.type', selection)) { let displayLayoutContext = selectionPath[1].context; return { control: "menu", domainObject: selectedParent, icon: "icon-object", title: "Merge into a stacked plot", options: APPLICABLE_VIEWS['telemetry.plot.overlay-multi'], method: function (option) { displayLayoutContext.mergeMultipleOverlayPlots(selection, option.value); } }; } } } function getSeparator() { return { control: "separator" }; } function isMainLayoutSelected(selectionPath) { let selectedObject = selectionPath[0].context.item; return selectedObject && selectedObject.type === 'layout' && !selectionPath[0].context.layoutItem; } if (isMainLayoutSelected(selectedObjects[0])) { return [getAddButton(selectedObjects)]; } let toolbar = { 'add-menu': [], 'text': [], 'url': [], 'viewSwitcher': [], 'toggle-frame': [], 'display-mode': [], 'telemetry-value': [], 'style': [], 'text-style': [], 'position': [], 'duplicate': [], 'remove': [] }; selectedObjects.forEach(selectionPath => { let selectedParent = selectionPath[1].context.item; let layoutItem = selectionPath[0].context.layoutItem; if (!layoutItem) { return; } if (layoutItem.type === 'subobject-view') { if (toolbar['add-menu'].length === 0 && selectionPath[0].context.item.type === 'layout') { toolbar['add-menu'] = [getAddButton(selectedObjects, selectionPath)]; } if (toolbar['toggle-frame'].length === 0) { toolbar['toggle-frame'] = [getToggleFrameButton(selectedParent, selectedObjects)]; } if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), getWidthInput(selectedParent, selectedObjects) ]; } if (toolbar.remove.length === 0) { toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)]; } if (toolbar.viewSwitcher.length === 0) { toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)]; } } else if (layoutItem.type === 'telemetry-view') { if (toolbar['display-mode'].length === 0) { toolbar['display-mode'] = [getDisplayModeMenu(selectedParent, selectedObjects)]; } if (toolbar['telemetry-value'].length === 0) { toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selectedObjects)]; } if (toolbar['text-style'].length === 0) { toolbar['text-style'] = [ getTextSizeMenu(selectedParent, selectedObjects) ]; } if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), getWidthInput(selectedParent, selectedObjects) ]; } if (toolbar.remove.length === 0) { toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)]; } if (toolbar.viewSwitcher.length === 0) { toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)]; } } else if (layoutItem.type === 'text-view') { if (toolbar['text-style'].length === 0) { toolbar['text-style'] = [ getTextSizeMenu(selectedParent, selectedObjects) ]; } if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), getWidthInput(selectedParent, selectedObjects) ]; } if (toolbar.text.length === 0) { toolbar.text = [getTextButton(selectedParent, selectedObjects)]; } if (toolbar.remove.length === 0) { toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)]; } } else if (layoutItem.type === 'box-view') { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), getWidthInput(selectedParent, selectedObjects) ]; } if (toolbar.remove.length === 0) { toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)]; } } else if (layoutItem.type === 'image-view') { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getHeightInput(selectedParent, selectedObjects), getWidthInput(selectedParent, selectedObjects) ]; } if (toolbar.remove.length === 0) { toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)]; } } else if (layoutItem.type === 'line-view') { if (toolbar.position.length === 0) { toolbar.position = [ getStackOrder(selectedParent, selectionPath), getXInput(selectedParent, selectedObjects), getYInput(selectedParent, selectedObjects), getX2Input(selectedParent, selectedObjects), getY2Input(selectedParent, selectedObjects) ]; } if (toolbar.remove.length === 0) { toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)]; } } if(toolbar.duplicate.length === 0) { toolbar.duplicate = [getDuplicateButton(selectedParent, selectionPath, selectedObjects)]; } }); let toolbarArray = Object.values(toolbar); return _.flatten(toolbarArray.reduce((accumulator, group, index) => { group = group.filter(control => control !== undefined); if (group.length > 0) { accumulator.push(group); if (index < toolbarArray.length - 1) { accumulator.push(getSeparator()); } } return accumulator; }, [])); } } } return DisplayLayoutToolbar; });