mirror of https://github.com/rancher/dashboard.git
Merge pull request #13357 from rak-phillip/task/12776-table-actions
Replace ActionMenu usage for Table actions and Global Settings
This commit is contained in:
commit
07289b9f81
|
|
@ -0,0 +1,15 @@
|
|||
import ComponentPo from '@/cypress/e2e/po/components/component.po';
|
||||
|
||||
export default class ActionMenuPo extends ComponentPo {
|
||||
constructor(arg:any) {
|
||||
super(arg || cy.get('[dropdown-menu-collection]'));
|
||||
}
|
||||
|
||||
clickMenuItem(index: number) {
|
||||
return this.self().find('[dropdown-menu-item]').eq(index).click();
|
||||
}
|
||||
|
||||
getMenuItem(name: string) {
|
||||
return this.self().get('[dropdown-menu-item]').contains(name);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ export default class ListRowPo extends ComponentPo {
|
|||
* the action button could be in a different column
|
||||
*/
|
||||
actionBtn() {
|
||||
return this.self().find('.btn.actions');
|
||||
return this.self().find('[data-testid*="action-button"]');
|
||||
}
|
||||
|
||||
get(selector: string, options?: any) {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ export default class Shell extends ComponentPo {
|
|||
|
||||
openTerminal() {
|
||||
// get and click on the first row's action menu button
|
||||
cy.get(`button[data-testid="sortable-table-0-action-button"]`).first().click();
|
||||
cy.get(`[data-testid="sortable-table-0-action-button"`).first().click();
|
||||
// get and click on the action menu's first option (execute shell)
|
||||
cy.get(`li[data-testid="action-menu-0-item"]`).click();
|
||||
cy.get(`[dropdown-menu-item]`).contains('Execute Shell').click();
|
||||
this.self().get('.window.show-grid .text-success').should('contain', 'Connected');
|
||||
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import ComponentPo, { GetOptions } from '@/cypress/e2e/po/components/component.po';
|
||||
import ActionMenuPo from '@/cypress/e2e/po/components/action-menu.po';
|
||||
import ActionMenuPo from '@/cypress/e2e/po/components/action-menu-shell.po';
|
||||
import CheckboxInputPo from '@/cypress/e2e/po/components/checkbox-input.po';
|
||||
import ListRowPo from '@/cypress/e2e/po/components/list-row.po';
|
||||
import PromptRemove from '@/cypress/e2e/po/prompts/promptRemove.po';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default class ChartRepositoriesListPo extends BaseResourceList {
|
|||
}
|
||||
|
||||
closeActionMenu() {
|
||||
cy.get('body').click(0, 0); // Click outside of the action menu
|
||||
cy.get('body').type('{esc}');
|
||||
}
|
||||
|
||||
openBulkActionDropdown() {
|
||||
|
|
@ -25,8 +25,11 @@ export default class ChartRepositoriesListPo extends BaseResourceList {
|
|||
}
|
||||
|
||||
refreshRepo(repoName: string) {
|
||||
return this.resourceTable().sortableTable().rowActionMenuOpen(repoName).getMenuItem('Refresh')
|
||||
.click();
|
||||
return this.resourceTable()
|
||||
.sortableTable()
|
||||
.rowActionMenuOpen(repoName)
|
||||
.getMenuItem('Refresh')
|
||||
.click({ force: true }); // We shouldn't require force, but other methods don't work
|
||||
}
|
||||
|
||||
state(repoName: string) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export class SettingsPagePo extends RootClusterPage {
|
|||
* @returns
|
||||
*/
|
||||
actionButtonByLabel(label: string) {
|
||||
return this.advancedSettingRow(label).find('.action > button');
|
||||
return this.advancedSettingRow(label).find('[data-testid*="action-button"]');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default class PageActionsPo extends ComponentPo {
|
|||
* @returns {Cypress.Chainable}
|
||||
*/
|
||||
static open(): Cypress.Chainable {
|
||||
return cy.getId('page-actions-menu').should('be.visible').click();
|
||||
return cy.getId('page-actions-menu-action-button').should('be.visible').click();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ describe('Cluster Management Helm Repositories', { testIsolation: 'off', tags: [
|
|||
ChartRepositoriesPagePo.navTo();
|
||||
repositoriesPage.waitForPage();
|
||||
cy.intercept('PUT', `/v1/catalog.cattle.io.clusterrepos/${ this.repoName }`).as('refreshRepo');
|
||||
repositoriesPage.list().actionMenu(this.repoName).getMenuItem('Refresh').click();
|
||||
repositoriesPage.list().actionMenu(this.repoName).getMenuItem('Refresh').click({ force: true });
|
||||
cy.wait('@refreshRepo').its('response.statusCode').should('eq', 200);
|
||||
|
||||
// check list details
|
||||
|
|
@ -278,7 +278,10 @@ describe('Cluster Management Helm Repositories', { testIsolation: 'off', tags: [
|
|||
repositoriesPage.list().actionMenu(this.repoName).getMenuItem('Refresh').should('be.visible');
|
||||
// close action menu
|
||||
repositoriesPage.list().closeActionMenu();
|
||||
|
||||
// disable repo
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1500);
|
||||
repositoriesPage.list().actionMenu(this.repoName).getMenuItem('Disable').click();
|
||||
repositoriesPage.list().details(this.repoName, 1).contains('Disabled', { timeout: 10000 }).scrollIntoView()
|
||||
.should('be.visible');
|
||||
|
|
@ -287,7 +290,10 @@ describe('Cluster Management Helm Repositories', { testIsolation: 'off', tags: [
|
|||
repositoriesPage.list().actionMenu(this.repoName).getMenuItem('Refresh').should('not.exist');
|
||||
// close action menu
|
||||
repositoriesPage.list().closeActionMenu();
|
||||
|
||||
// enable repo
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1500);
|
||||
repositoriesPage.list().actionMenu(this.repoName).getMenuItem('Enable').click();
|
||||
repositoriesPage.list().details(this.repoName, 1).contains('Active', LONG_TIMEOUT_OPT).scrollIntoView()
|
||||
.should('be.visible');
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ module.exports = {
|
|||
transform: {
|
||||
'^.+\\.js$': '<rootDir>/node_modules/babel-jest', // process js with `babel-jest`
|
||||
'.*\\.(vue)$': '<rootDir>/node_modules/@vue/vue3-jest', // process `*.vue` files with `vue-jest`
|
||||
'^.+\\.vue$': './vue3JestRegisterTs.js', // point to a different transformer than vue-jest and call registerTs before exporting vue-jest
|
||||
'^.+\\.tsx?$': 'ts-jest', // process `*.ts` files with `ts-jest`
|
||||
'^.+\\.svg$': '<rootDir>/svgTransform.js' // to mock `*.svg` files
|
||||
},
|
||||
|
|
|
|||
|
|
@ -65,25 +65,30 @@ defineExpose({ focus });
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.role-link {
|
||||
&:focus, &.focused {
|
||||
outline: var(--outline-width) solid var(--border);
|
||||
box-shadow: 0 0 0 var(--outline-width) var(--outline);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
&.role-link {
|
||||
&:focus, &.focused {
|
||||
@include focus-outline;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--accent-btn);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.role-ghost {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
|
||||
&:focus, &.focused {
|
||||
outline: 2px solid var(--primary-keyboard-focus);
|
||||
@include focus-outline;
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--primary-keyboard-focus);
|
||||
@include focus-outline;
|
||||
outline-offset: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ defineProps<{
|
|||
ariaLabel?: string
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:open']);
|
||||
|
||||
const {
|
||||
isMenuOpen,
|
||||
showMenu,
|
||||
|
|
@ -35,8 +37,7 @@ const {
|
|||
setFocus,
|
||||
provideDropdownContext,
|
||||
registerDropdownCollection,
|
||||
handleKeydown,
|
||||
} = useDropdownContext();
|
||||
} = useDropdownContext(emit);
|
||||
|
||||
provideDropdownContext();
|
||||
|
||||
|
|
|
|||
|
|
@ -53,12 +53,12 @@ const findNewIndex = (shouldAdvance: boolean, activeIndex: number, itemsArr: Ele
|
|||
return newIndex;
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
emits('click');
|
||||
emits('click', e);
|
||||
close();
|
||||
};
|
||||
|
||||
|
|
@ -85,6 +85,9 @@ const handleActivate = (e: KeyboardEvent) => {
|
|||
@keydown.enter.space="handleActivate"
|
||||
@keydown.up.down.stop="handleKeydown"
|
||||
>
|
||||
<slot name="before">
|
||||
<!--Empty slot content-->
|
||||
</slot>
|
||||
<slot name="default">
|
||||
<!--Empty slot content-->
|
||||
</slot>
|
||||
|
|
@ -93,6 +96,9 @@ const handleActivate = (e: KeyboardEvent) => {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
[dropdown-menu-item] {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 9px 8px;
|
||||
margin: 0 9px;
|
||||
border-radius: 4px;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
RcDropdown,
|
||||
RcDropdownItem,
|
||||
RcDropdownSeparator,
|
||||
RcDropdownTrigger
|
||||
} from '@components/RcDropdown';
|
||||
import { RcDropdownMenuComponentProps, DropdownOption } from './types';
|
||||
import IconOrSvg from '@shell/components/IconOrSvg';
|
||||
|
||||
// eslint-disable-next-line vue/no-setup-props-destructure
|
||||
const { buttonRole = 'primary', buttonSize = '' } = defineProps<RcDropdownMenuComponentProps>();
|
||||
|
||||
const emit = defineEmits(['update:open', 'select']);
|
||||
|
||||
const hasOptions = (options: DropdownOption[]) => {
|
||||
return options.length !== undefined ? options.length : Object.keys(options).length > 0;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<rc-dropdown
|
||||
:aria-label="dropdownAriaLabel"
|
||||
@update:open="(e: boolean) => emit('update:open', e)"
|
||||
>
|
||||
<rc-dropdown-trigger
|
||||
:[buttonRole]="true"
|
||||
:[buttonSize]="true"
|
||||
:data-testid="dataTestid"
|
||||
:aria-label="buttonAriaLabel"
|
||||
>
|
||||
<i class="icon icon-actions" />
|
||||
</rc-dropdown-trigger>
|
||||
<template #dropdownCollection>
|
||||
<template
|
||||
v-for="(a) in options"
|
||||
:key="a.label"
|
||||
>
|
||||
<rc-dropdown-item
|
||||
v-if="!a.divider"
|
||||
@click="(e: MouseEvent) => emit('select', e, a)"
|
||||
>
|
||||
<template #before>
|
||||
<IconOrSvg
|
||||
v-if="a.icon || a.svg"
|
||||
:icon="a.icon"
|
||||
:src="a.svg"
|
||||
class="icon"
|
||||
color="header"
|
||||
/>
|
||||
</template>
|
||||
{{ a.label }}
|
||||
</rc-dropdown-item>
|
||||
<rc-dropdown-separator
|
||||
v-else
|
||||
/>
|
||||
</template>
|
||||
<rc-dropdown-item
|
||||
v-if="!hasOptions(options)"
|
||||
disabled
|
||||
>
|
||||
No actions available
|
||||
</rc-dropdown-item>
|
||||
</template>
|
||||
</rc-dropdown>
|
||||
</template>
|
||||
|
|
@ -2,3 +2,4 @@ export { default as RcDropdown } from './RcDropdown.vue';
|
|||
export { default as RcDropdownItem } from './RcDropdownItem.vue';
|
||||
export { default as RcDropdownSeparator } from './RcDropdownSeparator.vue';
|
||||
export { default as RcDropdownTrigger } from './RcDropdownTrigger.vue';
|
||||
export { default as RcDropdownMenu } from './RcDropdownMenu.vue';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Ref, ref } from 'vue';
|
||||
import type { RcButtonType } from '@components/RcButton';
|
||||
import { ButtonRoleProps, ButtonSizeProps } from '@components/RcButton/types';
|
||||
|
||||
export type DropdownContext = {
|
||||
handleKeydown: () => void;
|
||||
|
|
@ -20,3 +21,29 @@ export const defaultContext: DropdownContext = {
|
|||
isMenuOpen: ref(false),
|
||||
close: () => null,
|
||||
};
|
||||
|
||||
export type DropdownOption = {
|
||||
action?: string;
|
||||
divider?: boolean;
|
||||
enabled: boolean;
|
||||
icon?: string;
|
||||
svg?: string;
|
||||
label?: string;
|
||||
total: number;
|
||||
allEnabled: boolean;
|
||||
anyEnabled: boolean;
|
||||
available: number;
|
||||
bulkable?: boolean;
|
||||
bulkAction?: string;
|
||||
altAction?: string;
|
||||
weight?: number;
|
||||
}
|
||||
|
||||
export type RcDropdownMenuComponentProps = {
|
||||
options: DropdownOption[];
|
||||
buttonRole?: keyof ButtonRoleProps;
|
||||
buttonSize?: keyof ButtonSizeProps;
|
||||
buttonAriaLabel?: string;
|
||||
dropdownAriaLabel?: string;
|
||||
dataTestid?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { ref, provide, nextTick } from 'vue';
|
||||
import { ref, provide, nextTick, defineEmits } from 'vue';
|
||||
import { useDropdownCollection } from './useDropdownCollection';
|
||||
import { RcButtonType } from '@components/RcButton';
|
||||
|
||||
const rcDropdownEmits = defineEmits(['update:open']);
|
||||
|
||||
/**
|
||||
* Composable that provides the context for a dropdown menu. Includes methods
|
||||
* and state for managing the dropdown's visibility, focus, and keyboard
|
||||
|
|
@ -11,7 +13,7 @@ import { RcButtonType } from '@components/RcButton';
|
|||
* @returns Dropdown context methods and state. Used for programmatic
|
||||
* interactions and setting focus.
|
||||
*/
|
||||
export const useDropdownContext = () => {
|
||||
export const useDropdownContext = (emit: typeof rcDropdownEmits) => {
|
||||
const {
|
||||
dropdownItems,
|
||||
firstDropdownItem,
|
||||
|
|
@ -30,6 +32,7 @@ export const useDropdownContext = () => {
|
|||
didKeydown.value = false;
|
||||
}
|
||||
isMenuOpen.value = show;
|
||||
emit('update:open', show);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export {
|
|||
RcDropdown,
|
||||
RcDropdownItem,
|
||||
RcDropdownSeparator,
|
||||
RcDropdownTrigger
|
||||
RcDropdownTrigger,
|
||||
RcDropdownMenu
|
||||
} from './components/RcDropdown';
|
||||
export * from './components/Form';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import { isAlternate } from '@shell/utils/platform';
|
||||
import { RcDropdownMenu } from '@components/RcDropdown';
|
||||
import { ButtonRoleProps, ButtonSizeProps } from '@components/RcButton/types';
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const options = computed(() => store.getters['action-menu/optionsArray']);
|
||||
|
||||
type RcDropdownMenuComponentProps = {
|
||||
buttonRole?: keyof ButtonRoleProps;
|
||||
buttonSize?: keyof ButtonSizeProps;
|
||||
buttonAriaLabel?: string;
|
||||
dropdownAriaLabel?: string;
|
||||
dataTestid?: string;
|
||||
resource: Object;
|
||||
}
|
||||
|
||||
const props = defineProps <RcDropdownMenuComponentProps>();
|
||||
|
||||
const openChanged = (event: boolean) => {
|
||||
if (event) {
|
||||
store.dispatch('action-menu/setResource', props.resource);
|
||||
}
|
||||
};
|
||||
|
||||
const execute = (action: any, event: MouseEvent, args?: any) => {
|
||||
if (action.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this will come from extensions...
|
||||
if (action.invoke) {
|
||||
const fn = action.invoke;
|
||||
|
||||
if (fn && action.enabled) {
|
||||
const resources = store.getters['action-menu/resources'];
|
||||
const opts = {
|
||||
event,
|
||||
action,
|
||||
isAlt: isAlternate(event)
|
||||
};
|
||||
|
||||
if (resources.length === 1) {
|
||||
fn.apply(this, [opts, resources]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the state of this component is controlled
|
||||
// by Vuex, mutate the store when an action is clicked.
|
||||
const opts = { alt: isAlternate(event) };
|
||||
|
||||
store.dispatch('action-menu/execute', {
|
||||
action, args, opts
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<rc-dropdown-menu
|
||||
:button-role="buttonRole || 'link'"
|
||||
:button-size="buttonSize || 'small'"
|
||||
:button-aria-label="buttonAriaLabel"
|
||||
:dropdown-aria-label="dropdownAriaLabel"
|
||||
:options="options"
|
||||
:data-testid="dataTestid"
|
||||
@update:open="openChanged"
|
||||
@select="(e: MouseEvent, option: object) => execute(option, e)"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -23,6 +23,7 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|||
import { getParent } from '@shell/utils/dom';
|
||||
import { FORMATTERS } from '@shell/components/SortableTable/sortable-config';
|
||||
import ButtonMultiAction from '@shell/components/ButtonMultiAction.vue';
|
||||
import ActionMenu from '@shell/components/ActionMenuShell.vue';
|
||||
|
||||
// Uncomment for table performance debugging
|
||||
// import tableDebug from './debug';
|
||||
|
|
@ -58,6 +59,7 @@ export default {
|
|||
ActionDropdown,
|
||||
LabeledSelect,
|
||||
ButtonMultiAction,
|
||||
ActionMenu,
|
||||
},
|
||||
mixins: [
|
||||
filtering,
|
||||
|
|
@ -1468,23 +1470,16 @@ export default {
|
|||
</template>
|
||||
<td
|
||||
v-if="rowActions"
|
||||
align="middle"
|
||||
>
|
||||
<slot
|
||||
name="row-actions"
|
||||
:row="row.row"
|
||||
:index="i"
|
||||
>
|
||||
<ButtonMultiAction
|
||||
:id="`actionButton+${i}+${(row.row && row.row.name) ? row.row.name : ''}`"
|
||||
:ref="`actionButton${i}`"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
:aria-label="t('sortableTable.tableActionsLabel', { resource: row?.row?.id || '' })"
|
||||
<ActionMenu
|
||||
:resource="row.row"
|
||||
:data-testid="componentTestid + '-' + i + '-action-button'"
|
||||
:borderless="true"
|
||||
@click="handleActionButtonClick(i, $event)"
|
||||
@keyup.enter="handleActionButtonClick(i, $event)"
|
||||
@keyup.space="handleActionButtonClick(i, $event)"
|
||||
:button-aria-label="t('sortableTable.tableActionsLabel', { resource: row?.row?.id || '' })"
|
||||
/>
|
||||
</slot>
|
||||
</td>
|
||||
|
|
@ -1782,7 +1777,6 @@ export default {
|
|||
min-width: 400px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
outline: 1px solid var(--border);
|
||||
overflow: hidden;
|
||||
background: var(--sortable-table-bg);
|
||||
border-radius: 4px;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,51 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import {
|
||||
RcDropdown,
|
||||
RcDropdownItem,
|
||||
RcDropdownSeparator,
|
||||
RcDropdownTrigger
|
||||
} from '@components/RcDropdown';
|
||||
|
||||
const isPageActionMenuOpen = ref(false);
|
||||
|
||||
const showPageActionsMenu = (show: boolean) => {
|
||||
isPageActionMenuOpen.value = show;
|
||||
};
|
||||
import { RcDropdownMenu } from '@components/RcDropdown';
|
||||
|
||||
const store = useStore();
|
||||
const pageActions = computed(() => store.getters.pageActions);
|
||||
const pageAction = (action: string) => {
|
||||
const pageAction = (_event: Event, action: string) => {
|
||||
store.dispatch('handlePageAction', action);
|
||||
showPageActionsMenu(false);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<rc-dropdown :aria-label="t('nav.actionMenu.label')">
|
||||
<rc-dropdown-trigger
|
||||
tertiary
|
||||
data-testid="page-actions-menu"
|
||||
:aria-label="t('nav.actionMenu.button.label')"
|
||||
>
|
||||
<i class="icon icon-actions" />
|
||||
</rc-dropdown-trigger>
|
||||
<template #dropdownCollection>
|
||||
<template
|
||||
v-for="(a) in pageActions"
|
||||
:key="a.label"
|
||||
>
|
||||
<rc-dropdown-item
|
||||
v-if="!a.separator"
|
||||
@click="pageAction(a)"
|
||||
>
|
||||
{{ a.labelKey ? t(a.labelKey) : a.label }}
|
||||
</rc-dropdown-item>
|
||||
<rc-dropdown-separator
|
||||
v-else
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</rc-dropdown>
|
||||
<rc-dropdown-menu
|
||||
:options="pageActions"
|
||||
:button-aria-label="t('nav.actionMenu.label')"
|
||||
:dropdown-aria-label="t('nav.actionMenu.button.label')"
|
||||
data-testid="page-actions-menu-action-button"
|
||||
button-role="tertiary"
|
||||
@select="pageAction"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -85,8 +85,8 @@ export default {
|
|||
|
||||
if (canSetAsHome) {
|
||||
pageActions.push({
|
||||
labelKey: 'nav.header.setLoginPage',
|
||||
action: SET_LOGIN_ACTION
|
||||
label: this.t('nav.header.setLoginPage'),
|
||||
action: SET_LOGIN_ACTION
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@ import { ALLOWED_SETTINGS } from '@shell/config/settings';
|
|||
import { Banner } from '@components/Banner';
|
||||
import Loading from '@shell/components/Loading';
|
||||
import { VIEW_IN_API } from '@shell/store/prefs';
|
||||
import ActionMenu from '@shell/components/ActionMenuShell.vue';
|
||||
|
||||
export default {
|
||||
components: { Banner, Loading },
|
||||
components: {
|
||||
Banner, Loading, ActionMenu
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
const viewInApi = this.$store.getters['prefs/get'](VIEW_IN_API);
|
||||
|
|
@ -64,31 +67,8 @@ export default {
|
|||
|
||||
computed: {
|
||||
...mapGetters({ t: 'i18n/t' }),
|
||||
...mapGetters({
|
||||
// Use either these Vuex getters
|
||||
// OR the props to set the action menu state,
|
||||
// but don't use both.
|
||||
targetElem: 'action-menu/elem',
|
||||
shouldShow: 'action-menu/showing',
|
||||
}),
|
||||
...mapGetters({ options: 'action-menu/optionsArray' }),
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleActionMenu(e, setting) {
|
||||
const actionElement = e.srcElement;
|
||||
|
||||
if (!this.targetElem && !this.shouldShow) {
|
||||
this.$store.commit(`action-menu/show`, {
|
||||
resources: setting.data,
|
||||
elem: actionElement
|
||||
});
|
||||
} else if (this.targetElem === actionElement && this.shouldShow) {
|
||||
// this condition is needed so that we can "toggle" the action menu with
|
||||
// the keyboard for accessibility (row action menu)
|
||||
this.$store.commit('action-menu/hide');
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -104,8 +84,8 @@ export default {
|
|||
</div>
|
||||
</Banner>
|
||||
<div
|
||||
v-for="(setting, i) in settings"
|
||||
:key="i"
|
||||
v-for="(setting) in settings"
|
||||
:key="setting.id"
|
||||
class="advanced-setting mb-20"
|
||||
:data-testid="`advanced-setting__option-${setting.id}`"
|
||||
>
|
||||
|
|
@ -128,20 +108,12 @@ export default {
|
|||
v-if="setting.hasActions"
|
||||
class="action"
|
||||
>
|
||||
<button
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
type="button"
|
||||
class="btn btn-sm role-multi-action actions"
|
||||
role="button"
|
||||
:aria-label="t('advancedSettings.edit.moreActions', { setting: setting.id })"
|
||||
@click="toggleActionMenu($event, setting)"
|
||||
>
|
||||
<i
|
||||
class="icon icon-actions"
|
||||
:alt="t('advancedSettings.edit.moreActions', { setting: setting.id })"
|
||||
/>
|
||||
</button>
|
||||
<action-menu
|
||||
:resource="setting.data"
|
||||
:button-aria-label="t('advancedSettings.edit.label')"
|
||||
data-testid="action-button"
|
||||
button-role="tertiary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div value>
|
||||
|
|
|
|||
|
|
@ -53,17 +53,17 @@ export default defineComponent({
|
|||
// Page actions don't change on the Home Page
|
||||
pageActions: [
|
||||
{
|
||||
labelKey: 'nav.header.setLoginPage',
|
||||
action: SET_LOGIN_ACTION
|
||||
label: this.t('nav.header.setLoginPage'),
|
||||
action: SET_LOGIN_ACTION
|
||||
},
|
||||
{ separator: true },
|
||||
{ divider: true },
|
||||
{
|
||||
labelKey: 'nav.header.showHideBanner',
|
||||
action: SHOW_HIDE_BANNER_ACTION
|
||||
label: this.t('nav.header.showHideBanner'),
|
||||
action: SHOW_HIDE_BANNER_ACTION
|
||||
},
|
||||
{
|
||||
labelKey: 'nav.header.restoreCards',
|
||||
action: RESET_CARDS_ACTION
|
||||
label: this.t('nav.header.restoreCards'),
|
||||
action: RESET_CARDS_ACTION
|
||||
},
|
||||
],
|
||||
vendor: getVendor(),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const getters = {
|
|||
event: (state) => state.event,
|
||||
resources: (state) => state.resources,
|
||||
|
||||
options(state) {
|
||||
optionsArray(state) {
|
||||
let selected = state.resources;
|
||||
|
||||
if ( !selected ) {
|
||||
|
|
@ -50,7 +50,10 @@ export const getters = {
|
|||
|
||||
const out = _filter(map);
|
||||
|
||||
return { ...out };
|
||||
return [...out];
|
||||
},
|
||||
options(_state, getters) {
|
||||
return { ...getters.optionsArray };
|
||||
},
|
||||
|
||||
};
|
||||
|
|
@ -144,12 +147,19 @@ export const mutations = {
|
|||
|
||||
state.modalData = data;
|
||||
},
|
||||
|
||||
SET_RESOURCE(state, resources) {
|
||||
state.resources = !isArray(resources) ? [resources] : resources;
|
||||
}
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
execute({ state }, { action, args, opts }) {
|
||||
return _execute(state.resources, action, args, opts);
|
||||
},
|
||||
setResource({ commit }, resource) {
|
||||
commit('SET_RESOURCE', resource);
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Unit tests are failing with the following error:
|
||||
*
|
||||
* ```
|
||||
* [@vue/compiler-sfc] No fs option provided to `compileScript` in non-Node environment. File system access is required for resolving imported types.
|
||||
* ```
|
||||
*
|
||||
* It seems TypeScript does not populate ts.sys when loaded in Jest. In order to
|
||||
* resolve this issue, we can use the hack below to point to a different
|
||||
* transformer than vue-jest and call registerTs before exporting vue-jest.
|
||||
*
|
||||
* SEE: https://github.com/vuejs/core/issues/8301
|
||||
*/
|
||||
require('@vue/compiler-sfc').registerTS(() => require('typescript'));
|
||||
module.exports = require('@vue/vue3-jest');
|
||||
Loading…
Reference in New Issue