mirror of
https://github.com/nasa/openmct.git
synced 2025-02-21 01:42:31 +00:00
* [Menu API] All our drop down menu's now use the new menu api #3607 Co-authored-by: charlesh88 <charles.f.hacskaylo@nasa.gov> Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
parent
cf3566742b
commit
f9bd31deee
@ -20,7 +20,25 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import Menu from './menu.js';
|
||||
import Menu, { MENU_PLACEMENT } from './menu.js';
|
||||
|
||||
/**
|
||||
* Popup Menu options
|
||||
* @typedef {Object} MenuOptions
|
||||
* @property {String} menuClass Class for popup menu
|
||||
* @property {MENU_PLACEMENT} placement Placement for menu relative to click
|
||||
* @property {Function} onDestroy callback function: invoked when menu is destroyed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Popup Menu Item/action
|
||||
* @typedef {Object} Action
|
||||
* @property {String} cssClass Class for menu item
|
||||
* @property {Boolean} isDisabled adds disable class if true
|
||||
* @property {String} name Menu item text
|
||||
* @property {String} description Menu item description
|
||||
* @property {Function} callBack callback function: invoked when item is clicked
|
||||
*/
|
||||
|
||||
/**
|
||||
* The MenuAPI allows the addition of new context menu actions, and for the context menu to be launched from
|
||||
@ -33,12 +51,46 @@ class MenuAPI {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
|
||||
this.menuPlacement = MENU_PLACEMENT;
|
||||
this.showMenu = this.showMenu.bind(this);
|
||||
this.showSuperMenu = this.showSuperMenu.bind(this);
|
||||
|
||||
this._clearMenuComponent = this._clearMenuComponent.bind(this);
|
||||
this._showObjectMenu = this._showObjectMenu.bind(this);
|
||||
}
|
||||
|
||||
showMenu(x, y, actions, onDestroy) {
|
||||
/**
|
||||
* Show popup menu
|
||||
* @param {number} x x-coordinates for popup
|
||||
* @param {number} y x-coordinates for popup
|
||||
* @param {Array.<Action>|Array.<Array.<Action>>} actions collection of actions{@link Action} or collection of groups of actions {@link Action}
|
||||
* @param {MenuOptions} [menuOptions] [Optional] The {@link MenuOptions} options for Menu
|
||||
*/
|
||||
showMenu(x, y, actions, menuOptions) {
|
||||
this._createMenuComponent(x, y, actions, menuOptions);
|
||||
|
||||
this.menuComponent.showMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show popup menu with description of item on hover
|
||||
* @param {number} x x-coordinates for popup
|
||||
* @param {number} y x-coordinates for popup
|
||||
* @param {Array.<Action>|Array.<Array.<Action>>} actions collection of actions {@link Action} or collection of groups of actions {@link Action}
|
||||
* @param {MenuOptions} [menuOptions] [Optional] The {@link MenuOptions} options for Menu
|
||||
*/
|
||||
showSuperMenu(x, y, actions, menuOptions) {
|
||||
this._createMenuComponent(x, y, actions, menuOptions);
|
||||
|
||||
this.menuComponent.showSuperMenu();
|
||||
}
|
||||
|
||||
_clearMenuComponent() {
|
||||
this.menuComponent = undefined;
|
||||
delete this.menuComponent;
|
||||
}
|
||||
|
||||
_createMenuComponent(x, y, actions, menuOptions = {}) {
|
||||
if (this.menuComponent) {
|
||||
this.menuComponent.dismiss();
|
||||
}
|
||||
@ -47,18 +99,13 @@ class MenuAPI {
|
||||
x,
|
||||
y,
|
||||
actions,
|
||||
onDestroy
|
||||
...menuOptions
|
||||
};
|
||||
|
||||
this.menuComponent = new Menu(options);
|
||||
this.menuComponent.once('destroy', this._clearMenuComponent);
|
||||
}
|
||||
|
||||
_clearMenuComponent() {
|
||||
this.menuComponent = undefined;
|
||||
delete this.menuComponent;
|
||||
}
|
||||
|
||||
_showObjectMenu(objectPath, x, y, actionsToBeIncluded) {
|
||||
let applicableActions = this.openmct.actions._groupedAndSortedObjectActions(objectPath, actionsToBeIncluded);
|
||||
|
||||
|
@ -22,10 +22,11 @@
|
||||
|
||||
import MenuAPI from './MenuAPI';
|
||||
import Menu from './menu';
|
||||
import { createOpenMct, resetApplicationState } from '../../utils/testing';
|
||||
import { createOpenMct, createMouseEvent, resetApplicationState } from '../../utils/testing';
|
||||
|
||||
describe ('The Menu API', () => {
|
||||
let openmct;
|
||||
let element;
|
||||
let menuAPI;
|
||||
let actionsArray;
|
||||
let x;
|
||||
@ -33,21 +34,37 @@ describe ('The Menu API', () => {
|
||||
let result;
|
||||
let onDestroy;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach((done) => {
|
||||
const appHolder = document.createElement('div');
|
||||
appHolder.style.display = 'block';
|
||||
appHolder.style.width = '1920px';
|
||||
appHolder.style.height = '1080px';
|
||||
|
||||
openmct = createOpenMct();
|
||||
|
||||
element = document.createElement('div');
|
||||
element.style.display = 'block';
|
||||
element.style.width = '1920px';
|
||||
element.style.height = '1080px';
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless(appHolder);
|
||||
|
||||
menuAPI = new MenuAPI(openmct);
|
||||
actionsArray = [
|
||||
{
|
||||
key: 'test-css-class-1',
|
||||
name: 'Test Action 1',
|
||||
cssClass: 'test-css-class-1',
|
||||
cssClass: 'icon-clock',
|
||||
description: 'This is a test action',
|
||||
callBack: () => {
|
||||
result = 'Test Action 1 Invoked';
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'test-css-class-2',
|
||||
name: 'Test Action 2',
|
||||
cssClass: 'test-css-class-2',
|
||||
cssClass: 'icon-clock',
|
||||
description: 'This is a test action',
|
||||
callBack: () => {
|
||||
result = 'Test Action 2 Invoked';
|
||||
@ -76,7 +93,11 @@ describe ('The Menu API', () => {
|
||||
beforeEach(() => {
|
||||
onDestroy = jasmine.createSpy('onDestroy');
|
||||
|
||||
menuAPI.showMenu(x, y, actionsArray, onDestroy);
|
||||
const menuOptions = {
|
||||
onDestroy
|
||||
};
|
||||
|
||||
menuAPI.showMenu(x, y, actionsArray, menuOptions);
|
||||
vueComponent = menuAPI.menuComponent.component;
|
||||
menuComponent = document.querySelector(".c-menu");
|
||||
|
||||
@ -131,4 +152,62 @@ describe ('The Menu API', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("superMenu method", () => {
|
||||
it("creates a superMenu", () => {
|
||||
menuAPI.showSuperMenu(x, y, actionsArray);
|
||||
|
||||
const superMenu = document.querySelector('.c-super-menu__menu');
|
||||
|
||||
expect(superMenu).not.toBeNull();
|
||||
});
|
||||
|
||||
it("Mouse over a superMenu shows correct description", (done) => {
|
||||
menuAPI.showSuperMenu(x, y, actionsArray);
|
||||
|
||||
const superMenu = document.querySelector('.c-super-menu__menu');
|
||||
const superMenuItem = superMenu.querySelector('li');
|
||||
const mouseOverEvent = createMouseEvent('mouseover');
|
||||
|
||||
superMenuItem.dispatchEvent(mouseOverEvent);
|
||||
const itemDescription = document.querySelector('.l-item-description__description');
|
||||
|
||||
setTimeout(() => {
|
||||
expect(itemDescription.innerText).toEqual(actionsArray[0].description);
|
||||
expect(superMenu).not.toBeNull();
|
||||
done();
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Menu Placements", () => {
|
||||
it("default menu position BOTTOM_RIGHT", () => {
|
||||
menuAPI.showMenu(x, y, actionsArray);
|
||||
|
||||
const menu = document.querySelector('.c-menu');
|
||||
|
||||
const boundingClientRect = menu.getBoundingClientRect();
|
||||
const left = boundingClientRect.left;
|
||||
const top = boundingClientRect.top;
|
||||
|
||||
expect(left).toEqual(x);
|
||||
expect(top).toEqual(y);
|
||||
});
|
||||
|
||||
it("menu position BOTTOM_RIGHT", () => {
|
||||
const menuOptions = {
|
||||
placement: openmct.menus.menuPlacement.BOTTOM_RIGHT
|
||||
};
|
||||
|
||||
menuAPI.showMenu(x, y, actionsArray, menuOptions);
|
||||
|
||||
const menu = document.querySelector('.c-menu');
|
||||
const boundingClientRect = menu.getBoundingClientRect();
|
||||
const left = boundingClientRect.left;
|
||||
const top = boundingClientRect.top;
|
||||
|
||||
expect(left).toEqual(x);
|
||||
expect(top).toEqual(y);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,10 @@
|
||||
<template>
|
||||
<div class="c-menu">
|
||||
<ul v-if="actions.length && actions[0].length">
|
||||
<div class="c-menu"
|
||||
:class="options.menuClass"
|
||||
>
|
||||
<ul v-if="options.actions.length && options.actions[0].length">
|
||||
<template
|
||||
v-for="(actionGroups, index) in actions"
|
||||
v-for="(actionGroups, index) in options.actions"
|
||||
>
|
||||
<li
|
||||
v-for="action in actionGroups"
|
||||
@ -14,7 +16,7 @@
|
||||
{{ action.name }}
|
||||
</li>
|
||||
<div
|
||||
v-if="index !== actions.length - 1"
|
||||
v-if="index !== options.actions.length - 1"
|
||||
:key="index"
|
||||
class="c-menu__section-separator"
|
||||
>
|
||||
@ -30,7 +32,7 @@
|
||||
|
||||
<ul v-else>
|
||||
<li
|
||||
v-for="action in actions"
|
||||
v-for="action in options.actions"
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
:title="action.description"
|
||||
@ -38,7 +40,7 @@
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
<li v-if="actions.length === 0">
|
||||
<li v-if="options.actions.length === 0">
|
||||
No actions defined.
|
||||
</li>
|
||||
</ul>
|
||||
@ -47,6 +49,6 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['actions']
|
||||
inject: ['options']
|
||||
};
|
||||
</script>
|
||||
|
88
src/api/menu/components/SuperMenu.vue
Normal file
88
src/api/menu/components/SuperMenu.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="c-menu"
|
||||
:class="[options.menuClass, 'c-super-menu']"
|
||||
>
|
||||
<ul v-if="options.actions.length && options.actions[0].length"
|
||||
class="c-super-menu__menu"
|
||||
>
|
||||
<template
|
||||
v-for="(actionGroups, index) in options.actions"
|
||||
>
|
||||
<li
|
||||
v-for="action in actionGroups"
|
||||
:key="action.name"
|
||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||
:title="action.description"
|
||||
@click="action.callBack"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
<div
|
||||
v-if="index !== options.actions.length - 1"
|
||||
:key="index"
|
||||
class="c-menu__section-separator"
|
||||
>
|
||||
</div>
|
||||
<li
|
||||
v-if="actionGroups.length === 0"
|
||||
:key="index"
|
||||
>
|
||||
No actions defined.
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
||||
<ul v-else
|
||||
class="c-super-menu__menu"
|
||||
>
|
||||
<li
|
||||
v-for="action in options.actions"
|
||||
:key="action.name"
|
||||
:class="action.cssClass"
|
||||
:title="action.description"
|
||||
@click="action.callBack"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
<li v-if="options.actions.length === 0">
|
||||
No actions defined.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="c-super-menu__item-description">
|
||||
<div :class="['l-item-description__icon', 'bg-' + hoveredItem.cssClass]"></div>
|
||||
<div class="l-item-description__name">
|
||||
{{ hoveredItem.name }}
|
||||
</div>
|
||||
<div class="l-item-description__description">
|
||||
{{ hoveredItem.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['options'],
|
||||
data: function () {
|
||||
return {
|
||||
hoveredItem: {}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleItemDescription(action = {}) {
|
||||
const hoveredItem = {
|
||||
name: action.name,
|
||||
description: action.description,
|
||||
cssClass: action.cssClass
|
||||
};
|
||||
|
||||
this.hoveredItem = Object.assign({}, this.hoveredItem, hoveredItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -21,32 +21,33 @@
|
||||
*****************************************************************************/
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import MenuComponent from './components/Menu.vue';
|
||||
import SuperMenuComponent from './components/SuperMenu.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export const MENU_PLACEMENT = {
|
||||
TOP: 'top',
|
||||
TOP_LEFT: 'top-left',
|
||||
TOP_RIGHT: 'top-right',
|
||||
BOTTOM: 'bottom',
|
||||
BOTTOM_LEFT: 'bottom-left',
|
||||
BOTTOM_RIGHT: 'bottom-right',
|
||||
LEFT: 'left',
|
||||
RIGHT: 'right'
|
||||
};
|
||||
|
||||
class Menu extends EventEmitter {
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
this.options = options;
|
||||
|
||||
this.component = new Vue({
|
||||
components: {
|
||||
MenuComponent
|
||||
},
|
||||
provide: {
|
||||
actions: options.actions
|
||||
},
|
||||
template: '<menu-component />'
|
||||
});
|
||||
|
||||
if (options.onDestroy) {
|
||||
this.once('destroy', options.onDestroy);
|
||||
}
|
||||
|
||||
this.dismiss = this.dismiss.bind(this);
|
||||
this.show = this.show.bind(this);
|
||||
|
||||
this.show();
|
||||
this.showMenu = this.showMenu.bind(this);
|
||||
this.showSuperMenu = this.showSuperMenu.bind(this);
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
@ -60,7 +61,7 @@ class Menu extends EventEmitter {
|
||||
this.component.$mount();
|
||||
document.body.appendChild(this.component.$el);
|
||||
|
||||
let position = this._calculatePopupPosition(this.options.x, this.options.y, this.component.$el);
|
||||
let position = this._calculatePopupPosition(this.component.$el);
|
||||
|
||||
this.component.$el.style.left = `${position.x}px`;
|
||||
this.component.$el.style.top = `${position.y}px`;
|
||||
@ -68,11 +69,97 @@ class Menu extends EventEmitter {
|
||||
document.addEventListener('click', this.dismiss);
|
||||
}
|
||||
|
||||
showMenu() {
|
||||
this.component = new Vue({
|
||||
provide: {
|
||||
options: this.options
|
||||
},
|
||||
components: {
|
||||
MenuComponent
|
||||
},
|
||||
template: '<menu-component />'
|
||||
});
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
showSuperMenu() {
|
||||
this.component = new Vue({
|
||||
provide: {
|
||||
options: this.options
|
||||
},
|
||||
components: {
|
||||
SuperMenuComponent
|
||||
},
|
||||
template: '<super-menu-component />'
|
||||
});
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_calculatePopupPosition(eventPosX, eventPosY, menuElement) {
|
||||
_calculatePopupPosition(menuElement) {
|
||||
let menuDimensions = menuElement.getBoundingClientRect();
|
||||
|
||||
if (!this.options.placement) {
|
||||
this.options.placement = MENU_PLACEMENT.BOTTOM_RIGHT;
|
||||
}
|
||||
|
||||
const menuPosition = this._getMenuPositionBasedOnPlacement(menuDimensions);
|
||||
|
||||
return this._preventMenuOverflow(menuPosition, menuDimensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_getMenuPositionBasedOnPlacement(menuDimensions) {
|
||||
let eventPosX = this.options.x;
|
||||
let eventPosY = this.options.y;
|
||||
|
||||
// Adjust popup menu based on placement
|
||||
switch (this.options.placement) {
|
||||
case MENU_PLACEMENT.TOP:
|
||||
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
|
||||
eventPosY = this.options.y - menuDimensions.height;
|
||||
break;
|
||||
case MENU_PLACEMENT.BOTTOM:
|
||||
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
|
||||
break;
|
||||
case MENU_PLACEMENT.LEFT:
|
||||
eventPosX = this.options.x - menuDimensions.width;
|
||||
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
|
||||
break;
|
||||
case MENU_PLACEMENT.RIGHT:
|
||||
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
|
||||
break;
|
||||
case MENU_PLACEMENT.TOP_LEFT:
|
||||
eventPosX = this.options.x - menuDimensions.width;
|
||||
eventPosY = this.options.y - menuDimensions.height;
|
||||
break;
|
||||
case MENU_PLACEMENT.TOP_RIGHT:
|
||||
eventPosY = this.options.y - menuDimensions.height;
|
||||
break;
|
||||
case MENU_PLACEMENT.BOTTOM_LEFT:
|
||||
eventPosX = this.options.x - menuDimensions.width;
|
||||
break;
|
||||
case MENU_PLACEMENT.BOTTOM_RIGHT:
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
x: eventPosX,
|
||||
y: eventPosY
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_preventMenuOverflow(menuPosition, menuDimensions) {
|
||||
let { x: eventPosX, y: eventPosY } = menuPosition;
|
||||
let overflowX = (eventPosX + menuDimensions.width) - document.body.clientWidth;
|
||||
let overflowY = (eventPosY + menuDimensions.height) - document.body.clientHeight;
|
||||
|
||||
@ -84,6 +171,14 @@ class Menu extends EventEmitter {
|
||||
eventPosY = eventPosY - overflowY;
|
||||
}
|
||||
|
||||
if (eventPosX < 0) {
|
||||
eventPosX = 0;
|
||||
}
|
||||
|
||||
if (eventPosY < 0) {
|
||||
eventPosY = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
x: eventPosX,
|
||||
y: eventPosY
|
||||
|
@ -20,59 +20,27 @@
|
||||
* 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-history-button icon-history"
|
||||
@click.prevent="toggle"
|
||||
>
|
||||
<span class="c-button__label">History</span>
|
||||
</button>
|
||||
<div v-if="open"
|
||||
class="c-menu c-conductor__history-menu"
|
||||
>
|
||||
<ul v-if="hasHistoryPresets">
|
||||
<li
|
||||
v-for="preset in presets"
|
||||
:key="preset.label"
|
||||
class="icon-clock"
|
||||
@click="selectPresetBounds(preset.bounds)"
|
||||
>
|
||||
{{ preset.label }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
v-if="hasHistoryPresets"
|
||||
class="c-menu__section-separator"
|
||||
></div>
|
||||
|
||||
<div class="c-menu__section-hint">
|
||||
Past timeframes, ordered by latest first
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li
|
||||
v-for="(timespan, index) in historyForCurrentTimeSystem"
|
||||
:key="index"
|
||||
class="icon-history"
|
||||
@click="selectTimespan(timespan)"
|
||||
>
|
||||
{{ formatTime(timespan.start) }} - {{ formatTime(timespan.end) }}
|
||||
</li>
|
||||
</ul>
|
||||
<div ref="historyButton"
|
||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||
>
|
||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
class="c-button--menu c-history-button icon-history"
|
||||
@click.prevent.stop="showHistoryMenu"
|
||||
>
|
||||
<span class="c-button__label">History</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import toggleMixin from '../../ui/mixins/toggle-mixin';
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
const LOCAL_STORAGE_HISTORY_KEY_FIXED = 'tcHistory';
|
||||
const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
|
||||
const DEFAULT_RECORDS = 10;
|
||||
|
||||
export default {
|
||||
mixins: [toggleMixin],
|
||||
inject: ['openmct', 'configuration'],
|
||||
props: {
|
||||
bounds: {
|
||||
@ -117,9 +85,6 @@ export default {
|
||||
isFixed() {
|
||||
return this.openmct.time.clock() === undefined;
|
||||
},
|
||||
hasHistoryPresets() {
|
||||
return this.timeSystem.isUTCBased && this.presets.length;
|
||||
},
|
||||
historyForCurrentTimeSystem() {
|
||||
const history = this[this.currentHistory][this.timeSystem.key];
|
||||
|
||||
@ -168,6 +133,36 @@ export default {
|
||||
this.initializeHistoryIfNoHistory();
|
||||
},
|
||||
methods: {
|
||||
getHistoryMenuItems() {
|
||||
const history = this.historyForCurrentTimeSystem.map(timespan => {
|
||||
return {
|
||||
cssClass: 'icon-history',
|
||||
name: `${this.formatTime(timespan.start)} - ${this.formatTime(timespan.end)}`,
|
||||
description: `${this.formatTime(timespan.start)} - ${this.formatTime(timespan.end)}`,
|
||||
callBack: () => this.selectTimespan(timespan)
|
||||
};
|
||||
});
|
||||
|
||||
history.unshift({
|
||||
cssClass: 'c-menu__section-hint',
|
||||
description: 'Past timeframes, ordered by latest first',
|
||||
isDisabled: true,
|
||||
name: 'Past timeframes, ordered by latest first',
|
||||
callBack: () => {}
|
||||
});
|
||||
|
||||
return history;
|
||||
},
|
||||
getPresetMenuItems() {
|
||||
return this.presets.map(preset => {
|
||||
return {
|
||||
cssClass: 'icon-clock',
|
||||
name: preset.label,
|
||||
description: preset.label,
|
||||
callBack: () => this.selectPresetBounds(preset.bounds)
|
||||
};
|
||||
});
|
||||
},
|
||||
getHistoryFromLocalStorage() {
|
||||
const localStorageHistory = localStorage.getItem(this.storageKey);
|
||||
const history = localStorageHistory ? JSON.parse(localStorageHistory) : undefined;
|
||||
@ -265,6 +260,28 @@ export default {
|
||||
}).formatter;
|
||||
|
||||
return (isNegativeOffset ? '-' : '') + formatter.format(time);
|
||||
},
|
||||
showHistoryMenu() {
|
||||
const elementBoundingClientRect = this.$refs.historyButton.getBoundingClientRect();
|
||||
const x = elementBoundingClientRect.x;
|
||||
const y = elementBoundingClientRect.y;
|
||||
|
||||
const menuOptions = {
|
||||
menuClass: 'c-conductor__history-menu',
|
||||
placement: this.openmct.menus.menuPlacement.TOP_RIGHT
|
||||
};
|
||||
|
||||
const menuActions = [];
|
||||
|
||||
const presets = this.getPresetMenuItems();
|
||||
if (presets.length) {
|
||||
menuActions.push(presets);
|
||||
}
|
||||
|
||||
const history = this.getHistoryMenuItems();
|
||||
menuActions.push(history);
|
||||
|
||||
this.openmct.menus.showMenu(x, y, menuActions, menuOptions);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -20,41 +20,16 @@
|
||||
* 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
|
||||
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="l-item-description__description">
|
||||
{{ hoveredMode.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div ref="modeButton"
|
||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||
>
|
||||
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
|
||||
<button
|
||||
class="c-button--menu c-mode-button"
|
||||
@click.prevent.stop="showModesMenu"
|
||||
>
|
||||
<span class="c-button__label">{{ selectedMode.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -88,6 +63,19 @@ export default {
|
||||
this.openmct.time.off('clock', this.setViewFromClock);
|
||||
},
|
||||
methods: {
|
||||
showModesMenu() {
|
||||
const elementBoundingClientRect = this.$refs.modeButton.getBoundingClientRect();
|
||||
const x = elementBoundingClientRect.x;
|
||||
const y = elementBoundingClientRect.y;
|
||||
|
||||
const menuOptions = {
|
||||
menuClass: 'c-conductor__mode-menu',
|
||||
placement: this.openmct.menus.menuPlacement.TOP_RIGHT
|
||||
};
|
||||
|
||||
this.openmct.menus.showSuperMenu(x, y, this.modes, menuOptions);
|
||||
},
|
||||
|
||||
loadClocksFromConfiguration() {
|
||||
let clocks = this.configuration.menuOptions
|
||||
.map(menuOption => menuOption.clock)
|
||||
@ -109,19 +97,25 @@ export default {
|
||||
|
||||
getModeOptionForClock(clock) {
|
||||
if (clock === undefined) {
|
||||
const key = 'fixed';
|
||||
|
||||
return {
|
||||
key: 'fixed',
|
||||
key,
|
||||
name: 'Fixed Timespan',
|
||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
||||
cssClass: 'icon-tabular'
|
||||
cssClass: 'icon-tabular',
|
||||
callBack: () => this.setOption(key)
|
||||
};
|
||||
} else {
|
||||
const key = clock.key;
|
||||
|
||||
return {
|
||||
key: clock.key,
|
||||
key,
|
||||
name: clock.name,
|
||||
description: "Monitor streaming data in real-time. The Time "
|
||||
+ "Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
||||
cssClass: clock.cssClass || 'icon-clock'
|
||||
cssClass: clock.cssClass || 'icon-clock',
|
||||
callBack: () => this.setOption(key)
|
||||
};
|
||||
}
|
||||
},
|
||||
@ -132,8 +126,7 @@ export default {
|
||||
})[0];
|
||||
},
|
||||
|
||||
setOption(option) {
|
||||
let clockKey = option.key;
|
||||
setOption(clockKey) {
|
||||
if (clockKey === 'fixed') {
|
||||
clockKey = undefined;
|
||||
}
|
||||
@ -181,6 +174,5 @@ export default {
|
||||
this.selectedMode = this.getModeOptionForClock(clock);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
@ -20,40 +20,22 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
<template>
|
||||
<div
|
||||
v-if="selectedTimeSystem.name"
|
||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||
<div v-if="selectedTimeSystem.name"
|
||||
ref="timeSystemButton"
|
||||
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||
>
|
||||
<button
|
||||
class="c-button--menu c-time-system-button"
|
||||
:class="selectedTimeSystem.cssClass"
|
||||
@click.prevent="toggle"
|
||||
@click.prevent.stop="showTimeSystemMenu"
|
||||
>
|
||||
<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>
|
||||
import toggleMixin from '../../ui/mixins/toggle-mixin';
|
||||
|
||||
export default {
|
||||
mixins: [toggleMixin],
|
||||
inject: ['openmct', 'configuration'],
|
||||
data: function () {
|
||||
let activeClock = this.openmct.time.clock();
|
||||
@ -72,10 +54,26 @@ export default {
|
||||
this.openmct.time.on('clock', this.setViewFromClock);
|
||||
},
|
||||
methods: {
|
||||
showTimeSystemMenu() {
|
||||
const elementBoundingClientRect = this.$refs.timeSystemButton.getBoundingClientRect();
|
||||
const x = elementBoundingClientRect.x;
|
||||
const y = elementBoundingClientRect.y;
|
||||
|
||||
const menuOptions = {
|
||||
placement: this.openmct.menus.menuPlacement.TOP_RIGHT
|
||||
};
|
||||
|
||||
this.openmct.menus.showMenu(x, y, this.timeSystems, menuOptions);
|
||||
},
|
||||
getValidTimesystemsForClock(clock) {
|
||||
return this.configuration.menuOptions
|
||||
.filter(menuOption => menuOption.clock === (clock && clock.key))
|
||||
.map(menuOption => JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem))));
|
||||
.map(menuOption => {
|
||||
const timeSystem = JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem)));
|
||||
timeSystem.callBack = () => this.setTimeSystemFromView(timeSystem);
|
||||
|
||||
return timeSystem;
|
||||
});
|
||||
},
|
||||
setTimeSystemFromView(timeSystem) {
|
||||
if (timeSystem.key !== this.selectedTimeSystem.key) {
|
||||
|
@ -1,64 +1,103 @@
|
||||
<template>
|
||||
<div class="c-toolbar">
|
||||
<toolbar-select-menu
|
||||
class="menus-to-left menus-no-icon"
|
||||
:options="fontSizeMenuOptions"
|
||||
@change="setFontSize"
|
||||
/>
|
||||
<div class="c-toolbar__separator"></div>
|
||||
<toolbar-select-menu
|
||||
class="menus-to-left menus-no-icon"
|
||||
:options="fontMenuOptions"
|
||||
@change="setFont"
|
||||
/>
|
||||
<div ref="fontSizeMenu"
|
||||
class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left"
|
||||
>
|
||||
<button
|
||||
class="c-icon-button c-button--menu icon-font-size"
|
||||
@click.prevent.stop="showFontSizeMenu"
|
||||
>
|
||||
<span class="c-button__label">{{ fontSizeLabel }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div ref="fontMenu"
|
||||
class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left"
|
||||
>
|
||||
<button
|
||||
class="c-icon-button c-button--menu icon-font"
|
||||
@click.prevent.stop="showFontMenu"
|
||||
>
|
||||
<span class="c-button__label">{{ fontTypeLable }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ToolbarSelectMenu from '@/ui/toolbar/components/toolbar-select-menu.vue';
|
||||
|
||||
import {
|
||||
FONT_SIZES,
|
||||
FONTS
|
||||
} from '@/ui/inspector/styles/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ToolbarSelectMenu
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
fontStyle: {
|
||||
type: Object,
|
||||
required: true
|
||||
required: true,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fontMenuOptions() {
|
||||
return {
|
||||
control: 'select-menu',
|
||||
icon: "icon-font",
|
||||
title: "Set font style",
|
||||
value: this.fontStyle.font,
|
||||
options: FONTS
|
||||
};
|
||||
fontTypeLable() {
|
||||
const fontType = FONTS.find(f => f.value === this.fontStyle.font);
|
||||
if (!fontType) {
|
||||
return '??';
|
||||
}
|
||||
|
||||
return fontType.name || fontType.value || FONTS[0].name;
|
||||
},
|
||||
fontSizeMenuOptions() {
|
||||
return {
|
||||
control: 'select-menu',
|
||||
icon: "icon-font-size",
|
||||
title: "Set font size",
|
||||
value: this.fontStyle.fontSize,
|
||||
options: FONT_SIZES
|
||||
};
|
||||
fontSizeLabel() {
|
||||
const fontSize = FONT_SIZES.find(f => f.value === this.fontStyle.fontSize);
|
||||
if (!fontSize) {
|
||||
return '??';
|
||||
}
|
||||
|
||||
return fontSize.name || fontSize.value || FONT_SIZES[0].name;
|
||||
},
|
||||
fontMenu() {
|
||||
return FONTS.map(font => {
|
||||
return {
|
||||
cssClass: font.cssClass || '',
|
||||
name: font.name,
|
||||
description: font.name,
|
||||
callBack: () => this.setFont(font.value)
|
||||
};
|
||||
});
|
||||
},
|
||||
fontSizeMenu() {
|
||||
return FONT_SIZES.map(fontSize => {
|
||||
return {
|
||||
cssClass: fontSize.cssClass || '',
|
||||
name: fontSize.name,
|
||||
description: fontSize.name,
|
||||
callBack: () => this.setFontSize(fontSize.value)
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setFont(font) {
|
||||
this.$emit('set-font-property', { font: font });
|
||||
this.$emit('set-font-property', { font });
|
||||
},
|
||||
setFontSize(fontSize) {
|
||||
this.$emit('set-font-property', { fontSize: fontSize });
|
||||
this.$emit('set-font-property', { fontSize });
|
||||
},
|
||||
showFontMenu() {
|
||||
const elementBoundingClientRect = this.$refs.fontMenu.getBoundingClientRect();
|
||||
const x = elementBoundingClientRect.x;
|
||||
const y = elementBoundingClientRect.bottom;
|
||||
|
||||
this.openmct.menus.showMenu(x, y, this.fontMenu);
|
||||
},
|
||||
showFontSizeMenu() {
|
||||
const elementBoundingClientRect = this.$refs.fontSizeMenu.getBoundingClientRect();
|
||||
const x = elementBoundingClientRect.x;
|
||||
const y = elementBoundingClientRect.bottom;
|
||||
|
||||
this.openmct.menus.showMenu(x, y, this.fontSizeMenu);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
export const FONT_SIZES = [
|
||||
{
|
||||
name: 'Default Size',
|
||||
name: 'Default',
|
||||
value: 'default'
|
||||
},
|
||||
{
|
||||
|
@ -1,41 +1,13 @@
|
||||
<template>
|
||||
<div class="c-create-button--w">
|
||||
<div ref="createButton"
|
||||
class="c-create-button--w"
|
||||
>
|
||||
<button
|
||||
class="c-create-button c-button--menu c-button--major icon-plus"
|
||||
@click="open"
|
||||
@click.prevent.stop="showCreateMenu"
|
||||
>
|
||||
<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"
|
||||
:aria-label="item.name"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@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="l-item-description__description">
|
||||
{{ selectedMenuItem.title }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -53,10 +25,10 @@ export default {
|
||||
|
||||
if (menuItem.creatable) {
|
||||
let menuItemTemplate = {
|
||||
key: key,
|
||||
cssClass: menuItem.cssClass,
|
||||
name: menuItem.name,
|
||||
class: menuItem.cssClass,
|
||||
title: menuItem.description
|
||||
description: menuItem.description,
|
||||
callBack: () => this.create(key)
|
||||
};
|
||||
|
||||
items.push(menuItemTemplate);
|
||||
@ -82,30 +54,19 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('click', this.close);
|
||||
},
|
||||
methods: {
|
||||
open: function () {
|
||||
if (this.opened) {
|
||||
return;
|
||||
}
|
||||
showCreateMenu() {
|
||||
const elementBoundingClientRect = this.$refs.createButton.getBoundingClientRect();
|
||||
const x = elementBoundingClientRect.x;
|
||||
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
|
||||
|
||||
this.opened = true;
|
||||
setTimeout(() => document.addEventListener('click', this.close));
|
||||
},
|
||||
close: function () {
|
||||
if (!this.opened) {
|
||||
return;
|
||||
}
|
||||
const menuOptions = {
|
||||
menuClass: 'c-create-menu'
|
||||
};
|
||||
|
||||
this.opened = false;
|
||||
document.removeEventListener('click', this.close);
|
||||
this.openmct.menus.showSuperMenu(x, y, this.sortedItems, menuOptions);
|
||||
},
|
||||
showItemDescription: function (menuItem) {
|
||||
this.selectedMenuItem = menuItem;
|
||||
},
|
||||
create: function (item) {
|
||||
create(key) {
|
||||
// Hack for support. TODO: rewrite create action.
|
||||
// 1. Get contextual object from navigation
|
||||
// 2. Get legacy type from legacy api
|
||||
@ -114,7 +75,7 @@ export default {
|
||||
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 legacyType = this.openmct.$injector.get('typeService').getType(key);
|
||||
let context = {
|
||||
key: "create",
|
||||
domainObject: legacyContextualParent // should be same as parent object.
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
.c-create-menu {
|
||||
max-height: 80vh;
|
||||
max-width: 500px;
|
||||
width: 500px;
|
||||
min-height: 250px;
|
||||
z-index: 70;
|
||||
|
||||
|
@ -40,7 +40,11 @@ export default {
|
||||
let actions = actionsCollection.getVisibleActions();
|
||||
let sortedActions = this.openmct.actions._groupAndSortActions(actions);
|
||||
|
||||
this.openmct.menus.showMenu(event.clientX, event.clientY, sortedActions, this.onContextMenuDestroyed);
|
||||
const menuOptions = {
|
||||
onDestroy: this.onContextMenuDestroyed
|
||||
};
|
||||
|
||||
this.openmct.menus.showMenu(event.clientX, event.clientY, sortedActions, menuOptions);
|
||||
this.contextClickActive = true;
|
||||
this.$emit('context-click-active', true);
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user