From ac09b3b5c8ecd122b778bb9c17f2aef1f00f6b05 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Daniel=20Kuty=C5=82a?= Date: Fri, 6 Nov 2020 22:15:21 +0100 Subject: [PATCH] 16005: Open collection and project in new tab feature added MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła 1~ --- src/common/array-utils.ts | 18 ++++ src/common/copy-store.ts | 28 ++++++ src/common/redirect-to.ts | 31 +++++-- .../open-in-new-tab.actions.ts | 40 +++++--- src/store/store.ts | 1 + .../advanced-tab-dialog.tsx | 2 +- .../action-sets/collection-action-set.ts | 19 +++- .../collection-admin-action-set.ts | 20 +++- .../collection-resource-action-set.ts | 91 +++++++++++++++++++ .../action-sets/project-action-set.ts | 19 +++- .../action-sets/project-admin-action-set.ts | 20 +++- .../context-menu/actions/helpers.ts | 10 +- .../context-menu/context-menu.tsx | 4 +- 13 files changed, 272 insertions(+), 31 deletions(-) create mode 100644 src/common/array-utils.ts create mode 100644 src/common/copy-store.ts create mode 100644 src/views-components/context-menu/action-sets/collection-resource-action-set.ts diff --git a/src/common/array-utils.ts b/src/common/array-utils.ts new file mode 100644 index 00000000..a92461c8 --- /dev/null +++ b/src/common/array-utils.ts @@ -0,0 +1,18 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +export const sortByProperty = (propName: string) => (obj1: any, obj2: any) => { + const prop1 = obj1[propName]; + const prop2 = obj2[propName]; + + if (prop1 > prop2) { + return 1; + } + + if (prop1 < prop2) { + return -1; + } + + return 0; +}; diff --git a/src/common/copy-store.ts b/src/common/copy-store.ts new file mode 100644 index 00000000..9c7f3875 --- /dev/null +++ b/src/common/copy-store.ts @@ -0,0 +1,28 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +const STORE_COPY_KEY = 'storeCopy'; + +export const copyStore = (store: any) => { + const { localStorage } = window; + const state = store.getState(); + const storeCopy = JSON.parse(JSON.stringify(state)); + storeCopy.router.location.pathname = '/'; + + if (localStorage) { + localStorage.setItem(STORE_COPY_KEY, JSON.stringify(storeCopy)); + } +}; + +export const restoreStore = () => { + let storeCopy = null; + const { localStorage } = window; + + if (localStorage && localStorage.getItem(STORE_COPY_KEY)) { + storeCopy = localStorage.getItem(STORE_COPY_KEY); + localStorage.removeItem(STORE_COPY_KEY); + } + + return storeCopy; +}; diff --git a/src/common/redirect-to.ts b/src/common/redirect-to.ts index f5ece21b..baf44711 100644 --- a/src/common/redirect-to.ts +++ b/src/common/redirect-to.ts @@ -5,14 +5,22 @@ import { Config } from './config'; const REDIRECT_TO_KEY = 'redirectTo'; +export const REDIRECT_TO_APPLY_TO_PATH = 'redirectToApplyToPath'; export const storeRedirects = () => { - if (window.location.href.indexOf(REDIRECT_TO_KEY) > -1) { - const { location: { href }, localStorage } = window; - const redirectUrl = href.split(`${REDIRECT_TO_KEY}=`)[1]; + let redirectUrl; + const { location: { href }, localStorage } = window; + const applyToPath = href.indexOf(REDIRECT_TO_APPLY_TO_PATH) > -1; - if (localStorage) { - localStorage.setItem(REDIRECT_TO_KEY, redirectUrl); + if (href.indexOf(REDIRECT_TO_KEY) > -1) { + redirectUrl = href.split(`${REDIRECT_TO_KEY}=`)[1]; + } + + if (localStorage && redirectUrl) { + localStorage.setItem(REDIRECT_TO_KEY, redirectUrl); + + if (applyToPath) { + localStorage.setItem(REDIRECT_TO_APPLY_TO_PATH, 'true'); } } }; @@ -24,9 +32,18 @@ export const handleRedirects = (token: string, config: Config) => { if (localStorage && localStorage.getItem(REDIRECT_TO_KEY)) { const redirectUrl = localStorage.getItem(REDIRECT_TO_KEY); localStorage.removeItem(REDIRECT_TO_KEY); + const applyToPath = localStorage.getItem(REDIRECT_TO_APPLY_TO_PATH); + if (redirectUrl) { - const sep = redirectUrl.indexOf("?") > -1 ? "&" : "?"; - window.location.href = `${keepWebServiceUrl}${redirectUrl}${sep}api_token=${token}`; + if (applyToPath === 'true') { + localStorage.removeItem(REDIRECT_TO_APPLY_TO_PATH); + setTimeout(() => { + window.location.pathname = redirectUrl; + }, 0); + } else { + const sep = redirectUrl.indexOf("?") > -1 ? "&" : "?"; + window.location.href = `${keepWebServiceUrl}${redirectUrl}${sep}api_token=${token}`; + } } } }; diff --git a/src/store/open-in-new-tab/open-in-new-tab.actions.ts b/src/store/open-in-new-tab/open-in-new-tab.actions.ts index 17ba7402..c6462ea1 100644 --- a/src/store/open-in-new-tab/open-in-new-tab.actions.ts +++ b/src/store/open-in-new-tab/open-in-new-tab.actions.ts @@ -2,24 +2,36 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { Dispatch } from 'redux'; +import * as copy from 'copy-to-clipboard'; import { ResourceKind } from '~/models/resource'; -import { unionize, ofType } from '~/common/unionize'; +import { getClipboardUrl } from '~/views-components/context-menu/actions/helpers'; -export const openInNewTabActions = unionize({ - COPY_STORE: ofType<{}>(), - OPEN_COLLECTION_IN_NEW_TAB: ofType(), - OPEN_PROJECT_IN_NEW_TAB: ofType() -}); - -export const openInNewTabAction = (resource: any) => (dispatch: Dispatch) => { +const getUrl = (resource: any) => { + let url = null; const { uuid, kind } = resource; - dispatch(openInNewTabActions.COPY_STORE()); - if (kind === ResourceKind.COLLECTION) { - dispatch(openInNewTabActions.OPEN_COLLECTION_IN_NEW_TAB(uuid)); - } else if (kind === ResourceKind.PROJECT) { - dispatch(openInNewTabActions.OPEN_PROJECT_IN_NEW_TAB(uuid)); + url = `/collections/${uuid}`; + } + if (kind === ResourceKind.PROJECT) { + url = `/projects/${uuid}`; + } + + return url; +}; + +export const openInNewTabAction = (resource: any) => () => { + const url = getUrl(resource); + + if (url) { + window.open(`${window.location.origin}${url}`, '_blank'); + } +}; + +export const copyToClipboardAction = (resource: any) => () => { + const url = getUrl(resource); + + if (url) { + copy(getClipboardUrl(url, false)); } }; \ No newline at end of file diff --git a/src/store/store.ts b/src/store/store.ts index 7beb099c..517368aa 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -163,6 +163,7 @@ export function configureStore(history: History, services: ServiceRepository, co collectionsContentAddress, subprocessMiddleware, ]; + const enhancer = composeEnhancers(applyMiddleware(redirectToMiddleware, ...middlewares)); return createStore(rootReducer, enhancer); } diff --git a/src/views-components/advanced-tab-dialog/advanced-tab-dialog.tsx b/src/views-components/advanced-tab-dialog/advanced-tab-dialog.tsx index fdd0bd70..7e90bfd2 100644 --- a/src/views-components/advanced-tab-dialog/advanced-tab-dialog.tsx +++ b/src/views-components/advanced-tab-dialog/advanced-tab-dialog.tsx @@ -87,7 +87,7 @@ export const AdvancedTabDialog = compose( {value === 4 && dialogContent(curlHeader, curlExample, classes)} - diff --git a/src/views-components/context-menu/action-sets/collection-action-set.ts b/src/views-components/context-menu/action-sets/collection-action-set.ts index 7fa6f224..4b6b9224 100644 --- a/src/views-components/context-menu/action-sets/collection-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-action-set.ts @@ -5,7 +5,7 @@ import { ContextMenuActionSet } from "../context-menu-action-set"; import { ToggleFavoriteAction } from "../actions/favorite-action"; import { toggleFavorite } from "~/store/favorites/favorites-actions"; -import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon } from "~/components/icon/icon"; +import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from "~/components/icon/icon"; import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions"; import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions'; @@ -15,16 +15,32 @@ import { toggleCollectionTrashed } from "~/store/trash/trash-actions"; import { openSharingDialog } from '~/store/sharing-dialog/sharing-dialog-actions'; import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab"; import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action'; +import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions"; export const readOnlyCollectionActionSet: ContextMenuActionSet = [[ { component: ToggleFavoriteAction, + name: 'ToggleFavoriteAction', execute: (dispatch, resource) => { dispatch(toggleFavorite(resource)).then(() => { dispatch(favoritePanelActions.REQUEST_ITEMS()); }); } }, + { + icon: OpenIcon, + name: "Open in new tab", + execute: (dispatch, resource) => { + dispatch(openInNewTabAction(resource)); + } + }, + { + icon: Link, + name: "Copy to clipboard", + execute: (dispatch, resource) => { + dispatch(copyToClipboardAction(resource)); + } + }, { icon: CopyIcon, name: "Make a copy", @@ -73,6 +89,7 @@ export const collectionActionSet: ContextMenuActionSet = [ }, { component: ToggleTrashAction, + name: 'ToggleTrashAction', execute: (dispatch, resource) => { dispatch(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!)); } diff --git a/src/views-components/context-menu/action-sets/collection-admin-action-set.ts b/src/views-components/context-menu/action-sets/collection-admin-action-set.ts index 10a839d8..7b39d749 100644 --- a/src/views-components/context-menu/action-sets/collection-admin-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-admin-action-set.ts @@ -5,7 +5,7 @@ import { ContextMenuActionSet } from "../context-menu-action-set"; import { ToggleFavoriteAction } from "../actions/favorite-action"; import { toggleFavorite } from "~/store/favorites/favorites-actions"; -import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon } from "~/components/icon/icon"; +import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from "~/components/icon/icon"; import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions"; import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions'; @@ -18,6 +18,7 @@ import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action'; import { TogglePublicFavoriteAction } from "~/views-components/context-menu/actions/public-favorite-action"; import { publicFavoritePanelActions } from "~/store/public-favorites-panel/public-favorites-action"; import { togglePublicFavorite } from "~/store/public-favorites/public-favorites-actions"; +import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions"; export const collectionAdminActionSet: ContextMenuActionSet = [[ { @@ -27,6 +28,20 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[ dispatch(openCollectionUpdateDialog(resource)); } }, + { + icon: OpenIcon, + name: "Open in new tab", + execute: (dispatch, resource) => { + dispatch(openInNewTabAction(resource)); + } + }, + { + icon: Link, + name: "Copy to clipboard", + execute: (dispatch, resource) => { + dispatch(copyToClipboardAction(resource)); + } + }, { icon: ShareIcon, name: "Share", @@ -36,6 +51,7 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[ }, { component: ToggleFavoriteAction, + name: 'ToggleFavoriteAction', execute: (dispatch, resource) => { dispatch(toggleFavorite(resource)).then(() => { dispatch(favoritePanelActions.REQUEST_ITEMS()); @@ -44,6 +60,7 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[ }, { component: TogglePublicFavoriteAction, + name: 'TogglePublicFavoriteAction', execute: (dispatch, resource) => { dispatch(togglePublicFavorite(resource)).then(() => { dispatch(publicFavoritePanelActions.REQUEST_ITEMS()); @@ -79,6 +96,7 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[ }, { component: ToggleTrashAction, + name: 'ToggleTrashAction', execute: (dispatch, resource) => { dispatch(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!)); } diff --git a/src/views-components/context-menu/action-sets/collection-resource-action-set.ts b/src/views-components/context-menu/action-sets/collection-resource-action-set.ts new file mode 100644 index 00000000..5bd362f5 --- /dev/null +++ b/src/views-components/context-menu/action-sets/collection-resource-action-set.ts @@ -0,0 +1,91 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ContextMenuActionSet } from "../context-menu-action-set"; +import { ToggleFavoriteAction } from "../actions/favorite-action"; +import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action"; +import { toggleFavorite } from "~/store/favorites/favorites-actions"; +import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon, OpenIcon } from '~/components/icon/icon'; +import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions"; +import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; +import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions'; +import { openCollectionCopyDialog } from '~/store/collections/collection-copy-actions'; +import { toggleCollectionTrashed } from "~/store/trash/trash-actions"; +import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions"; +import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab'; +import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action'; +import { openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions"; + +export const collectionResourceActionSet: ContextMenuActionSet = [[ + { + icon: RenameIcon, + name: "Edit collection", + execute: (dispatch, resource) => { + dispatch(openCollectionUpdateDialog(resource)); + } + }, + { + icon: ShareIcon, + name: "Share", + execute: (dispatch, { uuid }) => { + dispatch(openSharingDialog(uuid)); + } + }, + { + component: ToggleFavoriteAction, + execute: (dispatch, resource) => { + dispatch(toggleFavorite(resource)).then(() => { + dispatch(favoritePanelActions.REQUEST_ITEMS()); + }); + } + }, + { + icon: OpenIcon, + name: "Open in new tab", + execute: (dispatch, resource) => { + dispatch(openInNewTabAction(resource)); + } + }, + { + icon: MoveToIcon, + name: "Move to", + execute: (dispatch, resource) => { + dispatch(openMoveCollectionDialog(resource)); + } + }, + { + icon: CopyIcon, + name: "Copy to project", + execute: (dispatch, resource) => { + dispatch(openCollectionCopyDialog(resource)); + } + }, + { + icon: DetailsIcon, + name: "View details", + execute: dispatch => { + dispatch(toggleDetailsPanel()); + } + }, + { + icon: AdvancedIcon, + name: "Advanced", + execute: (dispatch, resource) => { + dispatch(openAdvancedTabDialog(resource.uuid)); + } + }, + { + component: ToggleTrashAction, + execute: (dispatch, resource) => { + dispatch(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!)); + } + }, + // { + // icon: RemoveIcon, + // name: "Remove", + // execute: (dispatch, resource) => { + // // add code + // } + // } +]]; diff --git a/src/views-components/context-menu/action-sets/project-action-set.ts b/src/views-components/context-menu/action-sets/project-action-set.ts index 4f92aeb8..c0b925c2 100644 --- a/src/views-components/context-menu/action-sets/project-action-set.ts +++ b/src/views-components/context-menu/action-sets/project-action-set.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import { ContextMenuActionSet } from "../context-menu-action-set"; -import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon } from '~/components/icon/icon'; +import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from '~/components/icon/icon'; import { ToggleFavoriteAction } from "../actions/favorite-action"; import { toggleFavorite } from "~/store/favorites/favorites-actions"; import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; @@ -16,16 +16,32 @@ import { ShareIcon } from '~/components/icon/icon'; import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions"; import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab"; import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action'; +import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions"; export const readOnlyProjectActionSet: ContextMenuActionSet = [[ { component: ToggleFavoriteAction, + name: 'ToggleFavoriteAction', execute: (dispatch, resource) => { dispatch(toggleFavorite(resource)).then(() => { dispatch(favoritePanelActions.REQUEST_ITEMS()); }); } }, + { + icon: OpenIcon, + name: "Open in new tab", + execute: (dispatch, resource) => { + dispatch(openInNewTabAction(resource)); + } + }, + { + icon: Link, + name: "Copy to clipboard", + execute: (dispatch, resource) => { + dispatch(copyToClipboardAction(resource)); + } + }, { icon: DetailsIcon, name: "View details", @@ -75,6 +91,7 @@ export const projectActionSet: ContextMenuActionSet = [ }, { component: ToggleTrashAction, + name: 'ToggleTrashAction', execute: (dispatch, resource) => { dispatch(toggleProjectTrashed(resource.uuid, resource.ownerUuid, resource.isTrashed!!)); } diff --git a/src/views-components/context-menu/action-sets/project-admin-action-set.ts b/src/views-components/context-menu/action-sets/project-admin-action-set.ts index f6185804..398864dc 100644 --- a/src/views-components/context-menu/action-sets/project-admin-action-set.ts +++ b/src/views-components/context-menu/action-sets/project-admin-action-set.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import { ContextMenuActionSet } from "../context-menu-action-set"; -import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon } from '~/components/icon/icon'; +import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from '~/components/icon/icon'; import { ToggleFavoriteAction } from "../actions/favorite-action"; import { toggleFavorite } from "~/store/favorites/favorites-actions"; import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; @@ -19,6 +19,7 @@ import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action'; import { TogglePublicFavoriteAction } from "~/views-components/context-menu/actions/public-favorite-action"; import { togglePublicFavorite } from "~/store/public-favorites/public-favorites-actions"; import { publicFavoritePanelActions } from "~/store/public-favorites-panel/public-favorites-action"; +import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions"; export const projectAdminActionSet: ContextMenuActionSet = [[ { @@ -28,6 +29,20 @@ export const projectAdminActionSet: ContextMenuActionSet = [[ dispatch(openProjectCreateDialog(resource.uuid)); } }, + { + icon: OpenIcon, + name: "Open in new tab", + execute: (dispatch, resource) => { + dispatch(openInNewTabAction(resource)); + } + }, + { + icon: Link, + name: "Copy to clipboard", + execute: (dispatch, resource) => { + dispatch(copyToClipboardAction(resource)); + } + }, { icon: RenameIcon, name: "Edit project", @@ -44,6 +59,7 @@ export const projectAdminActionSet: ContextMenuActionSet = [[ }, { component: ToggleFavoriteAction, + name: 'ToggleFavoriteAction', execute: (dispatch, resource) => { dispatch(toggleFavorite(resource)).then(() => { dispatch(favoritePanelActions.REQUEST_ITEMS()); @@ -52,6 +68,7 @@ export const projectAdminActionSet: ContextMenuActionSet = [[ }, { component: TogglePublicFavoriteAction, + name: 'TogglePublicFavoriteAction', execute: (dispatch, resource) => { dispatch(togglePublicFavorite(resource)).then(() => { dispatch(publicFavoritePanelActions.REQUEST_ITEMS()); @@ -88,6 +105,7 @@ export const projectAdminActionSet: ContextMenuActionSet = [[ }, { component: ToggleTrashAction, + name: 'ToggleTrashAction', execute: (dispatch, resource) => { dispatch(toggleProjectTrashed(resource.uuid, resource.ownerUuid, resource.isTrashed!!)); } diff --git a/src/views-components/context-menu/actions/helpers.ts b/src/views-components/context-menu/actions/helpers.ts index 8dfcaca0..578af205 100644 --- a/src/views-components/context-menu/actions/helpers.ts +++ b/src/views-components/context-menu/actions/helpers.ts @@ -2,7 +2,9 @@ // // SPDX-License-Identifier: AGPL-3.0 -export const sanitizeToken = (href: string, tokenAsQueryParam: boolean = true): string => { +import { REDIRECT_TO_APPLY_TO_PATH } from "~/common/redirect-to"; + +export const sanitizeToken = (href: string, tokenAsQueryParam = true): string => { const [prefix, suffix] = href.split('/t='); const [token1, token2, token3, ...rest] = suffix.split('/'); const token = `${token1}/${token2}/${token3}`; @@ -11,9 +13,9 @@ export const sanitizeToken = (href: string, tokenAsQueryParam: boolean = true): return `${[prefix, ...rest].join('/')}${tokenAsQueryParam ? `${sep}api_token=${token}` : ''}`; }; -export const getClipboardUrl = (href: string): string => { +export const getClipboardUrl = (href: string, shouldSanitizeToken = true): string => { const { origin } = window.location; - const url = sanitizeToken(href, false); + const url = shouldSanitizeToken ? sanitizeToken(href, false) : href; - return `${origin}?redirectTo=${url}`; + return `${origin}${!shouldSanitizeToken ? `?${REDIRECT_TO_APPLY_TO_PATH}=true&` : `?`}redirectTo=${url}`; }; diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx index 43474dd1..b86498a0 100644 --- a/src/views-components/context-menu/context-menu.tsx +++ b/src/views-components/context-menu/context-menu.tsx @@ -10,6 +10,7 @@ import { createAnchorAt } from "~/components/popover/helpers"; import { ContextMenuActionSet, ContextMenuAction } from "./context-menu-action-set"; import { Dispatch } from "redux"; import { memoize } from 'lodash'; +import { sortByProperty } from "~/common/array-utils"; type DataProps = Pick & { resource?: ContextMenuResource }; const mapStateToProps = (state: RootState): DataProps => { const { open, position, resource } = state.contextMenu; @@ -53,7 +54,8 @@ export const ContextMenu = connect(mapStateToProps, mapDispatchToProps, mergePro const menuActionSets = new Map(); export const addMenuActionSet = (name: string, itemSet: ContextMenuActionSet) => { - menuActionSets.set(name, itemSet); + const sorted = itemSet.map(items => items.sort(sortByProperty('name'))); + menuActionSets.set(name, sorted); }; const emptyActionSet: ContextMenuActionSet = []; -- 2.30.2