[Menu API] All our drop down menus should use the new menu api #3607 (#3620)

* [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:
Nikhil 2021-03-29 10:49:49 -07:00 committed by GitHub
parent cf3566742b
commit f9bd31deee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 561 additions and 239 deletions

View File

@ -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);

View File

@ -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);
});
});
});

View File

@ -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>

View 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>

View File

@ -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

View File

@ -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);
}
}
};

View File

@ -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>

View File

@ -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) {

View File

@ -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);
}
}
};

View File

@ -1,6 +1,6 @@
export const FONT_SIZES = [
{
name: 'Default Size',
name: 'Default',
value: 'default'
},
{

View File

@ -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.

View File

@ -15,7 +15,7 @@
.c-create-menu {
max-height: 80vh;
max-width: 500px;
width: 500px;
min-height: 250px;
z-index: 70;

View File

@ -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);
},