Compare commits

...

13 Commits

Author SHA1 Message Date
2cf5e738c1 added enter, esc listeners to time popups 2025-01-09 10:48:33 -08:00
8d434e30d4 remove description search for filter 2025-01-08 15:49:33 -08:00
2b24033ae1 Merge branches 'filterable-create' and 'filterable-create' of https://github.com/nasa/openmct into filterable-create 2025-01-08 15:45:35 -08:00
93093667cd Sanding and shimming in CSS related to Create menu
- Create menu labels can now wrap.
- Line height, padding adjusted for menu items.
- Create menu filter input slimmed down a bit.
2025-01-07 15:39:57 -08:00
8dff9f1a25 cleanup remove debug code 2025-01-07 13:37:42 -08:00
f68fdb03bc making sure we dont save invalid forms 2025-01-06 13:59:25 -08:00
3ecb9d4355 adding key handling in forms and dialogs for submit and cancel clean up some code 2025-01-06 12:31:17 -08:00
f665c5e71b reverting changes brought over from other issue 2025-01-06 11:02:26 -08:00
2b78e7ce04 modifying super menu template and css to handle new filter input 2025-01-06 10:57:02 -08:00
cded8fc4d0 not sure if scatter plots are annotatable 2024-12-20 15:21:47 -08:00
1326693643 updated types api to return all types, updated annotations api to return annotatable types, cleaned up use of hasNumericTelemetry elsewhere in the code 2024-12-20 15:18:40 -08:00
128f4827df moving hasNumericTelemetry to an api method 2024-12-20 15:12:08 -08:00
e1969585f9 added getTypes to types api, modifying which tabs are shown, working on annoatation tab 2024-12-20 14:22:18 -08:00
12 changed files with 182 additions and 67 deletions

View File

@ -129,6 +129,7 @@ export default {
}
},
mounted() {
document.addEventListener('keydown', this.handleKeyDown);
this.formSections = this.model.sections.map((section) => {
section.id = uuid();
@ -141,6 +142,9 @@ export default {
return section;
});
},
unmounted() {
document.removeEventListener('keydown', this.handleKeyDown);
},
methods: {
onChange(data) {
this.invalidProperties[data.model.key] = data.invalid;
@ -152,6 +156,13 @@ export default {
},
onSave() {
this.$emit('on-save');
},
handleKeyDown({ key }) {
if (key === 'Enter' && !this.isInvalid) {
this.onSave();
} else if (key === 'Escape') {
this.onCancel();
}
}
}
};

View File

@ -26,53 +26,66 @@
:class="[options.menuClass, 'c-super-menu']"
:style="styleObject"
>
<ul
v-if="options.actions.length && options.actions[0].length"
role="menu"
class="c-super-menu__menu"
>
<template v-for="(actionGroups, index) in options.actions" :key="index">
<div role="group">
<li
v-for="action in actionGroups"
:key="action.name"
role="menuitem"
:aria-disabled="action.isDisabled"
aria-describedby="item-description"
:class="action.cssClass"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
>
{{ action.name }}
</li>
<div
v-if="index !== options.actions.length - 1"
:key="index"
role="separator"
class="c-menu__section-separator"
></div>
<li v-if="actionGroups.length === 0" :key="index">No actions defined.</li>
</div></template
<div class="c-super-menu__left-col">
<div v-if="options.filterable" class="c-super-menu__filter l-input-lg">
<input
ref="filterInput"
v-model="searchTerm"
type="text"
placeholder="Filter..."
@input="filterItems"
@keydown.stop="handleKeyDown"
@click.stop
/>
</div>
<ul
v-if="filteredActions.length && filteredActions[0].length"
role="menu"
class="c-super-menu__menu"
>
</ul>
<template v-for="(actionGroups, index) in filteredActions" :key="index">
<div role="group">
<li
v-for="action in actionGroups"
:key="action.name"
role="menuitem"
:aria-disabled="action.isDisabled"
aria-describedby="item-description"
:class="action.cssClass"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
>
{{ action.name }}
</li>
<div
v-if="index !== filteredActions.length - 1"
:key="index"
role="separator"
class="c-menu__section-separator"
></div>
<li v-if="actionGroups.length === 0" :key="index">No actions defined.</li>
</div></template
>
</ul>
<ul v-else class="c-super-menu__menu" role="menu">
<li
v-for="action in options.actions"
:key="action.name"
role="menuitem"
:class="action.cssClass"
:aria-label="action.name"
aria-describedby="item-description"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
>
{{ action.name }}
</li>
<li v-if="options.actions.length === 0">No actions defined.</li>
</ul>
<ul v-else class="c-super-menu__menu" role="menu">
<li
v-for="action in filteredActions"
:key="action.name"
role="menuitem"
:class="action.cssClass"
:aria-label="action.name"
aria-describedby="item-description"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
>
{{ action.name }}
</li>
<li v-if="filteredActions.length === 0">No actions defined.</li>
</ul>
</div>
<div aria-live="polite" class="c-super-menu__item-description">
<div :class="itemDescriptionIconClass"></div>
@ -89,10 +102,12 @@
import popupMenuMixin from '../mixins/popupMenuMixin.js';
export default {
mixins: [popupMenuMixin],
inject: ['options'],
inject: ['options', 'dismiss'],
data() {
return {
hoveredItem: null
hoveredItem: null,
filteredActions: [],
searchTerm: ''
};
},
computed: {
@ -114,6 +129,15 @@ export default {
return this.hoveredItem?.description ?? '';
}
},
mounted() {
this.filteredActions = this.options.actions;
if (this.options.filterable) {
this.$nextTick(() => {
this.$refs.filterInput.focus();
});
}
},
methods: {
toggleItemDescription(action = null) {
const hoveredItem = {
@ -123,6 +147,42 @@ export default {
};
this.hoveredItem = hoveredItem;
},
filterItems() {
const term = this.searchTerm.toLowerCase();
if (!term) {
this.filteredActions = this.options.actions;
return;
}
if (Array.isArray(this.options.actions[0])) {
// Handle grouped actions
this.filteredActions = this.options.actions
.map((group) => group.filter((action) => action.name.toLowerCase().includes(term)))
.filter((group) => group.length > 0);
} else {
// Handle flat actions list
this.filteredActions = this.options.actions.filter((action) =>
action.name.toLowerCase().includes(term)
);
}
},
handleKeyDown({ key }) {
if (key === 'Enter') {
// if there is only one action, select it immediately on enter
const flattenedActions = Array.isArray(this.filteredActions[0])
? this.filteredActions.flat()
: this.filteredActions;
if (flattenedActions.length === 1) {
flattenedActions[0].onItemClicked();
this.dismiss();
}
} else if (key === 'Escape') {
this.dismiss();
}
}
}
};

View File

@ -114,7 +114,8 @@ class Menu extends EventEmitter {
return h(SuperMenuComponent);
},
provide: {
options: this.options
options: this.options,
dismiss: this.dismiss
}
});

View File

@ -64,6 +64,7 @@ export default {
};
},
mounted() {
document.addEventListener('keydown', this.handleKeyDown);
const element = this.$refs.element;
element.appendChild(this.element);
const elementForFocus = this.getElementForFocus() || element;
@ -73,6 +74,7 @@ export default {
},
methods: {
destroy() {
document.removeEventListener('keydown', this.handleKeyDown);
if (this.dismissible) {
this.dismiss();
}
@ -100,6 +102,20 @@ export default {
}
return focusButton[0];
},
handleKeyDown({ key }) {
if (key === 'Enter') {
if (this.focusIndex >= 0 && this.focusIndex < this.buttons.length) {
this.buttonClickHandler(this.buttons[this.focusIndex].callback);
} else {
const okButton = this.buttons?.find((button) => button.label.toLowerCase() === 'ok');
if (okButton) {
this.buttonClickHandler(okButton.callback);
}
}
} else if (key === 'Escape') {
this.destroy();
}
}
}
};

View File

@ -182,13 +182,22 @@ export default {
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
},
mounted() {
document.addEventListener('keydown', this.handleKeyDown);
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
this.setViewFromBounds(this.bounds);
},
beforeUnmount() {
this.clearAllValidation();
document.removeEventListener('keydown', this.handleKeyDown);
},
methods: {
handleKeyDown({ key }) {
if (key === 'Enter' && !this.hasInputValidityError) {
this.handleFormSubmission(true);
} else if (key === 'Escape') {
this.dismiss();
}
},
handleNewBounds(bounds) {
this.setBounds(bounds);
this.setViewFromBounds(bounds);
@ -322,8 +331,11 @@ export default {
},
hide($event) {
if ($event.target.className.indexOf('c-button icon-x') > -1) {
this.$emit('dismiss');
this.dismiss();
}
},
dismiss() {
this.$emit('dismiss');
}
}
};

View File

@ -172,11 +172,20 @@ export default {
mounted() {
this.setOffsets();
document.addEventListener('click', this.hide);
document.addEventListener('keydown', this.handleKeyDown);
},
beforeUnmount() {
document.removeEventListener('click', this.hide);
document.removeEventListener('keydown', this.handleKeyDown);
},
methods: {
handleKeyDown({ key }) {
if (key === 'Enter' && !this.isDisabled) {
this.submit();
} else if (key === 'Escape') {
this.dismiss();
}
},
format(ref) {
const curVal = this[ref];
this[ref] = curVal.toString().padStart(2, '0');
@ -218,13 +227,16 @@ export default {
seconds: this.endInputSecs
}
});
this.$emit('dismiss');
this.dismiss();
},
hide($event) {
if ($event.target.className.indexOf('c-button icon-x') > -1) {
this.$emit('dismiss');
this.dismiss();
}
},
dismiss() {
this.$emit('dismiss');
},
increment($ev, ref) {
$ev.preventDefault();
const step = ref === 'startInputHrs' || ref === 'endInputHrs' ? 1 : 5;

View File

@ -348,7 +348,7 @@ $colorMenuElementHilite: pullForward($colorMenuBg, 10%);
$shdwMenu: rgba(black, 0.8) 0 2px 10px;
$shdwMenuInner: inset 0 0 0 1px rgba(white, 0.2);
$shdwMenuText: none;
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$menuItemPad: 4px, 6px;
// Palettes and Swatches
$paletteItemBorderOuterColorSelected: black;

View File

@ -317,7 +317,7 @@ $colorMenuElementHilite: pullForward($colorMenuBg, 10%);
$shdwMenu: rgba(black, 0.8) 0 2px 10px;
$shdwMenuInner: inset 0 0 0 1px rgba(white, 0.2);
$shdwMenuText: none;
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$menuItemPad: 4px, 6px;
// Palettes and Swatches
$paletteItemBorderOuterColorSelected: black;

View File

@ -333,7 +333,7 @@ $colorMenuElementHilite: pullForward($colorMenuBg, 10%);
$shdwMenu: rgba(black, 0.8) 0 2px 10px;
$shdwMenuInner: inset 0 0 0 1px rgba(white, 0.2);
$shdwMenuText: none;
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$menuItemPad: 4px, 6px;
// Palettes and Swatches
$paletteItemBorderOuterColorSelected: black;

View File

@ -316,7 +316,7 @@ $colorMenuElementHilite: darken($colorMenuBg, 10%);
$shdwMenu: rgba(black, 0.8) 0 2px 10px;
$shdwMenuInner: none;
$shdwMenuText: none;
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
$menuItemPad: 4px, 6px;
// Palettes and Swatches
$paletteItemBorderOuterColorSelected: black;

View File

@ -58,9 +58,11 @@
@mixin menuInner() {
li {
@include cControl();
align-items: baseline;
justify-content: start;
cursor: pointer;
display: flex;
line-height: 1.2em;
padding: nth($menuItemPad, 1) nth($menuItemPad, 2);
white-space: nowrap;
@ -651,22 +653,21 @@ select {
padding: $interiorMarginLg;
flex-direction: row;
> [class*='__'] {
//flex: 1 1 50%;
//&:first-child {
// margin-right: $m;
//}
&__left-col {
display: flex;
flex-direction: column;
flex: 1 1 50%;
gap: $interiorMargin;
margin-right: $m;
}
&:last-child {
//border-left: 1px solid $colorInteriorBorder;
//padding-left: $m;
}
&__filter {
flex: 0 0 auto;
}
&__menu {
@include menuInner();
flex: 1 1 50%;
margin-right: $m;
flex: 1 1 auto;
overflow: auto;
ul {
@ -675,6 +676,7 @@ select {
li {
border-radius: $controlCr;
white-space: normal; // Let long names wrap
}
}

View File

@ -92,7 +92,8 @@ export default {
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
const menuOptions = {
menuClass: 'c-create-menu'
menuClass: 'c-create-menu',
filterable: true
};
this.openmct.menus.showSuperMenu(x, y, this.sortedItems, menuOptions);