diff --git a/webapp/packages/core-blocks/src/Menu/Menu.tsx b/webapp/packages/core-blocks/src/Menu/Menu.tsx index 4bdc61dc9f..68f3a0cdad 100644 --- a/webapp/packages/core-blocks/src/Menu/Menu.tsx +++ b/webapp/packages/core-blocks/src/Menu/Menu.tsx @@ -16,7 +16,7 @@ import { useCombinedRef } from '../useCombinedRef.js'; import { useObjectRef } from '../useObjectRef.js'; import { useS } from '../useS.js'; import style from './Menu.module.css'; -import { MenuPanel } from './MenuPanel.js'; +import { type IMenuPanelProps, MenuPanel } from './MenuPanel.js'; import { type IMenuState, MenuStateContext } from './MenuStateContext.js'; import type { IMouseContextMenu } from './useMouseContextMenu.js'; @@ -33,6 +33,7 @@ interface IMenuProps extends React.ButtonHTMLAttributes { rtl?: boolean; hasBindings?: boolean; panelAvailable?: boolean; + panelProps?: Partial; getHasBindings?: () => boolean; onVisibleSwitch?: (visible: boolean) => void; } @@ -55,6 +56,7 @@ export const Menu = observer( modal, submenu, rtl, + panelProps, className, ...props }, @@ -153,6 +155,7 @@ export const Menu = observer( panelAvailable={panelAvailable} hasBindings={hasBindings} getHasBindings={getHasBindings} + {...panelProps} > {items} @@ -187,6 +190,7 @@ export const Menu = observer( panelAvailable={panelAvailable} hasBindings={hasBindings} getHasBindings={getHasBindings} + {...panelProps} > {items} diff --git a/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx b/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx index 1c915a19f2..070bb5f67f 100644 --- a/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx +++ b/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx @@ -11,12 +11,13 @@ import { Menu, type MenuStateReturn } from 'reakit'; import { ErrorBoundary } from '../ErrorBoundary.js'; import { getComputed } from '../getComputed.js'; +import { useTranslate } from '../localization/useTranslate.js'; import { s } from '../s.js'; import { useS } from '../useS.js'; import { MenuEmptyItem } from './MenuEmptyItem.js'; import style from './MenuPanel.module.css'; -export interface IMenuPanelProps { +export interface IMenuPanelProps extends Omit, 'children'> { label: string; menu: MenuStateReturn; // from reakit useMenuState panelAvailable?: boolean; @@ -25,11 +26,14 @@ export interface IMenuPanelProps { children: React.ReactNode | (() => React.ReactNode); rtl?: boolean; submenu?: boolean; - className?: string; } export const MenuPanel = observer( - forwardRef(function MenuPanel({ label, menu, submenu, panelAvailable = true, rtl, getHasBindings, hasBindings, children, className }, ref) { + forwardRef(function MenuPanel( + { label, menu, submenu, panelAvailable = true, rtl, getHasBindings, hasBindings, children, className, ...rest }, + ref, + ) { + const translate = useTranslate(); const styles = useS(style); const visible = menu.visible; @@ -51,8 +55,9 @@ export const MenuPanel = observer( ref={ref} className={s(styles, { menu: true, modal: menu.modal, submenu }, className)} {...menu} - aria-label={label} + aria-label={translate(label)} visible={panelAvailable} + {...rest} >
{Children.count(renderedChildren) === 0 && } diff --git a/webapp/packages/core-blocks/src/MenuPanel/MenuPanelItem.tsx b/webapp/packages/core-blocks/src/MenuPanel/MenuPanelItem.tsx deleted file mode 100644 index b28c284059..0000000000 --- a/webapp/packages/core-blocks/src/MenuPanel/MenuPanelItem.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { observer } from 'mobx-react-lite'; - -import type { IMenuItem } from '@cloudbeaver/core-dialogs'; - -import { Checkbox } from '../FormControls/Checkboxes/Checkbox.js'; -import { Radio } from '../FormControls/Radio.js'; -import { Icon } from '../Icon.js'; -import { IconOrImage } from '../IconOrImage.js'; -import { Loader } from '../Loader/Loader.js'; -import { useTranslate } from '../localization/useTranslate.js'; -import { s } from '../s.js'; -import { useS } from '../useS.js'; -import MenuPanelItemAndTriggerStyles from './shared/MenuPanelItemAndTrigger.module.css'; - -interface MenuPanelItemProps { - menuItem: IMenuItem; - className?: string; -} - -export const MenuPanelItem = observer(function MenuPanelItem({ menuItem }) { - const translate = useTranslate(); - const style = useS(MenuPanelItemAndTriggerStyles); - - const title = translate(menuItem.title); - let control = null; - - if (menuItem.type === 'radio') { - control = ; - } else if (menuItem.type === 'checkbox') { - control = ; - } - - return ( -
-
- {menuItem.icon ? : control} -
-
- {title} -
-
- {menuItem.panel && - (menuItem.isProcessing ? ( - - ) : ( - - ))} -
-
- ); -}); diff --git a/webapp/packages/core-blocks/src/MenuPanel/MenuTrigger.tsx b/webapp/packages/core-blocks/src/MenuPanel/MenuTrigger.tsx deleted file mode 100644 index 5173467340..0000000000 --- a/webapp/packages/core-blocks/src/MenuPanel/MenuTrigger.tsx +++ /dev/null @@ -1,250 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { computed } from 'mobx'; -import { observer } from 'mobx-react-lite'; -import React, { type ButtonHTMLAttributes, forwardRef, useCallback, useEffect, useMemo } from 'react'; -import { Menu, MenuButton, type MenuInitialState, MenuItem, MenuItemCheckbox, MenuItemRadio, type MenuStateReturn, useMenuState } from 'reakit'; -import type { ExtractHTMLAttributes } from 'reakit-utils'; - -import type { IMenuItem, IMenuPanel } from '@cloudbeaver/core-dialogs'; - -import { s } from '../s.js'; -import { useObjectRef } from '../useObjectRef.js'; -import { useS } from '../useS.js'; -import { MenuPanelItem } from './MenuPanelItem.js'; -import MenuPanelItemAndTriggerStyles from './shared/MenuPanelItemAndTrigger.module.css'; - -export type MenuState = MenuStateReturn; - -/** - * MenuTrigger - */ - -interface IMenuTriggerBaseProps extends Omit, 'style'> { - menuRef?: React.RefObject; - disclosure?: boolean; - placement?: MenuInitialState['placement']; - modal?: boolean; - visible?: boolean; - rtl?: boolean; - onVisibleSwitch?: (visible: boolean) => void; -} - -interface IMenuTriggerLazyProps extends IMenuTriggerBaseProps { - getPanel: () => IMenuPanel; - panel?: IMenuPanel; -} - -interface IMenuTriggerProps extends IMenuTriggerBaseProps { - panel: IMenuPanel; - getPanel?: () => IMenuPanel; - className?: string; -} - -export const MenuTrigger = React.forwardRef, IMenuTriggerProps | IMenuTriggerLazyProps>(function MenuTrigger( - { panel, menuRef, getPanel, disclosure, className, children, placement, visible, onVisibleSwitch, modal, rtl, ...props }, - ref, -) { - const propsRef = useObjectRef({ onVisibleSwitch, visible }); - const menu = useMenuState({ modal, placement, visible, rtl }); - const style = useS(MenuPanelItemAndTriggerStyles); - - if (menuRef) { - //@ts-expect-error ref mutation - menuRef.current = menu; - } - - const handleItemClose = useCallback(() => { - menu.hide(); - }, [menu.hide]); - - useEffect(() => { - propsRef.onVisibleSwitch?.(menu.visible); - }, [menu.visible]); - - if (menu.visible && getPanel) { - panel = getPanel(); - } - - if (React.isValidElement(children) && disclosure) { - return ( -
- - {(disclosureProps: ExtractHTMLAttributes) => React.cloneElement(children, disclosureProps)} - - {panel && } -
- ); - } - - return ( -
- -
{children}
-
- {panel && } -
- ); -}); - -/** - * MenuPanel - */ - -interface MenuPanelProps { - panel: IMenuPanel; - menu: MenuStateReturn; // from reakit useMenuState - panelAvailable?: boolean; - onItemClose?: () => void; - rtl?: boolean; -} - -const MenuPanel = observer(function MenuPanel({ panel, menu, panelAvailable, rtl, onItemClose }) { - const style = useS(MenuPanelItemAndTriggerStyles); - if (!menu.visible) { - return null; - } - - return ( - -
- {panel.menuItems.map(item => ( - - ))} -
-
- ); -}); - -/** - * MenuPanelElement - */ - -interface IMenuPanelElementProps extends Omit, 'style'> { - item: IMenuItem; - menu: MenuStateReturn; // from reakit useMenuState - onItemClose?: () => void; -} - -const MenuPanelElement = observer(function MenuPanelElement({ item, menu, onItemClose }) { - const style = useS(MenuPanelItemAndTriggerStyles); - const onClick = useCallback(() => { - if (item.onClick) { - item.onClick(); - } - if (!item.keepMenuOpen && !item.panel) { - onItemClose?.(); - } - }, [item, menu, onItemClose]); - - const hidden = useMemo(() => computed(() => item.panel?.menuItems.every(item => item.isHidden)), [item.panel]); - - if (hidden.get() && item.isPanelAvailable === undefined) { - return null; - } - - if (item.panel) { - return ( - - ); - } - - if (item.type === 'radio') { - return ( - - - - ); - } - - if (item.type === 'checkbox') { - return ( - - - - ); - } - - return ( - - - - ); -}); - -/** - * MenuInnerTrigger - */ - -interface IMenuInnerTriggerProps extends Omit, 'style'> { - menuItem: IMenuItem; - onItemClose?: () => void; -} - -export const MenuInnerTrigger = observer( - forwardRef(function MenuInnerTrigger(props, ref) { - const { menuItem, onItemClose, ...rest } = props; - const menu = useMenuState(); - const style = useS(MenuPanelItemAndTriggerStyles); - - const handleItemClose = useCallback(() => { - menu.hide(); - onItemClose?.(); - }, [menu.hide, onItemClose]); - - const handleMouseEnter = useCallback(() => { - menuItem.onMouseEnter?.(); - }, [menuItem.onMouseEnter]); - - return ( - <> -
- -
- -
-
-
- - - ); - }), -); diff --git a/webapp/packages/core-blocks/src/MenuPanel/shared/MenuPanelItemAndTrigger.module.css b/webapp/packages/core-blocks/src/MenuPanel/shared/MenuPanelItemAndTrigger.module.css deleted file mode 100644 index a4be1be057..0000000000 --- a/webapp/packages/core-blocks/src/MenuPanel/shared/MenuPanelItemAndTrigger.module.css +++ /dev/null @@ -1,118 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ - -.menuBox { - composes: theme-background-surface theme-text-on-surface from global; -} - -.menuItem, -.menuItemCheckbox, -.menuItemRadio { - composes: theme-ripple from global; -} -.menuItemElement { - composes: theme-ripple from global; -} -.menuPanelItem { - composes: theme-border-color-background from global; -} -.box { - display: flex; - align-items: center; - flex: 1; - height: inherit; -} - -.menuButton { - background: none; - border: none; - outline: none !important; - color: inherit; - cursor: pointer; -} -.menu { - outline: none; - z-index: 999; -} - -.menuBox { - composes: theme-typography--body2 theme-elevation-z5 from global; - min-width: 140px; - padding: 12px 0; - & .menuBox { - margin-top: -12px; - } -} -.menuBox, -.menuPanelButtonWrapper { - display: flex; - flex-direction: column; -} -.menuItem, -.menuItemCheckbox, -.menuItemRadio { - display: flex; - align-items: center; - border: none; - padding: 0; - background: none; - text-align: left; - outline: none; - color: inherit; - cursor: pointer; - white-space: nowrap; - &.use, - &.hidden { - display: none; - } - &:hover, - &:global([aria-expanded='true']) { - & .icon { - opacity: 1; - } - } -} -.menuPanelItem { - flex: 1; - display: flex; - align-items: center; - height: 30px; - padding: 0 4px; - &.separator { - border-bottom: 1px solid; - } - & .menuItemText { - display: block; - padding: 0 4px; - flex: 1; - } - & .menuItemContent { - width: 24px; - height: 24px; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - } - & .icon { - width: 16px; - height: 16px; - opacity: 0.5; - transform: rotate(-90deg); - } - & .iconOrImage { - width: 16px; - height: 16px; - object-fit: contain; - } - & .loader { - width: 16px; - } -} -.menuTrigger { -} diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index d29262d5eb..002ef6cadb 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -58,7 +58,6 @@ export * from './Menu/MenuBarSmallItem.js'; export * from './Menu/MenuEmptyItem.js'; export * from './Menu/MenuItem.js'; export { default as MenuItemStyles } from './Menu/MenuItem.module.css'; -export { default as MenuPanelItemAndTriggerStyles } from './MenuPanel/shared/MenuPanelItemAndTrigger.module.css'; export * from './Menu/MenuItemCheckbox.js'; export * from './Menu/MenuItemElement.js'; export { default as MenuItemElementStyles } from './Menu/MenuItemElement.module.css'; @@ -69,7 +68,6 @@ export * from './Menu/MenuSeparator.js'; export { default as MenuSeparatorStyles } from './Menu/MenuSeparator.module.css'; export * from './Menu/MenuStateContext.js'; export * from './Menu/useMouseContextMenu.js'; -export { MenuTrigger, type MenuState } from './MenuPanel/MenuTrigger.js'; export * from './ObjectPropertyInfo/ObjectPropertyInfoForm/ObjectPropertyInfoFormLoader.js'; export * from './ObjectPropertyInfo/useObjectPropertyCategories.js'; diff --git a/webapp/packages/core-dialogs/package.json b/webapp/packages/core-dialogs/package.json index 3c88e74dc0..67dc3c893c 100644 --- a/webapp/packages/core-dialogs/package.json +++ b/webapp/packages/core-dialogs/package.json @@ -19,7 +19,6 @@ }, "dependencies": { "@cloudbeaver/core-di": "^0", - "@cloudbeaver/core-localization": "^0", "@cloudbeaver/core-utils": "^0", "mobx": "^6" }, diff --git a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts b/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts deleted file mode 100644 index 5508670907..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import type { TLocalizationToken } from '@cloudbeaver/core-localization'; -import { uuid } from '@cloudbeaver/core-utils'; - -import type { IMenuPanel } from '../IMenuPanel.js'; -import { ComputedContextMenuModel } from '../models/ComputedContextMenuModel.js'; -import { ComputedMenuItemModel, type IComputedMenuItemOptions } from '../models/ComputedMenuItemModel.js'; -import { type MenuItemType, MenuOptionsStore } from '../models/MenuOptionsStore.js'; -import type { IContextMenuItem } from './IContextMenuItem.js'; -import type { IMenuContext } from './IMenuContext.js'; - -/** - * this class allows to store IContextMenuItem in a tree structure - * and create IMenuPanel from this tree - */ -export class ContextMenu { - menuStore = new MenuOptionsStore>(); - - addRootPanel(panelId: string) { - this.menuStore.addRootPanel(panelId); - } - - /** - * note that you can add items with different content type in one menu panel - * just verify content type in IContextMenuItem.isPresent to show menu item in certain context - * - */ - addMenuItem(panelId: string, params: IContextMenuItem) { - this.menuStore.addMenuItem(panelId, params); - } - - constructMenuWithContext(panelId: string, context: IMenuContext): IMenuPanel { - return this.constructMenuPanelWithContext(panelId, context); - } - - private constructMenuItemWithContext(params: IContextMenuItem, context: IMenuContext): ComputedMenuItemModel { - // depends on context - const modelOptions = new ComputedMenuItemOptionsWithContext(params, context); - const model = new ComputedMenuItemModel(modelOptions); - - if (params.isPanel && !params.panel) { - model.panel = this.constructMenuPanelWithContext(params.id, context); - } else if (params.panel instanceof ComputedContextMenuModel) { - const basePanel = params.panel; - model.panel = new ContextMenuPanel(`${params.panel.id}-${context.contextId!}-panel`, () => - this.constructMenuItems(basePanel.options.menuItemsGetter(context), context), - ); - } - - return model; - } - - private constructMenuPanelWithContext(panelId: string, context: IMenuContext): IMenuPanel { - const panel = this.menuStore.getPanel(panelId); - const contextId = context.contextId || uuid(); - return new ContextMenuPanel(`${panelId}-${contextId}-panel`, () => this.constructMenuItems(panel.menuItems.values, context)); - } - - private constructMenuItems(menuItems: Array>, context: IMenuContext): ComputedMenuItemModel[] { - return menuItems - .filter(item => item.isPresent(context)) // show menu items based on context - .map(item => this.constructMenuItemWithContext(item, context)); - } -} - -/** - * This is helper class and is in use only inside the class ContextRootMenu - */ -class ComputedMenuItemOptionsWithContext implements IComputedMenuItemOptions { - id: string; - onClick?: () => void; - onMouseEnter?: () => void; - // set title or getter - title?: TLocalizationToken; - titleGetter?: () => TLocalizationToken | undefined; - tooltip?: TLocalizationToken; - tooltipGetter?: () => TLocalizationToken | undefined; - isDisabled?: () => boolean; - isHidden?: () => boolean; - isProcessing?: () => boolean; - isPanelAvailable?: () => boolean; - // set icon or getter - icon?: string; - isChecked?: () => boolean; - type?: MenuItemType; - separator?: boolean; - keepMenuOpen?: boolean; - iconGetter?: () => string | undefined; - - constructor( - private readonly options: IContextMenuItem, - private readonly context: IMenuContext, - ) { - // doesn't depend on context - this.title = options.title; - this.tooltip = options.tooltip; - this.tooltipGetter = options.tooltipGetter; - this.icon = options.icon; - this.type = options.type; - this.separator = options.separator; - this.keepMenuOpen = options.keepMenuOpen; - this.iconGetter = options.iconGetter; - - this.id = `${options.id}-${context.contextId!}`; - - if (options.onClick) { - this.onClick = () => options.onClick!(this.context); - } - if (options.onMouseEnter) { - this.onMouseEnter = () => options.onMouseEnter!(this.context); - } - if (options.isDisabled) { - this.isDisabled = () => options.isDisabled!(this.context); - } - if (options.isHidden) { - this.isHidden = () => options.isHidden!(this.context); - } - if (options.isProcessing) { - this.isProcessing = () => options.isProcessing!(this.context); - } - if (options.isPanelAvailable) { - this.isPanelAvailable = () => options.isPanelAvailable!(this.context); - } - if (options.isChecked) { - this.isChecked = () => options.isChecked!(this.context); - } - if (options.titleGetter) { - this.titleGetter = () => options.titleGetter!(this.context); - } - } -} - -class ContextMenuPanel implements IMenuPanel { - get menuItems() { - if (!this.items) { - this.items = this.itemsGetter(); - } - return this.items; - } - - private items?: ComputedMenuItemModel[]; - - constructor( - public id: string, - private readonly itemsGetter: () => ComputedMenuItemModel[], - ) {} -} diff --git a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenuService.ts b/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenuService.ts deleted file mode 100644 index 3a4190d7cb..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenuService.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { injectable } from '@cloudbeaver/core-di'; - -import type { IMenuPanel } from '../IMenuPanel.js'; -import { ContextMenu } from './ContextMenu.js'; -import type { IContextMenuItem } from './IContextMenuItem.js'; -import type { IMenuContext } from './IMenuContext.js'; - -@injectable() -export class ContextMenuService { - private static readonly rootPanelId = 'contextRoot'; - - private readonly contextMenu = new ContextMenu(); - - constructor() { - this.contextMenu.addRootPanel(ContextMenuService.rootPanelId); - } - - getRootMenuToken() { - return ContextMenuService.rootPanelId; - } - - addPanel(panelId: string) { - this.contextMenu.addRootPanel(panelId); - } - - addMenuItem(panelId: string, menuItem: IContextMenuItem) { - this.contextMenu.addMenuItem(panelId, menuItem); - } - - createContextMenu(context: IMenuContext, panelId?: string): IMenuPanel { - return this.contextMenu.constructMenuWithContext(panelId || ContextMenuService.rootPanelId, context); - } -} diff --git a/webapp/packages/core-dialogs/src/Menu/ContextMenu/IContextMenuItem.ts b/webapp/packages/core-dialogs/src/Menu/ContextMenu/IContextMenuItem.ts deleted file mode 100644 index 5858f8bdba..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/ContextMenu/IContextMenuItem.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import type { TLocalizationToken } from '@cloudbeaver/core-localization'; - -import type { IMenuItemOptions } from '../models/MenuOptionsStore.js'; -import type { IMenuContext } from './IMenuContext.js'; - -/** - * Options allow to create context menu item - */ -export interface IContextMenuItem extends IMenuItemOptions { - onClick?: (context: IMenuContext) => void; - onMouseEnter?: (context: IMenuContext) => void; - titleGetter?: (context: IMenuContext) => TLocalizationToken | undefined; - // if isPresent is false menu item will not be included in resulting context menu - isPresent: (context: IMenuContext) => boolean; - isDisabled?: (context: IMenuContext) => boolean; - // When the item is present in menu it can be hidden based on certain conditions - isHidden?: (context: IMenuContext) => boolean; - isProcessing?: (context: IMenuContext) => boolean; - /** - * @param {IMenuContext} context - * Useful when we want to show menu panel based on some async data - */ - isPanelAvailable?: (context: IMenuContext) => boolean; - isChecked?: (context: IMenuContext) => boolean; -} diff --git a/webapp/packages/core-dialogs/src/Menu/ContextMenu/IMenuContext.ts b/webapp/packages/core-dialogs/src/Menu/ContextMenu/IMenuContext.ts deleted file mode 100644 index 0f92e2de93..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/ContextMenu/IMenuContext.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ - -export interface IMenuContext { - menuId: string; // use this id to check where the menu is called - data: T; - - // if the context is an instance of a class you don't need context type - contextType?: string; - // if is omitted the context menu id will be generated based on random uuid - contextId?: string; -} diff --git a/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts b/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts deleted file mode 100644 index 5ef01c192c..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import type { TLocalizationToken } from '@cloudbeaver/core-localization'; - -import type { MenuItemType } from './models/MenuOptionsStore.js'; - -export type MenuMod = 'primary' | 'surface' | 'secondary'; - -export interface IMenuPanel { - id: string; - title?: TLocalizationToken; - menuItems: IMenuItem[]; -} - -export interface IMenuItem { - id: string; - title: TLocalizationToken; - onClick?: () => void; // it is not mandatory if it is just opens submenu - onMouseEnter?: () => void; - isDisabled?: boolean; - isHidden?: boolean; - isProcessing?: boolean; - isPanelAvailable?: boolean; - keepMenuOpen?: boolean; - icon?: string; // path to icon or svg icon name - tooltip?: string; - panel?: IMenuPanel; // if menu has sub-items - type?: MenuItemType; - separator?: boolean; - isChecked?: boolean; -} diff --git a/webapp/packages/core-dialogs/src/Menu/StaticMenu/StaticMenu.ts b/webapp/packages/core-dialogs/src/Menu/StaticMenu/StaticMenu.ts deleted file mode 100644 index b58faea209..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/StaticMenu/StaticMenu.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import type { IMenuPanel } from '../IMenuPanel.js'; -import { ComputedMenuItemModel, type IComputedMenuItemOptions } from '../models/ComputedMenuItemModel.js'; -import { ComputedMenuPanelModel } from '../models/ComputedMenuPanelModel.js'; -import { MenuOptionsStore } from '../models/MenuOptionsStore.js'; - -/** - * this class allows to store IComputedMenuItemOptions in a tree structure - * and create IMenuPanel from this tree - */ -export class StaticMenu { - private readonly menuStore = new MenuOptionsStore(); - private readonly menuModels = new Map(); - - addRootPanel(panelId: string): void { - this.menuStore.addRootPanel(panelId); - } - - addMenuItem(panelId: string, params: IComputedMenuItemOptions): void { - this.menuStore.addMenuItem(panelId, params); - } - - getMenu(panelId: string): IMenuPanel { - // construct menu model only once - if (!this.menuModels.has(panelId)) { - const menuModel = this.constructMenuPanel(panelId); - this.menuModels.set(panelId, menuModel); - } - return this.menuModels.get(panelId)!; - } - - private constructMenuPanel(panelId: string): IMenuPanel { - return new ComputedMenuPanelModel({ - id: `${panelId}-panel`, - menuItemsGetter: () => this.constructMenuItems(panelId), - }); - } - - private constructMenuItems(panelId: string): ComputedMenuItemModel[] { - const panel = this.menuStore.getPanel(panelId); - return panel.menuItems.values.map(item => this.constructMenuItem(item)); - } - - private constructMenuItem(options: IComputedMenuItemOptions): ComputedMenuItemModel { - const model = new ComputedMenuItemModel(options); - - if (options.isPanel) { - model.panel = this.constructMenuPanel(options.id); - } - - return model; - } -} diff --git a/webapp/packages/core-dialogs/src/Menu/models/ComputedContextMenuModel.ts b/webapp/packages/core-dialogs/src/Menu/models/ComputedContextMenuModel.ts deleted file mode 100644 index 1b21240967..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/models/ComputedContextMenuModel.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import type { IContextMenuItem } from '../ContextMenu/IContextMenuItem.js'; -import type { IMenuContext } from '../ContextMenu/IMenuContext.js'; -import type { IMenuPanel } from '../IMenuPanel.js'; - -export interface IComputedContextMenuPanelOptions { - id: string; - menuItemsGetter: (context: IMenuContext) => Array>; -} - -export class ComputedContextMenuModel implements IMenuPanel { - id: string; - - get menuItems() { - return []; - } - - constructor(readonly options: IComputedContextMenuPanelOptions) { - this.id = this.options.id; - } -} diff --git a/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts b/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts deleted file mode 100644 index a9cdabbf9e..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { computed, makeObservable } from 'mobx'; - -import type { TLocalizationToken } from '@cloudbeaver/core-localization'; - -import type { IMenuItem, IMenuPanel } from '../IMenuPanel.js'; -import type { IMenuItemOptions, MenuItemType } from './MenuOptionsStore.js'; - -export interface IComputedMenuItemOptions extends IMenuItemOptions { - onClick?: () => void; - onMouseEnter?: () => void; - isDisabled?: () => boolean; - isHidden?: () => boolean; - isProcessing?: () => boolean; - isPanelAvailable?: () => boolean; - isChecked?: () => boolean; -} - -export class ComputedMenuItemModel implements IMenuItem { - id: string; - onClick?: () => void; - onMouseEnter?: () => void; - panel?: IMenuPanel; - type?: MenuItemType; - separator?: boolean; - keepMenuOpen?: boolean; - rtl?: boolean; - - get title(): TLocalizationToken { - if (this.options.title) { - return this.options.title; - } - return this.options.titleGetter ? this.options.titleGetter() || '' : ''; - } - - get tooltip() { - if (this.options.tooltip) { - return this.options.tooltip; - } - return this.options.tooltipGetter ? this.options.tooltipGetter() : undefined; - } - - get isDisabled() { - return this.options.isDisabled ? this.options.isDisabled() : false; - } - - get icon() { - if (this.options.icon) { - return this.options.icon; - } - return this.options.iconGetter ? this.options.iconGetter() : undefined; - } - - get isHidden(): boolean { - return this.options.isHidden ? this.options.isHidden() : false; - } - - get isProcessing(): boolean { - return this.options.isProcessing ? this.options.isProcessing() : false; - } - - get isPanelAvailable(): boolean | undefined { - return this.options.isPanelAvailable ? this.options.isPanelAvailable() : undefined; - } - - get isChecked(): boolean { - return this.options.isChecked ? this.options.isChecked() : false; - } - - constructor(private readonly options: IComputedMenuItemOptions) { - makeObservable(this, { - title: computed, - tooltip: computed, - isDisabled: computed, - icon: computed, - isHidden: computed, - isProcessing: computed, - isPanelAvailable: computed, - isChecked: computed, - }); - - this.id = options.id; - this.type = options.type; - this.keepMenuOpen = options.keepMenuOpen; - this.separator = options.separator; - this.rtl = options.rtl; - this.panel = options.panel; - this.onClick = this.options.onClick; - this.onMouseEnter = this.options.onMouseEnter; - } -} diff --git a/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuPanelModel.ts b/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuPanelModel.ts deleted file mode 100644 index abd2559b35..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuPanelModel.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { computed, makeObservable } from 'mobx'; - -import type { IMenuItem, IMenuPanel } from '../IMenuPanel.js'; - -export interface IComputedMenuPanelOptions { - id: string; - menuItemsGetter: () => IMenuItem[]; -} - -export class ComputedMenuPanelModel implements IMenuPanel { - id: string; - - get menuItems() { - return this.options.menuItemsGetter(); - } - - constructor(private readonly options: IComputedMenuPanelOptions) { - makeObservable(this, { - menuItems: computed, - }); - - this.id = this.options.id; - } -} diff --git a/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts b/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts deleted file mode 100644 index 6e322f393c..0000000000 --- a/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import type { TLocalizationToken } from '@cloudbeaver/core-localization'; -import { OrderedMap } from '@cloudbeaver/core-utils'; - -import type { IMenuPanel } from '../IMenuPanel.js'; - -const DEFAULT_ITEM_ORDER = 100; - -export type MenuItemType = 'checkbox' | 'radio'; -export interface IMenuItemOptions { - id: string; - // set title or getter - title?: TLocalizationToken; - titleGetter?: (...args: any[]) => TLocalizationToken | undefined; - // set icon or getter - icon?: string; - iconGetter?: () => string | undefined; - tooltip?: string; - tooltipGetter?: () => TLocalizationToken | undefined; - order?: number; - isPanel?: boolean; - keepMenuOpen?: boolean; - panel?: IMenuPanel; - type?: MenuItemType; - separator?: boolean; - rtl?: boolean; -} - -/** - * This class store IMenuItemOptions in a set of trees. - * Items on a certain level of the tree are always ordered. - * to show menu you need to convert menuItemOptions to MenuItemModels - */ -export class MenuOptionsStore { - private readonly panelsMap: Map> = new Map(); - - addRootPanel(panelId: string) { - this.createPanelIfNotExists(panelId); - } - - addMenuItem(panelId: string, params: T) { - const panel = this.createPanelIfNotExists(panelId); - if (panel.menuItems.has(params.id)) { - throw new Error(`Panel "${panelId}" already has item ${params.id}`); - } - - this.putItemInPanel(panel, params); - - if (params.isPanel) { - this.createPanelIfNotExists(params.id); - } - } - - getPanel(panelId: string): MenuItemOptionsList { - const panel = this.panelsMap.get(panelId); - if (!panel) { - throw new Error(`Menu panel "${panelId}" is missing`); - } - return panel; - } - - private putItemInPanel(panel: MenuItemOptionsList, params: T) { - panel.menuItems.addValue(params); - // later sorting may become much more complicated - // and be based on rules like "item A should be after item B" - panel.menuItems.sort((a, b) => this.compare(a, b)); - } - - private createPanelIfNotExists(panelId: string): MenuItemOptionsList { - if (this.panelsMap.has(panelId)) { - // it means that the panel was created early by some menu item that has been registered in this panel - return this.panelsMap.get(panelId)!; - } - const panel = new MenuItemOptionsList(panelId); - this.panelsMap.set(panelId, panel); - return panel; - } - - private compare(a: IMenuItemOptions, b: IMenuItemOptions): number { - const orderA = a.order !== undefined ? a.order : DEFAULT_ITEM_ORDER; - const orderB = b.order !== undefined ? b.order : DEFAULT_ITEM_ORDER; - return orderA - orderB; - } -} - -class MenuItemOptionsList { - readonly id: string; - - menuItems = new OrderedMap(option => option.id); - - constructor(id: string) { - this.id = id; - } -} diff --git a/webapp/packages/core-dialogs/src/index.ts b/webapp/packages/core-dialogs/src/index.ts index b7da754a92..36de943a21 100644 --- a/webapp/packages/core-dialogs/src/index.ts +++ b/webapp/packages/core-dialogs/src/index.ts @@ -6,16 +6,4 @@ * you may not use this file except in compliance with the License. */ export * from './CommonDialog/CommonDialogService.js'; - -export * from './Menu/IMenuPanel.js'; -export * from './Menu/StaticMenu/StaticMenu.js'; - -export * from './Menu/models/ComputedContextMenuModel.js'; -export * from './Menu/models/ComputedMenuItemModel.js'; -export * from './Menu/models/ComputedMenuPanelModel.js'; - -// contextMenu -export * from './Menu/ContextMenu/ContextMenuService.js'; -export * from './Menu/ContextMenu/IContextMenuItem.js'; -export * from './Menu/ContextMenu/IMenuContext.js'; export * from './manifest.js'; diff --git a/webapp/packages/core-dialogs/src/manifest.ts b/webapp/packages/core-dialogs/src/manifest.ts index 6fb7ecdd5c..89fa3ee937 100644 --- a/webapp/packages/core-dialogs/src/manifest.ts +++ b/webapp/packages/core-dialogs/src/manifest.ts @@ -12,8 +12,5 @@ export const coreDialogsManifest: PluginManifest = { name: 'Core Dialogs', }, - providers: [ - () => import('./CommonDialog/CommonDialogService.js').then(m => m.CommonDialogService), - () => import('./Menu/ContextMenu/ContextMenuService.js').then(m => m.ContextMenuService), - ], + providers: [() => import('./CommonDialog/CommonDialogService.js').then(m => m.CommonDialogService)], }; diff --git a/webapp/packages/core-dialogs/tsconfig.json b/webapp/packages/core-dialogs/tsconfig.json index 55659bb8c2..3330e215c0 100644 --- a/webapp/packages/core-dialogs/tsconfig.json +++ b/webapp/packages/core-dialogs/tsconfig.json @@ -9,9 +9,6 @@ { "path": "../core-di/tsconfig.json" }, - { - "path": "../core-localization/tsconfig.json" - }, { "path": "../core-utils/tsconfig.json" } diff --git a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx index be50f14c97..c83e7a41eb 100644 --- a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx @@ -17,7 +17,7 @@ import { MenuItemRenderer } from './MenuItemRenderer.js'; // TODO the click doesn't work for React components as children export const ContextMenu = observer( forwardRef(function ContextMenu( - { mouseContextMenu, menu: menuData, disclosure, children, placement, visible, onVisibleSwitch, modal, rtl, ...props }, + { mouseContextMenu, menu: menuData, disclosure, children, placement, visible, onVisibleSwitch, modal, rtl, panelProps, ...props }, ref, ) { const translate = useTranslate(); @@ -82,6 +82,7 @@ export const ContextMenu = observer( placement={placement} disabled={disabled} disclosure={disclosure} + panelProps={panelProps} getHasBindings={handlers.hasBindings} onVisibleSwitch={handlers.handleVisibleSwitch} > diff --git a/webapp/packages/core-ui/src/ContextMenu/IContextMenuProps.ts b/webapp/packages/core-ui/src/ContextMenu/IContextMenuProps.ts index a6bd3f940d..5252c8f587 100644 --- a/webapp/packages/core-ui/src/ContextMenu/IContextMenuProps.ts +++ b/webapp/packages/core-ui/src/ContextMenu/IContextMenuProps.ts @@ -8,7 +8,7 @@ import type { ButtonHTMLAttributes } from 'react'; import type { MenuInitialState } from 'reakit'; -import type { IMouseContextMenu } from '@cloudbeaver/core-blocks'; +import type { IMenuPanelProps, IMouseContextMenu } from '@cloudbeaver/core-blocks'; import type { IMenuData } from '@cloudbeaver/core-view'; export interface IContextMenuBaseProps extends React.PropsWithChildren { @@ -26,6 +26,7 @@ export interface IContextMenuProps extends Omit, 'chil modal?: boolean; visible?: boolean; rtl?: boolean; + panelProps?: Partial; children?: React.ReactNode | ContextMenuRenderingChildren; onVisibleSwitch?: (visible: boolean) => void; } diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuItemRenderer.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuItemRenderer.tsx index 845e5bb700..a80382193f 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuItemRenderer.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuItemRenderer.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react-lite'; import React, { useCallback } from 'react'; -import { Checkbox, MenuItem, MenuItemCheckbox, MenuItemElement, MenuSeparator, useTranslate } from '@cloudbeaver/core-blocks'; +import { Checkbox, MenuItem, MenuItemCheckbox, MenuItemElement, MenuItemRadio, MenuSeparator, Radio, useTranslate } from '@cloudbeaver/core-blocks'; import { type IMenuData, type IMenuItem, @@ -16,6 +16,7 @@ import { MenuActionItem, MenuBaseItem, MenuCheckboxItem, + MenuRadioItem, MenuSeparatorItem, MenuSubMenuItem, } from '@cloudbeaver/core-view'; @@ -97,6 +98,23 @@ export const MenuItemRenderer = observer(function MenuIt ); } + if (item instanceof MenuRadioItem) { + return ( + + ); + } + if (item instanceof MenuBaseItem) { const IconComponent = item.iconComponent?.(); const extraProps = item.getExtraProps?.(); diff --git a/webapp/packages/core-view/src/Menu/MenuItem/IMenuRadioItem.ts b/webapp/packages/core-view/src/Menu/MenuItem/IMenuRadioItem.ts new file mode 100644 index 0000000000..b3a4944e6f --- /dev/null +++ b/webapp/packages/core-view/src/Menu/MenuItem/IMenuRadioItem.ts @@ -0,0 +1,25 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import type { IMenuItem } from './IMenuItem.js'; + +interface IMenuRadioItemCommonProperties { + checked?: boolean; + label?: string; + tooltip?: string; + hidden?: boolean; + disabled?: boolean; +} + +export interface IMenuRadioItemOptions extends IMenuRadioItemCommonProperties { + id: string; + label: string; +} + +export interface IMenuRadioItem extends IMenuItem, IMenuRadioItemCommonProperties { + label: string; +} diff --git a/webapp/packages/core-view/src/Menu/MenuItem/MenuRadioItem.ts b/webapp/packages/core-view/src/Menu/MenuItem/MenuRadioItem.ts new file mode 100644 index 0000000000..b8bf101246 --- /dev/null +++ b/webapp/packages/core-view/src/Menu/MenuItem/MenuRadioItem.ts @@ -0,0 +1,43 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import type { IMenuItemEvents } from './IMenuItem.js'; +import type { IMenuRadioItem, IMenuRadioItemOptions } from './IMenuRadioItem.js'; +import { MenuItem } from './MenuItem.js'; + +interface IMenuBaseItemPropertyGetters { + isDisabled?: () => boolean; + isChecked?: () => boolean; +} + +export class MenuRadioItem extends MenuItem implements IMenuRadioItem { + private readonly isDisabled?: () => boolean; + private readonly isChecked?: () => boolean; + + readonly label: string; + readonly tooltip?: string; + readonly hidden?: boolean; + + get disabled(): boolean { + return this.isDisabled?.() ?? false; + } + + get checked(): boolean { + return this.isChecked?.() ?? this._checked; + } + + private readonly _checked: boolean; + + constructor(options: IMenuRadioItemOptions, events?: IMenuItemEvents, getters?: IMenuBaseItemPropertyGetters) { + super(options.id, events); + this._checked = options.checked ?? false; + this.label = options.label; + this.tooltip = options.tooltip; + this.isDisabled = getters?.isDisabled; + this.isChecked = getters?.isChecked; + } +} diff --git a/webapp/packages/core-view/src/index.ts b/webapp/packages/core-view/src/index.ts index f4002ea472..d55729e5ea 100644 --- a/webapp/packages/core-view/src/index.ts +++ b/webapp/packages/core-view/src/index.ts @@ -53,6 +53,7 @@ export * from './Menu/MenuItem/IMenuCustomItem.js'; export * from './Menu/MenuItem/IMenuItem.js'; export * from './Menu/MenuItem/IMenuSubMenuItem.js'; export * from './Menu/MenuItem/MenuCheckboxItem.js'; +export * from './Menu/MenuItem/MenuRadioItem.js'; export * from './Menu/MenuItem/MenuActionItem.js'; export * from './Menu/MenuItem/MenuBaseItem.js'; export * from './Menu/MenuItem/MenuCustomItem.js'; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_ADD_ROW.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_ADD_ROW.ts new file mode 100644 index 0000000000..53ac1ada5d --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_ADD_ROW.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DATA_GRID_EDITING_ADD_ROW = createAction('data-grid-editing-add-row', { + label: 'data_grid_table_editing_row_add', + icon: '/icons/data_add_sm.svg', +}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_DELETE_ROW.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_DELETE_ROW.ts new file mode 100644 index 0000000000..3512a01806 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_DELETE_ROW.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DATA_GRID_EDITING_DELETE_ROW = createAction('data-grid-editing-delete-row', { + label: 'data_grid_table_editing_row_delete', + icon: '/icons/data_delete_sm.svg', +}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW.ts new file mode 100644 index 0000000000..195b6933c4 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW = createAction('data-grid-editing-delete-selected-row', { + label: 'data_viewer_action_edit_delete', + icon: '/icons/data_delete_sm.svg', +}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_DUPLICATE_ROW.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_DUPLICATE_ROW.ts new file mode 100644 index 0000000000..830b0af449 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_DUPLICATE_ROW.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DATA_GRID_EDITING_DUPLICATE_ROW = createAction('data-grid-editing-duplicate-row', { + label: 'data_grid_table_editing_row_add_copy', + icon: '/icons/data_add_copy_sm.svg', +}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_REVERT_ROW.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_REVERT_ROW.ts new file mode 100644 index 0000000000..19b77e0e11 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_REVERT_ROW.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DATA_GRID_EDITING_REVERT_ROW = createAction('data-grid-editing-revert-row', { + label: 'data_grid_table_editing_row_revert', + icon: '/icons/data_revert_sm.svg', +}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW.ts new file mode 100644 index 0000000000..482a74d59b --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW = createAction('data-grid-editing-revert-selected-row', { + label: 'data_viewer_action_edit_revert', + icon: '/icons/data_revert_sm.svg', +}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_SET_TO_NULL.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_SET_TO_NULL.ts new file mode 100644 index 0000000000..06f0be10de --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Editing/ACTION_DATA_GRID_EDITING_SET_TO_NULL.ts @@ -0,0 +1,12 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DATA_GRID_EDITING_SET_TO_NULL = createAction('data-grid-editing-set-to-null', { + label: 'data_grid_table_editing_set_to_null', +}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Filters/ACTION_DATA_GRID_FILTERS_RESET_ALL.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Filters/ACTION_DATA_GRID_FILTERS_RESET_ALL.ts new file mode 100644 index 0000000000..e902d73bc3 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Filters/ACTION_DATA_GRID_FILTERS_RESET_ALL.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DATA_GRID_FILTERS_RESET_ALL = createAction('filters-reset-all', { + label: 'data_grid_table_filter_reset_all_filters', + icon: 'filter-reset-all', +}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Ordering/ACTION_DATA_GRID_ORDERING_DISABLE_ALL.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Ordering/ACTION_DATA_GRID_ORDERING_DISABLE_ALL.ts new file mode 100644 index 0000000000..b37c07cb56 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Actions/Ordering/ACTION_DATA_GRID_ORDERING_DISABLE_ALL.ts @@ -0,0 +1,12 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_DATA_GRID_ORDERING_DISABLE_ALL = createAction('data-grid-ordering-disable-all', { + label: 'data_grid_table_disable_all_orders', +}); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts index 6e97412423..a4d7a03f5b 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts @@ -6,7 +6,12 @@ * you may not use this file except in compliance with the License. */ import { injectable } from '@cloudbeaver/core-di'; +import { ACTION_EDIT, ActionService, MenuService } from '@cloudbeaver/core-view'; import { + DATA_CONTEXT_DV_DDM, + DATA_CONTEXT_DV_DDM_RESULT_INDEX, + DATA_CONTEXT_DV_PRESENTATION_ACTIONS, + DATA_CONTEXT_DV_RESULT_KEY, DatabaseEditChangeType, isBooleanValuePresentationAvailable, isResultSetDataSource, @@ -18,218 +23,162 @@ import { ResultSetViewAction, } from '@cloudbeaver/plugin-data-viewer'; -import { DataGridContextMenuService } from './DataGridContextMenuService.js'; +import { ACTION_DATA_GRID_EDITING_ADD_ROW } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_ADD_ROW.js'; +import { ACTION_DATA_GRID_EDITING_DELETE_ROW } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_DELETE_ROW.js'; +import { ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW.js'; +import { ACTION_DATA_GRID_EDITING_DUPLICATE_ROW } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_DUPLICATE_ROW.js'; +import { ACTION_DATA_GRID_EDITING_REVERT_ROW } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_REVERT_ROW.js'; +import { ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW.js'; +import { ACTION_DATA_GRID_EDITING_SET_TO_NULL } from '../Actions/Editing/ACTION_DATA_GRID_EDITING_SET_TO_NULL.js'; +import { MENU_DATA_GRID_EDITING } from './MENU_DATA_GRID_EDITING.js'; @injectable() export class DataGridContextMenuCellEditingService { - private static readonly menuEditingToken = 'menuEditing'; - - constructor(private readonly dataGridContextMenuService: DataGridContextMenuService) {} - - getMenuEditingToken(): string { - return DataGridContextMenuCellEditingService.menuEditingToken; - } + constructor( + private readonly actionService: ActionService, + private readonly menuService: MenuService, + ) {} register(): void { - this.dataGridContextMenuService.add(this.dataGridContextMenuService.getMenuToken(), { - id: this.getMenuEditingToken(), - order: 4, - title: 'data_grid_table_editing', - icon: 'edit', - isPanel: true, - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; - }, - isHidden(context) { - return context.data.model.isDisabled(context.data.resultIndex) || context.data.model.isReadonly(context.data.resultIndex); - }, + this.menuService.addCreator({ + root: true, + contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX, DATA_CONTEXT_DV_RESULT_KEY], + isApplicable: context => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + return isResultSetDataSource(model.source) && !model.isDisabled(resultIndex) && !model.isReadonly(resultIndex); + }, + getItems: (context, items) => [...items, MENU_DATA_GRID_EDITING], }); - this.dataGridContextMenuService.add(this.getMenuEditingToken(), { - id: 'open_inline_editor', - order: 0, - title: 'data_grid_table_editing_open_inline_editor', - icon: 'edit', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const format = source.getAction(context.data.resultIndex, ResultSetFormatAction); - const view = source.getAction(context.data.resultIndex, ResultSetViewAction); - const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); - const cellValue = view.getCellValue(context.data.key); - const column = view.getColumn(context.data.key.column); - const isComplex = format.isBinary(context.data.key) || format.isGeometry(context.data.key); - const isTruncated = content.isTextTruncated(context.data.key); - - if (!column || cellValue === undefined || format.isReadOnly(context.data.key) || isComplex || isTruncated) { - return true; - } - return isBooleanValuePresentationAvailable(cellValue, column); - }, - onClick(context) { - context.data.spreadsheetActions.edit(context.data.key); - }, + this.menuService.addCreator({ + menus: [MENU_DATA_GRID_EDITING], + getItems: (context, items) => [ + ...items, + ACTION_EDIT, + ACTION_DATA_GRID_EDITING_SET_TO_NULL, + ACTION_DATA_GRID_EDITING_ADD_ROW, + ACTION_DATA_GRID_EDITING_DUPLICATE_ROW, + ACTION_DATA_GRID_EDITING_DELETE_ROW, + ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW, + ACTION_DATA_GRID_EDITING_REVERT_ROW, + ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW, + ], }); - this.dataGridContextMenuService.add(this.getMenuEditingToken(), { - id: 'set_to_null', - order: 1, - title: 'data_grid_table_editing_set_to_null', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - const { key, model, resultIndex } = context.data; + + this.actionService.addHandler({ + id: 'data-grid-editing-base-handler', + menus: [MENU_DATA_GRID_EDITING], + isActionApplicable(context, action) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + const source = model.source as unknown as ResultSetDataSource; - const view = source.getAction(resultIndex, ResultSetViewAction); const format = source.getAction(resultIndex, ResultSetFormatAction); + const view = source.getAction(resultIndex, ResultSetViewAction); + const content = source.getAction(resultIndex, ResultSetDataContentAction); + const editor = source.getAction(resultIndex, ResultSetEditAction); + const select = source.getActionImplementation(resultIndex, ResultSetSelectAction); + const cellValue = view.getCellValue(key); + const column = view.getColumn(key.column); + const isComplex = format.isBinary(key) || format.isGeometry(key); + const isTruncated = content.isTextTruncated(key); + const selectedElements = select?.getSelectedElements() || []; - return cellValue === undefined || format.isReadOnly(context.data.key) || view.getColumn(key.column)?.required || format.isNull(key); - }, - onClick(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - source.getAction(context.data.resultIndex, ResultSetEditAction).set(context.data.key, null); - }, - }); - this.dataGridContextMenuService.add(this.getMenuEditingToken(), { - id: 'row_add', - order: 5, - icon: '/icons/data_add_sm.svg', - title: 'data_grid_table_editing_row_add', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - return !editor.hasFeature('add'); - }, - onClick(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - editor.addRow(context.data.key.row); - }, - }); - this.dataGridContextMenuService.add(this.getMenuEditingToken(), { - id: 'row_add_copy', - order: 5.5, - icon: '/icons/data_add_copy_sm.svg', - title: 'data_grid_table_editing_row_add_copy', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - return !editor.hasFeature('add'); - }, - onClick(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - editor.duplicateRow(context.data.key); - }, - }); - this.dataGridContextMenuService.add(this.getMenuEditingToken(), { - id: 'row_delete', - order: 6, - icon: '/icons/data_delete_sm.svg', - title: 'data_grid_table_editing_row_delete', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); + if (action === ACTION_EDIT) { + if (!column || cellValue === undefined || format.isReadOnly(key) || isComplex || isTruncated) { + return false; + } - if (context.data.model.isReadonly(context.data.resultIndex) || !editor.hasFeature('delete')) { - return true; + return !isBooleanValuePresentationAvailable(cellValue, column); } - const format = source.getAction(context.data.resultIndex, ResultSetFormatAction); - return format.isReadOnly(context.data.key) || editor.getElementState(context.data.key) === DatabaseEditChangeType.delete; - }, - onClick(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - editor.deleteRow(context.data.key.row); - }, - }); - this.dataGridContextMenuService.add(this.getMenuEditingToken(), { - id: 'row_delete_selected', - order: 6.1, - icon: '/icons/data_delete_sm.svg', - title: 'data_viewer_action_edit_delete', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); + if (action === ACTION_DATA_GRID_EDITING_SET_TO_NULL) { + return cellValue !== undefined && !format.isReadOnly(key) && !view.getColumn(key.column)?.required && !format.isNull(key); + } - if (context.data.model.isReadonly(context.data.resultIndex) || !editor.hasFeature('delete')) { - return true; + if (action === ACTION_DATA_GRID_EDITING_ADD_ROW || action === ACTION_DATA_GRID_EDITING_DUPLICATE_ROW) { + return editor.hasFeature('add'); } - const select = source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); + if (action === ACTION_DATA_GRID_EDITING_DELETE_ROW) { + return !format.isReadOnly(key) && editor.getElementState(key) !== DatabaseEditChangeType.delete; + } - const selectedElements = select?.getSelectedElements() || []; + if (action === ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW) { + if (model.isReadonly(resultIndex) || !editor.hasFeature('delete')) { + return false; + } - return !selectedElements.some(key => editor.getElementState(key) !== DatabaseEditChangeType.delete); - }, - onClick(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - const select = source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); + return selectedElements.some(key => editor.getElementState(key) !== DatabaseEditChangeType.delete); + } - const selectedElements = select?.getSelectedElements() || []; + if (action === ACTION_DATA_GRID_EDITING_REVERT_ROW) { + return editor.getElementState(key) !== null; + } - editor.delete(...selectedElements); - }, - }); - this.dataGridContextMenuService.add(this.getMenuEditingToken(), { - id: 'row_revert', - order: 7, - icon: '/icons/data_revert_sm.svg', - title: 'data_grid_table_editing_row_revert', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - return editor.getElementState(context.data.key) === null; - }, - onClick(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - editor.revert(context.data.key); - }, - }); - this.dataGridContextMenuService.add(this.getMenuEditingToken(), { - id: 'row_revert_selected', - order: 7.1, - icon: '/icons/data_revert_sm.svg', - title: 'data_viewer_action_edit_revert', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - const select = source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); + if (action === ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW) { + return selectedElements.some(key => editor.getElementState(key) !== null); + } - const selectedElements = select?.getSelectedElements() || []; - return !selectedElements.some(key => editor.getElementState(key) !== null); + return [ + ACTION_EDIT, + ACTION_DATA_GRID_EDITING_SET_TO_NULL, + ACTION_DATA_GRID_EDITING_ADD_ROW, + ACTION_DATA_GRID_EDITING_DUPLICATE_ROW, + ACTION_DATA_GRID_EDITING_DELETE_ROW, + ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW, + ACTION_DATA_GRID_EDITING_REVERT_ROW, + ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW, + ].includes(action); + }, + getActionInfo(context, action) { + if (action === ACTION_EDIT) { + return { ...action.info, label: 'data_grid_table_editing_open_inline_editor', icon: 'edit' }; + } + + return action.info; }, - onClick(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const editor = source.getAction(context.data.resultIndex, ResultSetEditAction); - const select = source.getActionImplementation(context.data.resultIndex, ResultSetSelectAction); + handler(context, action) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const actions = context.get(DATA_CONTEXT_DV_PRESENTATION_ACTIONS)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + + const source = model.source as unknown as ResultSetDataSource; + const editor = source.getAction(resultIndex, ResultSetEditAction); + const select = source.getActionImplementation(resultIndex, ResultSetSelectAction); const selectedElements = select?.getSelectedElements() || []; - editor.revert(...selectedElements); + + switch (action) { + case ACTION_EDIT: + actions.edit(key); + break; + case ACTION_DATA_GRID_EDITING_SET_TO_NULL: + editor.set(key, null); + break; + case ACTION_DATA_GRID_EDITING_ADD_ROW: + editor.addRow(key.row); + break; + case ACTION_DATA_GRID_EDITING_DUPLICATE_ROW: + editor.duplicateRow(key); + break; + case ACTION_DATA_GRID_EDITING_DELETE_ROW: + editor.deleteRow(key.row); + break; + case ACTION_DATA_GRID_EDITING_DELETE_SELECTED_ROW: + editor.delete(...selectedElements); + break; + case ACTION_DATA_GRID_EDITING_REVERT_ROW: + editor.revert(key); + break; + case ACTION_DATA_GRID_EDITING_REVERT_SELECTED_ROW: + editor.revert(...selectedElements); + break; + } }, }); } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts index 33401a6b79..f838212024 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts @@ -7,16 +7,14 @@ */ import { importLazyComponent } from '@cloudbeaver/core-blocks'; import { injectable } from '@cloudbeaver/core-di'; -import { - CommonDialogService, - ComputedContextMenuModel, - DialogueStateResult, - type IContextMenuItem, - type IMenuContext, -} from '@cloudbeaver/core-dialogs'; +import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { ClipboardService } from '@cloudbeaver/core-ui'; import { replaceMiddle } from '@cloudbeaver/core-utils'; +import { ACTION_DELETE, ActionService, MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; import { + DATA_CONTEXT_DV_DDM, + DATA_CONTEXT_DV_DDM_RESULT_INDEX, + DATA_CONTEXT_DV_RESULT_KEY, DatabaseDataConstraintAction, type IDatabaseDataModel, type IResultSetColumnKey, @@ -31,25 +29,22 @@ import { wrapOperationArgument, } from '@cloudbeaver/plugin-data-viewer'; -import { DataGridContextMenuService, type IDataGridCellMenuContext } from '../DataGridContextMenuService.js'; +import { ACTION_DATA_GRID_FILTERS_RESET_ALL } from '../../Actions/Filters/ACTION_DATA_GRID_FILTERS_RESET_ALL.js'; +import { MENU_DATA_GRID_FILTERS } from './MENU_DATA_GRID_FILTERS.js'; +import { MENU_DATA_GRID_FILTERS_CELL_VALUE } from './MENU_DATA_GRID_FILTERS_CELL_VALUE.js'; +import { MENU_DATA_GRID_FILTERS_CLIPBOARD } from './MENU_DATA_GRID_FILTERS_CLIPBOARD.js'; +import { MENU_DATA_GRID_FILTERS_CUSTOM } from './MENU_DATA_GRID_FILTERS_CUSTOM.js'; const FilterCustomValueDialog = importLazyComponent(() => import('./FilterCustomValueDialog.js').then(m => m.FilterCustomValueDialog)); @injectable() export class DataGridContextMenuFilterService { - private static readonly menuFilterToken = 'menuFilter'; - constructor( - private readonly dataGridContextMenuService: DataGridContextMenuService, private readonly commonDialogService: CommonDialogService, private readonly clipboardService: ClipboardService, - ) { - this.dataGridContextMenuService.onRootMenuOpen.addHandler(this.getClipboardValue.bind(this)); - } - - getMenuFilterToken(): string { - return DataGridContextMenuFilterService.menuFilterToken; - } + private readonly actionService: ActionService, + private readonly menuService: MenuService, + ) {} private async applyFilter( model: IDatabaseDataModel, @@ -75,218 +70,261 @@ export class DataGridContextMenuFilterService { }); } - private async getClipboardValue() { - if (this.clipboardService.state === 'granted') { - await this.clipboardService.read(); - } - } + register(): void { + this.menuService.addCreator({ + root: true, + contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX, DATA_CONTEXT_DV_RESULT_KEY], + isApplicable: context => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; - private getGeneralizedMenuItems( - context: IMenuContext, - value: any | (() => any), - icon: string, - isHidden?: (context: IMenuContext) => boolean, - ): Array> { - const { model, resultIndex, key } = context.data; - const source = model.source as unknown as ResultSetDataSource; - const data = source.getAction(resultIndex, ResultSetDataAction); - const supportedOperations = data.getColumnOperations(key.column); - const columnLabel = data.getColumn(key.column)?.label || ''; - - return supportedOperations - .filter(operation => !nullOperationsFilter(operation)) - .map(operation => ({ - id: operation.id, - icon, - isPresent: context => isResultSetDataSource(context.data.model.source), - isDisabled(context) { - return context.data.model.isLoading(); - }, - isHidden(context) { - return isHidden?.(context) ?? false; - }, - titleGetter() { - const val = typeof value === 'function' ? value() : value; - const wrappedValue = wrapOperationArgument(operation.id, val); - const clippedValue = replaceMiddle(wrappedValue, ' ... ', 8, 30); - return `${columnLabel} ${operation.expression} ${clippedValue}`; - }, - onClick: async () => { - const val = typeof value === 'function' ? value() : value; - const wrappedValue = wrapOperationArgument(operation.id, val); - await this.applyFilter(model as unknown as IDatabaseDataModel, resultIndex, key.column, operation.id, wrappedValue); - }, - })); - } + const source = model.source as unknown as ResultSetDataSource; - register(): void { - this.dataGridContextMenuService.add(this.dataGridContextMenuService.getMenuToken(), { - id: this.getMenuFilterToken(), - order: 2, - title: 'data_grid_table_filter', - icon: 'filter', - isPanel: true, - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - if (context.data.model.isDisabled(context.data.resultIndex)) { - return true; + if (!isResultSetDataSource(source)) { + return false; } - const source = context.data.model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); - return !constraints.supported; + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + return constraints.supported && !model.isDisabled(resultIndex); }, + getItems: (context, items) => [...items, MENU_DATA_GRID_FILTERS], }); - this.dataGridContextMenuService.add(this.dataGridContextMenuService.getMenuToken(), { - id: 'deleteFiltersAndOrders', - order: 3, - title: 'data_grid_table_delete_filters_and_orders', - icon: 'erase', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - if (context.data.model.isDisabled(context.data.resultIndex)) { - return true; - } - const source = context.data.model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); - return constraints.orderConstraints.length === 0 && constraints.filterConstraints.length === 0; - }, - onClick: async context => { - const { model, resultIndex } = context.data; + this.menuService.addCreator({ + menus: [MENU_DATA_GRID_FILTERS], + getItems: (context, items) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + const source = model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + const data = source.getAction(resultIndex, ResultSetDataAction); + const resultColumn = data.getColumn(key.column); - await model.request(() => { - constraints.deleteData(); - }); + const supportedOperations = data.getColumnOperations(key.column); + const result = []; + + for (const filter of [IS_NULL_ID, IS_NOT_NULL_ID]) { + const label = `${resultColumn ? `"${resultColumn.label}" ` : ''}${filter.split('_').join(' ')}`; + + if (supportedOperations.some(operation => operation.id === filter)) { + result.push( + new MenuBaseItem( + { + id: filter, + label, + icon: 'filter', + }, + { + onSelect: async () => { + await this.applyFilter(model as unknown as IDatabaseDataModel, resultIndex, key.column, filter); + }, + }, + ), + ); + } + } + + return [ + ...items, + MENU_DATA_GRID_FILTERS_CELL_VALUE, + MENU_DATA_GRID_FILTERS_CUSTOM, + MENU_DATA_GRID_FILTERS_CLIPBOARD, + ...result, + ACTION_DELETE, + ACTION_DATA_GRID_FILTERS_RESET_ALL, + ]; }, }); - this.dataGridContextMenuService.add(this.getMenuFilterToken(), { - id: 'clipboardValue', - order: 0, - title: 'ui_clipboard', - icon: 'filter-clipboard', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); + + this.actionService.addHandler({ + id: 'data-grid-filters-base-handler', + menus: [MENU_DATA_GRID_FILTERS], + contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX, DATA_CONTEXT_DV_RESULT_KEY], + isActionApplicable: (context, action) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + + if (!isResultSetDataSource(model.source)) { + return false; + } + + return [ACTION_DELETE, ACTION_DATA_GRID_FILTERS_RESET_ALL].includes(action); }, - isHidden: context => { - if (!this.clipboardService.clipboardAvailable || this.clipboardService.state === 'denied') { - return true; + isHidden: (context, action) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); + + if (action === ACTION_DELETE) { + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + const resultColumn = data.getColumn(key.column); + const currentConstraint = resultColumn ? constraints.get(resultColumn.position) : undefined; + + return !currentConstraint || !isFilterConstraint(currentConstraint); + } + + if (action === ACTION_DATA_GRID_FILTERS_RESET_ALL) { + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + return constraints.filterConstraints.length === 0 && !model.requestInfo.requestFilter; } - const source = context.data.model.source as unknown as ResultSetDataSource; - const data = source.getAction(context.data.resultIndex, ResultSetDataAction); - const supportedOperations = data.getColumnOperations(context.data.key.column); + return true; + }, + + getActionInfo(context, action) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); + const resultColumn = data.getColumn(key.column); + + if (action === ACTION_DELETE) { + return { + ...action.info, + icon: 'filter-reset', + label: `Delete filter for "${resultColumn?.name ?? '?'}"`, + }; + } - return supportedOperations.length === 0; + return action.info; }, - panel: new ComputedContextMenuModel({ - id: 'clipboardValuePanel', - menuItemsGetter: context => { - if (context.contextType !== DataGridContextMenuService.cellContext) { - return []; + handler: async (context, action) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); + + if (action === ACTION_DELETE) { + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + const resultColumn = data.getColumn(key.column); + + if (!resultColumn) { + throw new Error(`Failed to get result column info for the following column index: "${key.column.index}"`); } - const valueGetter = () => this.clipboardService.clipboardValue || ''; - const items = this.getGeneralizedMenuItems(context, valueGetter, 'filter-clipboard', () => this.clipboardService.state === 'prompt'); + await model.request(() => { + constraints.deleteFilter(resultColumn.position); + }); + } - return [ - { - id: 'permission', - isPresent: () => true, - isHidden: () => this.clipboardService.state !== 'prompt', - isDisabled(context) { - return context.data.model.isLoading(); - }, - title: 'data_grid_table_context_menu_filter_clipboard_permission', - icon: 'permission', - onClick: async () => { - await this.clipboardService.read(); - }, - }, - ...items, - ]; - }, - }), - }); - this.dataGridContextMenuService.add(this.getMenuFilterToken(), { - id: 'cellValue', - order: 1, - title: 'data_grid_table_filter_cell_value', - icon: 'filter', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); + if (action === ACTION_DATA_GRID_FILTERS_RESET_ALL) { + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + + await model.request(() => { + constraints.deleteDataFilters(); + }); + } }, - isHidden: context => { - const { model, resultIndex, key } = context.data; + }); + + this.menuService.addCreator({ + menus: [MENU_DATA_GRID_FILTERS_CELL_VALUE], + isApplicable: context => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + const source = model.source as unknown as ResultSetDataSource; const data = source.getAction(resultIndex, ResultSetDataAction); + + if (model.isDisabled(resultIndex)) { + return false; + } + + const supportedOperations = data.getColumnOperations(key.column); + return supportedOperations.length > 0; + }, + getItems: (context, items) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + + const source = model.source as unknown as ResultSetDataSource; const format = source.getAction(resultIndex, ResultSetFormatAction); + const data = source.getAction(resultIndex, ResultSetDataAction); + + const cellValue = format.getText(key); const supportedOperations = data.getColumnOperations(key.column); - const value = data.getCellValue(key); + const columnLabel = data.getColumn(key.column)?.label || ''; - return value === undefined || supportedOperations.length === 0 || format.isNull(key); + const filters = supportedOperations + .filter(operation => !nullOperationsFilter(operation)) + .map(operation => { + const wrappedValue = wrapOperationArgument(operation.id, cellValue); + const clippedValue = replaceMiddle(wrappedValue, ' ... ', 8, 30); + + return new MenuBaseItem( + { + id: operation.id, + label: `${columnLabel} ${operation.expression} ${clippedValue}`, + icon: 'filter', + }, + { + onSelect: async () => { + await this.applyFilter( + model as unknown as IDatabaseDataModel, + resultIndex, + key.column, + operation.id, + wrappedValue, + ); + }, + }, + ); + }); + + return [...items, ...filters]; }, - panel: new ComputedContextMenuModel({ - id: 'cellValuePanel', - menuItemsGetter: context => { - const { model, resultIndex, key } = context.data; - const source = model.source as unknown as ResultSetDataSource; - const format = source.getAction(resultIndex, ResultSetFormatAction); - const cellValue = format.getText(key); - const items = this.getGeneralizedMenuItems(context, cellValue, 'filter'); - return items; - }, - }), }); - this.dataGridContextMenuService.add(this.getMenuFilterToken(), { - id: 'customValue', - order: 2, - title: 'data_grid_table_filter_custom_value', - icon: 'filter-custom', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden: context => { - const { model, resultIndex, key } = context.data; + + this.menuService.addCreator({ + menus: [MENU_DATA_GRID_FILTERS_CUSTOM], + isApplicable(context) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + const source = model.source as unknown as ResultSetDataSource; const data = source.getAction(resultIndex, ResultSetDataAction); - const cellValue = data.getCellValue(key); + const supportedOperations = data.getColumnOperations(key.column); + const cellValue = data.getCellValue(key); - return cellValue === undefined || supportedOperations.length === 0; + return cellValue !== undefined && supportedOperations.length > 0; }, - panel: new ComputedContextMenuModel({ - id: 'customValuePanel', - menuItemsGetter: context => { - const { model, resultIndex, key } = context.data; - const source = model.source as unknown as ResultSetDataSource; - const data = source.getAction(resultIndex, ResultSetDataAction); - const supportedOperations = data.getColumnOperations(key.column); - const columnLabel = data.getColumn(key.column)?.label || ''; - - return supportedOperations - .filter(operation => !nullOperationsFilter(operation)) - .map(operation => { - const title = `${columnLabel} ${operation.expression}`; + getItems: (context, items) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + + const source = model.source as unknown as ResultSetDataSource; + const data = source.getAction(resultIndex, ResultSetDataAction); + const format = source.getAction(resultIndex, ResultSetFormatAction); - return { + const supportedOperations = data.getColumnOperations(key.column); + const columnLabel = data.getColumn(key.column)?.label || ''; + const displayString = format.getText(key); + + const filters = supportedOperations + .filter(operation => !nullOperationsFilter(operation)) + .map(operation => { + const title = `${columnLabel} ${operation.expression}`; + + return new MenuBaseItem( + { id: operation.id, - isPresent: () => true, - isDisabled(context) { - return context.data.model.isLoading(); - }, - title: title + ' ..', + label: title + ' ..', icon: 'filter-custom', - onClick: async () => { - const source = model.source as unknown as ResultSetDataSource; - const format = source.getAction(resultIndex, ResultSetFormatAction); - const displayString = format.getText(key); + }, + { + onSelect: async () => { const customValue = await this.commonDialogService.open(FilterCustomValueDialog, { defaultValue: displayString, inputTitle: title + ':', @@ -304,131 +342,100 @@ export class DataGridContextMenuFilterService { customValue, ); }, - }; - }); - }, - }), - }); - this.dataGridContextMenuService.add(this.getMenuFilterToken(), { - id: 'isNullValue', - order: 3, - icon: 'filter', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const data = source.getAction(context.data.resultIndex, ResultSetDataAction); - const supportedOperations = data.getColumnOperations(context.data.key.column); + }, + ); + }); - return !supportedOperations.some(operation => operation.id === IS_NULL_ID); - }, - titleGetter: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const data = source.getAction(context.data.resultIndex, ResultSetDataAction); - const columnLabel = data.getColumn(context.data.key.column)?.label || ''; - return `${columnLabel} IS NULL`; - }, - onClick: async context => { - await this.applyFilter( - context.data.model as unknown as IDatabaseDataModel, - context.data.resultIndex, - context.data.key.column, - IS_NULL_ID, - ); + return [...items, ...filters]; }, }); - this.dataGridContextMenuService.add(this.getMenuFilterToken(), { - id: 'isNotNullValue', - order: 4, - icon: 'filter', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const data = source.getAction(context.data.resultIndex, ResultSetDataAction); - const supportedOperations = data.getColumnOperations(context.data.key.column); - return !supportedOperations.some(operation => operation.id === IS_NOT_NULL_ID); - }, - titleGetter: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const data = source.getAction(context.data.resultIndex, ResultSetDataAction); - const columnLabel = data.getColumn(context.data.key.column)?.label || ''; - return `${columnLabel} IS NOT NULL`; - }, - onClick: async context => { - await this.applyFilter( - context.data.model as unknown as IDatabaseDataModel, - context.data.resultIndex, - context.data.key.column, - IS_NOT_NULL_ID, - ); + this.menuService.setHandler({ + id: 'data-grid-filters-clipboard-handler', + menus: [MENU_DATA_GRID_FILTERS_CLIPBOARD], + handler: () => { + if (this.clipboardService.state === 'granted') { + this.clipboardService.read(); + } }, }); - this.dataGridContextMenuService.add(this.getMenuFilterToken(), { - id: 'deleteFilter', - order: 5, - icon: 'filter-reset', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden: context => { - const { model, resultIndex, key } = context.data; + + this.menuService.addCreator({ + menus: [MENU_DATA_GRID_FILTERS_CLIPBOARD], + isApplicable: context => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + const source = model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); const data = source.getAction(resultIndex, ResultSetDataAction); - const resultColumn = data.getColumn(key.column); - const currentConstraint = resultColumn ? constraints.get(resultColumn.position) : undefined; + const supportedOperations = data.getColumnOperations(key.column); - return !currentConstraint || !isFilterConstraint(currentConstraint); - }, - titleGetter: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const data = source.getAction(context.data.resultIndex, ResultSetDataAction); - const columnLabel = data.getColumn(context.data.key.column)?.name || ''; - return `Delete filter for ${columnLabel}`; + return this.clipboardService.clipboardAvailable && this.clipboardService.state !== 'denied' && supportedOperations.length > 0; }, - onClick: async context => { - const { model, resultIndex, key } = context.data; + getItems: (context, items) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + const source = model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); const data = source.getAction(resultIndex, ResultSetDataAction); - const resultColumn = data.getColumn(key.column); + const supportedOperations = data.getColumnOperations(key.column); + const columnLabel = data.getColumn(key.column)?.label || ''; + + const result = [...items]; + + if (this.clipboardService.state === 'prompt') { + const permission = new MenuBaseItem( + { + id: 'permission', + hidden: this.clipboardService.state !== 'prompt', + label: 'data_grid_table_context_menu_filter_clipboard_permission', + icon: 'permission', + }, + { + onSelect: async () => { + await this.clipboardService.read(); + }, + }, + { isDisabled: () => model.isLoading() }, + ); - if (!resultColumn) { - throw new Error(`Failed to get result column info for the following column index: "${key.column.index}"`); + result.push(permission); } - await model.request(() => { - constraints.deleteFilter(resultColumn.position); - }); - }, - }); - this.dataGridContextMenuService.add(this.getMenuFilterToken(), { - id: 'deleteAllFilters', - order: 6, - icon: 'filter-reset-all', - title: 'data_grid_table_filter_reset_all_filters', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden: context => { - const { model, resultIndex } = context.data; - const source = model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + if (this.clipboardService.state === 'granted') { + const filters = supportedOperations + .filter(operation => !nullOperationsFilter(operation)) + .map(operation => { + const val = this.clipboardService.clipboardValue || ''; + const wrappedValue = wrapOperationArgument(operation.id, val); + const clippedValue = replaceMiddle(wrappedValue, ' ... ', 8, 30); + const label = `${columnLabel} ${operation.expression} ${clippedValue}`; + + return new MenuBaseItem( + { id: operation.id, icon: 'filter-clipboard', label }, + { + onSelect: async () => { + const wrappedValue = wrapOperationArgument(operation.id, val); + + await this.applyFilter( + model as unknown as IDatabaseDataModel, + resultIndex, + key.column, + operation.id, + wrappedValue, + ); + }, + }, + { isDisabled: () => model.isLoading() }, + ); + }); - return constraints.filterConstraints.length === 0 && !model.requestInfo.requestFilter; - }, - onClick: async context => { - const { model, resultIndex } = context.data; - const source = model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + result.push(...filters); + } - await model.request(() => { - constraints.deleteDataFilters(); - }); + return result; }, }); } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS.ts new file mode 100644 index 0000000000..182065f92f --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS.ts @@ -0,0 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createMenu } from '@cloudbeaver/core-view'; + +export const MENU_DATA_GRID_FILTERS = createMenu('data-grid-filters', 'data_grid_table_filter', 'filter'); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS_CELL_VALUE.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS_CELL_VALUE.ts new file mode 100644 index 0000000000..ffcd76db8a --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS_CELL_VALUE.ts @@ -0,0 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createMenu } from '@cloudbeaver/core-view'; + +export const MENU_DATA_GRID_FILTERS_CELL_VALUE = createMenu('data-grid-filters-cell-value', 'data_grid_table_filter_cell_value', 'filter'); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS_CLIPBOARD.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS_CLIPBOARD.ts new file mode 100644 index 0000000000..3e457ab1d2 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS_CLIPBOARD.ts @@ -0,0 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createMenu } from '@cloudbeaver/core-view'; + +export const MENU_DATA_GRID_FILTERS_CLIPBOARD = createMenu('data-grid-filters-clipboard', 'ui_clipboard', 'filter-clipboard'); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS_CUSTOM.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS_CUSTOM.ts new file mode 100644 index 0000000000..205f089ba0 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/MENU_DATA_GRID_FILTERS_CUSTOM.ts @@ -0,0 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createMenu } from '@cloudbeaver/core-view'; + +export const MENU_DATA_GRID_FILTERS_CUSTOM = createMenu('data-grid-filters-custom', 'data_grid_table_filter_custom_value', 'filter-custom'); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts index 2c5bac60e0..1d2ff9ec31 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts @@ -6,7 +6,11 @@ * you may not use this file except in compliance with the License. */ import { injectable } from '@cloudbeaver/core-di'; +import { ActionService, MenuRadioItem, MenuService } from '@cloudbeaver/core-view'; import { + DATA_CONTEXT_DV_DDM, + DATA_CONTEXT_DV_DDM_RESULT_INDEX, + DATA_CONTEXT_DV_RESULT_KEY, DatabaseDataConstraintAction, EOrder, type IDatabaseDataModel, @@ -19,17 +23,15 @@ import { ResultSetDataSource, } from '@cloudbeaver/plugin-data-viewer'; -import { DataGridContextMenuService } from './DataGridContextMenuService.js'; +import { ACTION_DATA_GRID_ORDERING_DISABLE_ALL } from '../Actions/Ordering/ACTION_DATA_GRID_ORDERING_DISABLE_ALL.js'; +import { MENU_DATA_GRID_ORDERING } from './MENU_DATA_GRID_ORDERING.js'; @injectable() export class DataGridContextMenuOrderService { - private static readonly menuOrderToken = 'menuOrder'; - - constructor(private readonly dataGridContextMenuService: DataGridContextMenuService) {} - - getMenuOrderToken(): string { - return DataGridContextMenuOrderService.menuOrderToken; - } + constructor( + private readonly actionService: ActionService, + private readonly menuService: MenuService, + ) {} private async changeOrder(unknownModel: IDatabaseDataModel, resultIndex: number, column: IResultSetColumnKey, order: Order) { const model = unknownModel as any; @@ -50,102 +52,97 @@ export class DataGridContextMenuOrderService { } register(): void { - this.dataGridContextMenuService.add(this.dataGridContextMenuService.getMenuToken(), { - id: this.getMenuOrderToken(), - order: 1, - title: 'data_grid_table_order', - icon: 'order-arrow-unknown', - isPanel: true, - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden(context) { - const source = context.data.model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); - return !constraints.supported || context.data.model.isDisabled(context.data.resultIndex); - }, - }); - this.dataGridContextMenuService.add(this.getMenuOrderToken(), { - id: 'asc', - type: 'radio', - title: 'ASC', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isDisabled: context => context.data.model.isLoading(), - onClick: async context => { - await this.changeOrder(context.data.model, context.data.resultIndex, context.data.key.column, EOrder.asc); - }, - isChecked: context => { - const { model, resultIndex, key } = context.data; + this.menuService.addCreator({ + root: true, + contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX, DATA_CONTEXT_DV_RESULT_KEY], + isApplicable: context => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const source = model.source as unknown as ResultSetDataSource; - const data = source.getAction(resultIndex, ResultSetDataAction); - const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); - const resultColumn = data.getColumn(key.column); - return !!resultColumn && constraints.getOrder(resultColumn.position) === EOrder.asc; + if (!isResultSetDataSource(source)) { + return false; + } + + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + return constraints.supported && !model.isDisabled(resultIndex); }, + getItems: (context, items) => [...items, MENU_DATA_GRID_ORDERING], }); - this.dataGridContextMenuService.add(this.getMenuOrderToken(), { - id: 'desc', - type: 'radio', - title: 'DESC', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isDisabled: context => context.data.model.isLoading(), - onClick: async context => { - await this.changeOrder(context.data.model, context.data.resultIndex, context.data.key.column, EOrder.desc); - }, - isChecked: context => { - const { model, resultIndex, key } = context.data; + + this.menuService.addCreator({ + menus: [MENU_DATA_GRID_ORDERING], + getItems: (context, items) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + const source = model.source as unknown as ResultSetDataSource; const data = source.getAction(resultIndex, ResultSetDataAction); const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); const resultColumn = data.getColumn(key.column); - return !!resultColumn && constraints.getOrder(resultColumn.position) === EOrder.desc; + const result = [...items]; + + if (resultColumn) { + for (const order of [EOrder.asc, EOrder.desc, null]) { + result.push( + new MenuRadioItem( + { + id: `data-grid-ordering-${order ? order : 'disable'}`, + label: order ? order.toUpperCase() : 'data_grid_table_disable_order', + }, + { + onSelect: async () => { + await this.changeOrder(model, resultIndex, key.column, order); + }, + }, + { isChecked: () => constraints.getOrder(resultColumn.position) === order, isDisabled: () => model.isLoading() }, + ), + ); + } + } + + return [...result, ACTION_DATA_GRID_ORDERING_DISABLE_ALL]; }, }); - this.dataGridContextMenuService.add(this.getMenuOrderToken(), { - id: 'disableOrder', - type: 'radio', - title: 'data_grid_table_disable_order', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); + + this.actionService.addHandler({ + id: 'data-grid-ordering-handler', + actions: [ACTION_DATA_GRID_ORDERING_DISABLE_ALL], + contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX, DATA_CONTEXT_DV_RESULT_KEY], + isHidden(context, action) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + + if (action === ACTION_DATA_GRID_ORDERING_DISABLE_ALL) { + const source = model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + return !constraints.orderConstraints.length; + } + + return false; }, - isDisabled: context => context.data.model.isLoading(), - onClick: async context => { - await this.changeOrder(context.data.model, context.data.resultIndex, context.data.key.column, null); + isDisabled: context => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + return model.isLoading(); }, - isChecked: context => { - const { model, resultIndex, key } = context.data; + handler: async (context, action) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; const source = model.source as unknown as ResultSetDataSource; - const data = source.getAction(resultIndex, ResultSetDataAction); - const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); - const resultColumn = data.getColumn(key.column); - return !!resultColumn && constraints.getOrder(resultColumn.position) === null; - }, - }); - this.dataGridContextMenuService.add(this.getMenuOrderToken(), { - id: 'disableOrders', - title: 'data_grid_table_disable_all_orders', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); - }, - isHidden: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); - return !constraints.orderConstraints.length; - }, - isDisabled: context => context.data.model.isLoading(), - onClick: async context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const constraints = source.getAction(context.data.resultIndex, DatabaseDataConstraintAction); - await context.data.model.request(() => { - constraints.deleteOrders(); - }); + switch (action) { + case ACTION_DATA_GRID_ORDERING_DISABLE_ALL: { + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + + await model.request(() => { + constraints.deleteOrders(); + }); + break; + } + } }, }); } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts index 25d8c630d3..a14b08933f 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts @@ -8,8 +8,12 @@ import { selectFiles } from '@cloudbeaver/core-browser'; import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; +import { ACTION_DOWNLOAD, ACTION_UPLOAD, ActionService, MenuService } from '@cloudbeaver/core-view'; import { createResultSetBlobValue, + DATA_CONTEXT_DV_DDM, + DATA_CONTEXT_DV_DDM_RESULT_INDEX, + DATA_CONTEXT_DV_RESULT_KEY, DataViewerService, isResultSetDataSource, ResultSetDataContentAction, @@ -18,76 +22,88 @@ import { ResultSetFormatAction, } from '@cloudbeaver/plugin-data-viewer'; -import { DataGridContextMenuService } from './DataGridContextMenuService.js'; - @injectable() export class DataGridContextMenuSaveContentService { constructor( - private readonly dataGridContextMenuService: DataGridContextMenuService, private readonly notificationService: NotificationService, private readonly dataViewerService: DataViewerService, + private readonly actionService: ActionService, + private readonly menuService: MenuService, ) {} register(): void { - this.dataGridContextMenuService.add(this.dataGridContextMenuService.getMenuToken(), { - id: 'menuContentDownload', - order: 4, - title: 'ui_download', - icon: '/icons/export.svg', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); + this.menuService.addCreator({ + root: true, + contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX, DATA_CONTEXT_DV_RESULT_KEY], + isApplicable: context => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + return isResultSetDataSource(model.source); }, - onClick: async context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); - try { - await content.downloadFileData(context.data.key); - } catch (exception: any) { - this.notificationService.logException(exception, 'data_grid_table_context_menu_save_value_error'); + getItems: (context, items) => [...items, ACTION_UPLOAD, ACTION_DOWNLOAD], + }); + + this.actionService.addHandler({ + id: 'data-grid-save-content-handler', + actions: [ACTION_UPLOAD, ACTION_DOWNLOAD], + contexts: [DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX, DATA_CONTEXT_DV_RESULT_KEY], + isHidden: (context, action) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + + const source = model.source as unknown as ResultSetDataSource; + const content = source.getAction(resultIndex, ResultSetDataContentAction); + const format = source.getAction(resultIndex, ResultSetFormatAction); + + if (action === ACTION_DOWNLOAD) { + return !content.isDownloadable(key) || !this.dataViewerService.canExportData; } - }, - isHidden: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); - return !content.isDownloadable(context.data.key) || !this.dataViewerService.canExportData; - }, - isDisabled: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); + if (action === ACTION_UPLOAD) { + return !format.isBinary(key) || model.isReadonly(resultIndex); + } - return context.data.model.isLoading() || content.isLoading(context.data.key); - }, - }); - this.dataGridContextMenuService.add(this.dataGridContextMenuService.getMenuToken(), { - id: 'menuContentUpload', - order: 5, - title: 'ui_upload', - icon: '/icons/import.svg', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext && isResultSetDataSource(context.data.model.source); + return true; }, - onClick: async context => { - selectFiles(files => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const edit = source.getAction(context.data.resultIndex, ResultSetEditAction); - const file = files?.[0] ?? undefined; - if (file) { - edit.set(context.data.key, createResultSetBlobValue(file)); - } - }); - }, - isHidden: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const format = source.getAction(context.data.resultIndex, ResultSetFormatAction); + isDisabled(context, action) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; + + const source = model.source as unknown as ResultSetDataSource; + const content = source.getAction(resultIndex, ResultSetDataContentAction); + + if (action === ACTION_DOWNLOAD || action === ACTION_UPLOAD) { + return model.isLoading() || content.isLoading(key); + } - return !format.isBinary(context.data.key) || context.data.model.isReadonly(context.data.resultIndex); + return false; }, - isDisabled: context => { - const source = context.data.model.source as unknown as ResultSetDataSource; - const content = source.getAction(context.data.resultIndex, ResultSetDataContentAction); + handler: async (context, action) => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + const key = context.get(DATA_CONTEXT_DV_RESULT_KEY)!; - return context.data.model.isLoading() || content.isLoading(context.data.key); + const source = model.source as unknown as ResultSetDataSource; + const content = source.getAction(resultIndex, ResultSetDataContentAction); + const edit = source.getAction(resultIndex, ResultSetEditAction); + + if (action === ACTION_DOWNLOAD) { + try { + await content.downloadFileData(key); + } catch (exception: any) { + this.notificationService.logException(exception, 'data_grid_table_context_menu_save_value_error'); + } + } + + if (action === ACTION_UPLOAD) { + selectFiles(files => { + const file = files?.[0] ?? undefined; + if (file) { + edit.set(key, createResultSetBlobValue(file)); + } + }); + } }, }); } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuService.ts deleted file mode 100644 index a1673bbc2b..0000000000 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuService.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { injectable } from '@cloudbeaver/core-di'; -import { ContextMenuService, type IContextMenuItem, type IMenuPanel } from '@cloudbeaver/core-dialogs'; -import { Executor, type IExecutor } from '@cloudbeaver/core-executor'; -import type { IDatabaseDataModel, IDataPresentationActions, IDataTableActions, IResultSetElementKey } from '@cloudbeaver/plugin-data-viewer'; - -export interface IDataGridCellMenuContext { - model: IDatabaseDataModel; - actions: IDataTableActions; - spreadsheetActions: IDataPresentationActions; - resultIndex: number; - key: IResultSetElementKey; - simple: boolean; -} - -@injectable() -export class DataGridContextMenuService { - onRootMenuOpen: IExecutor; - static cellContext = 'data-grid-cell-context-menu'; - private static readonly menuToken = 'dataGridCell'; - - constructor(private readonly contextMenuService: ContextMenuService) { - this.onRootMenuOpen = new Executor(); - } - - getMenuToken(): string { - return DataGridContextMenuService.menuToken; - } - - constructMenuWithContext( - model: IDatabaseDataModel, - actions: IDataTableActions, - spreadsheetActions: IDataPresentationActions, - resultIndex: number, - key: IResultSetElementKey, - simple: boolean, - ): IMenuPanel { - return this.contextMenuService.createContextMenu( - { - menuId: this.getMenuToken(), - contextType: DataGridContextMenuService.cellContext, - data: { model, actions, spreadsheetActions, resultIndex, key, simple }, - }, - this.getMenuToken(), - ); - } - - openMenu( - model: IDatabaseDataModel, - actions: IDataTableActions, - spreadsheetActions: IDataPresentationActions, - resultIndex: number, - key: IResultSetElementKey, - simple: boolean, - ): void { - this.onRootMenuOpen.execute({ model, actions, spreadsheetActions, resultIndex, key, simple }); - } - - add(panelId: string, menuItem: IContextMenuItem): void { - this.contextMenuService.addMenuItem(panelId, menuItem); - } -} diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/MENU_DATA_GRID_EDITING.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/MENU_DATA_GRID_EDITING.ts new file mode 100644 index 0000000000..77d61b11e5 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/MENU_DATA_GRID_EDITING.ts @@ -0,0 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createMenu } from '@cloudbeaver/core-view'; + +export const MENU_DATA_GRID_EDITING = createMenu('data-grid-editing', 'data_grid_table_editing', 'edit'); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/MENU_DATA_GRID_ORDERING.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/MENU_DATA_GRID_ORDERING.ts new file mode 100644 index 0000000000..3344788604 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/MENU_DATA_GRID_ORDERING.ts @@ -0,0 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createMenu } from '@cloudbeaver/core-view'; + +export const MENU_DATA_GRID_ORDERING = createMenu('data-grid-ordering', 'data_grid_table_order', 'order-arrow-unknown'); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.module.css b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.module.css index abbd9a1564..fe1c8390db 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.module.css +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.module.css @@ -10,6 +10,7 @@ display: flex; overflow: hidden; box-sizing: border-box; + gap: 6px; } .container { @@ -18,10 +19,10 @@ } .menuContainer { - width: 25px; - height: 100%; + display: flex; box-sizing: border-box; overflow: hidden; + z-index: 1; &:empty { display: none; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.module.css b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.module.css index 24fd4d4dbf..a64ea14844 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.module.css +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.module.css @@ -6,39 +6,27 @@ * you may not use this file except in compliance with the License. */ -.iconOrImage { - composes: theme-text-primary from global; -} -:global(.rdg-cell):not(:global([aria-selected='true'])):not(:hover) .cellMenu { - display: none; -} -.cellMenu { - flex: 0 0 auto; - height: var(--rdg-row-height); - position: absolute; - top: 0px; - right: 0px; - height: 100%; +.contextMenu { + padding: 0; + height: 16px; + width: 16px; + + &:before { + display: none; + } } -.menuTrigger { - padding: 1px 2px; +.trigger { + cursor: pointer; display: flex; align-items: center; - height: 100%; - justify-content: center; - - &:hover { - background: none; - } +} - &:before { - display: none; - } +.icon { + width: 16px; + height: 10px; +} - & .icon { - cursor: pointer; - width: 16px; - height: 10px; - } +.iconOrImage { + composes: theme-text-primary from global; } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.tsx index bf0e70927c..ccd37dce02 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.tsx @@ -7,13 +7,26 @@ */ import { observer } from 'mobx-react-lite'; -import { Icon, MenuPanelItemAndTriggerStyles, MenuTrigger, s, SContext, type StyleRegistry, useS } from '@cloudbeaver/core-blocks'; -import { useService } from '@cloudbeaver/core-di'; +import { Icon, MenuItemElementStyles, s, SContext, type StyleRegistry, useS } from '@cloudbeaver/core-blocks'; +import { useDataContextLink } from '@cloudbeaver/core-data-context'; import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events'; -import type { IDatabaseDataModel, IDataPresentationActions, IDataTableActions, IResultSetElementKey } from '@cloudbeaver/plugin-data-viewer'; +import { ContextMenu } from '@cloudbeaver/core-ui'; +import { useMenu } from '@cloudbeaver/core-view'; +import { + DATA_CONTEXT_DV_ACTIONS, + DATA_CONTEXT_DV_DDM, + DATA_CONTEXT_DV_DDM_RESULT_INDEX, + DATA_CONTEXT_DV_PRESENTATION_ACTIONS, + DATA_CONTEXT_DV_RESULT_KEY, + DATA_CONTEXT_DV_SIMPLE, + type IDatabaseDataModel, + type IDataPresentationActions, + type IDataTableActions, + type IResultSetElementKey, + MENU_DV_CONTEXT_MENU, +} from '@cloudbeaver/plugin-data-viewer'; -import { DataGridContextMenuService } from '../../DataGridContextMenu/DataGridContextMenuService.js'; -import styles from './CellMenu.module.css'; +import classes from './CellMenu.module.css'; interface Props { model: IDatabaseDataModel; @@ -21,46 +34,32 @@ interface Props { spreadsheetActions: IDataPresentationActions; resultIndex: number; cellKey: IResultSetElementKey; - className?: string; simple: boolean; - onClick?: () => void; onStateSwitch?: (state: boolean) => void; } const registry: StyleRegistry = [ [ - MenuPanelItemAndTriggerStyles, + MenuItemElementStyles, { mode: 'append', - styles: [styles], + styles: [classes], }, ], ]; -export const CellMenu = observer(function CellMenu({ - model, - actions, - spreadsheetActions, - resultIndex, - className, - cellKey, - simple, - onClick, - onStateSwitch, -}) { - const style = useS(styles); - const dataGridContextMenuService = useService(DataGridContextMenuService); +export const CellMenu = observer(function CellMenu({ model, actions, spreadsheetActions, resultIndex, cellKey, simple, onStateSwitch }) { + const style = useS(classes); + const menu = useMenu({ menu: MENU_DV_CONTEXT_MENU }); - const panel = dataGridContextMenuService.constructMenuWithContext(model, actions, spreadsheetActions, resultIndex, cellKey, simple); - - if (!panel.menuItems.length || panel.menuItems.every(item => item.isHidden)) { - return null; - } - - function handleClick() { - dataGridContextMenuService.openMenu(model, actions, spreadsheetActions, resultIndex, cellKey, simple); - onClick?.(); - } + useDataContextLink(menu.context, (context, id) => { + context.set(DATA_CONTEXT_DV_DDM, model, id); + context.set(DATA_CONTEXT_DV_DDM_RESULT_INDEX, resultIndex, id); + context.set(DATA_CONTEXT_DV_SIMPLE, simple, id); + context.set(DATA_CONTEXT_DV_ACTIONS, actions, id); + context.set(DATA_CONTEXT_DV_PRESENTATION_ACTIONS, spreadsheetActions, id); + context.set(DATA_CONTEXT_DV_RESULT_KEY, cellKey, id); + }); function stopPropagation(event: React.MouseEvent) { event.stopPropagation(); @@ -72,11 +71,20 @@ export const CellMenu = observer(function CellMenu({ return ( -
- + +
- -
+
+
); }); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/SpreadsheetBootstrap.ts b/webapp/packages/plugin-data-spreadsheet-new/src/SpreadsheetBootstrap.ts index 415d03a409..1837bfd34e 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/SpreadsheetBootstrap.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/SpreadsheetBootstrap.ts @@ -9,15 +9,27 @@ import { importLazyComponent } from '@cloudbeaver/core-blocks'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ExceptionsCatcherService } from '@cloudbeaver/core-events'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; -import { DataPresentationService } from '@cloudbeaver/plugin-data-viewer'; +import { ACTION_DELETE, ACTION_OPEN, ActionService, MenuService } from '@cloudbeaver/core-view'; +import { + DATA_CONTEXT_DV_ACTIONS, + DATA_CONTEXT_DV_DDM, + DATA_CONTEXT_DV_DDM_RESULT_INDEX, + DATA_CONTEXT_DV_SIMPLE, + DatabaseDataConstraintAction, + DataPresentationService, + isResultSetDataSource, + MENU_DV_CONTEXT_MENU, + ResultSetDataSource, +} from '@cloudbeaver/plugin-data-viewer'; import { DataGridContextMenuCellEditingService } from './DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.js'; import { DataGridContextMenuFilterService } from './DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.js'; import { DataGridContextMenuOrderService } from './DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.js'; import { DataGridContextMenuSaveContentService } from './DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.js'; -import { DataGridContextMenuService } from './DataGrid/DataGridContextMenu/DataGridContextMenuService.js'; import { DataGridSettingsService } from './DataGridSettingsService.js'; +const VALUE_TEXT_PRESENTATION_ID = 'value-text-presentation'; + const SpreadsheetGrid = importLazyComponent(() => import('./SpreadsheetGrid.js').then(m => m.SpreadsheetGrid)); @injectable() @@ -28,8 +40,9 @@ export class SpreadsheetBootstrap extends Bootstrap { private readonly dataGridContextMenuSortingService: DataGridContextMenuOrderService, private readonly dataGridContextMenuFilterService: DataGridContextMenuFilterService, private readonly dataGridContextMenuCellEditingService: DataGridContextMenuCellEditingService, - private readonly dataGridContextMenuService: DataGridContextMenuService, private readonly dataGridContextMenuSaveContentService: DataGridContextMenuSaveContentService, + private readonly actionService: ActionService, + private readonly menuService: MenuService, exceptionsCatcherService: ExceptionsCatcherService, ) { super(); @@ -45,24 +58,78 @@ export class SpreadsheetBootstrap extends Bootstrap { title: 'Table', icon: 'table-icon-sm', }); + this.dataGridContextMenuSortingService.register(); this.dataGridContextMenuFilterService.register(); this.dataGridContextMenuCellEditingService.register(); this.dataGridContextMenuSaveContentService.register(); - this.dataGridContextMenuService.add(this.dataGridContextMenuService.getMenuToken(), { - id: 'view_value_panel', - isPresent(context) { - return context.contextType === DataGridContextMenuService.cellContext; + this.menuService.addCreator({ + root: true, + menus: [MENU_DV_CONTEXT_MENU], + contexts: [DATA_CONTEXT_DV_SIMPLE, DATA_CONTEXT_DV_ACTIONS, DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX], + getItems: (context, items) => [ACTION_OPEN, ...items, ACTION_DELETE], + }); + + this.actionService.addHandler({ + id: 'data-grid-key-base-handler', + menus: [MENU_DV_CONTEXT_MENU], + contexts: [DATA_CONTEXT_DV_SIMPLE, DATA_CONTEXT_DV_ACTIONS, DATA_CONTEXT_DV_DDM, DATA_CONTEXT_DV_DDM_RESULT_INDEX], + getActionInfo: (context, action) => { + if (action === ACTION_OPEN) { + return { ...action.info, label: 'data_grid_table_open_value_panel', icon: 'value-panel' }; + } + + if (action === ACTION_DELETE) { + return { ...action.info, label: 'data_grid_table_delete_filters_and_orders', icon: 'erase' }; + } + + return action.info; }, - isHidden(context) { - return context.data.actions.valuePresentationId === 'value-text-presentation' || context.data.simple; + isActionApplicable: (context, action): boolean => { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + + if (action === ACTION_OPEN) { + const actions = context.get(DATA_CONTEXT_DV_ACTIONS); + const simple = context.get(DATA_CONTEXT_DV_SIMPLE); + + return actions?.valuePresentationId !== VALUE_TEXT_PRESENTATION_ID && !simple; + } + + if (action === ACTION_DELETE) { + const source = model.source as unknown as ResultSetDataSource; + + if (!isResultSetDataSource(model.source)) { + return false; + } + + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + return constraints.orderConstraints.length > 0 || constraints.filterConstraints.length > 0; + } + + return [ACTION_OPEN, ACTION_DELETE].includes(action); }, - order: 0.5, - title: 'data_grid_table_open_value_panel', - icon: 'value-panel', - onClick(context) { - context.data.actions.setValuePresentation('value-text-presentation'); + handler: async (context, action) => { + if (action === ACTION_OPEN) { + const actions = context.get(DATA_CONTEXT_DV_ACTIONS); + + if (actions) { + actions.setValuePresentation(VALUE_TEXT_PRESENTATION_ID); + } + } + + if (action === ACTION_DELETE) { + const model = context.get(DATA_CONTEXT_DV_DDM)!; + const resultIndex = context.get(DATA_CONTEXT_DV_DDM_RESULT_INDEX)!; + + const source = model.source as unknown as ResultSetDataSource; + const constraints = source.getAction(resultIndex, DatabaseDataConstraintAction); + + await model.request(() => { + constraints.deleteData(); + }); + } }, }); } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/manifest.ts b/webapp/packages/plugin-data-spreadsheet-new/src/manifest.ts index c82f27c91b..ea9f300dc3 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/manifest.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/manifest.ts @@ -13,7 +13,6 @@ export const dataSpreadsheetNewManifest: PluginManifest = { () => import('./SpreadsheetBootstrap.js').then(m => m.SpreadsheetBootstrap), () => import('./DataGridSettingsService.js').then(m => m.DataGridSettingsService), () => import('./LocaleService.js').then(m => m.LocaleService), - () => import('./DataGrid/DataGridContextMenu/DataGridContextMenuService.js').then(m => m.DataGridContextMenuService), () => import('./DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.js').then(m => m.DataGridContextMenuOrderService), () => import('./DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.js').then( diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/DATA_CONTEXT_DV_RESULT_KEY.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/DATA_CONTEXT_DV_RESULT_KEY.ts new file mode 100644 index 0000000000..a94c193f9a --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/DATA_CONTEXT_DV_RESULT_KEY.ts @@ -0,0 +1,12 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createDataContext } from '@cloudbeaver/core-data-context'; + +import type { IResultSetElementKey } from './IResultSetDataKey.js'; + +export const DATA_CONTEXT_DV_RESULT_KEY = createDataContext('data-viewer-database-result-key'); diff --git a/webapp/packages/plugin-data-viewer/src/MENU_DV_CONTEXT_MENU.ts b/webapp/packages/plugin-data-viewer/src/MENU_DV_CONTEXT_MENU.ts new file mode 100644 index 0000000000..f4bb5946ba --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/MENU_DV_CONTEXT_MENU.ts @@ -0,0 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createMenu } from '@cloudbeaver/core-view'; + +export const MENU_DV_CONTEXT_MENU = createMenu('dv-context-menu', 'Data Viewer Context Menu'); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/DATA_CONTEXT_DV_ACTIONS.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/DATA_CONTEXT_DV_ACTIONS.ts new file mode 100644 index 0000000000..8c7faf42af --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/DATA_CONTEXT_DV_ACTIONS.ts @@ -0,0 +1,12 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createDataContext } from '@cloudbeaver/core-data-context'; + +import type { IDataTableActions } from './IDataTableActions.js'; + +export const DATA_CONTEXT_DV_ACTIONS = createDataContext('data-viewer-database-actions'); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/DATA_CONTEXT_DV_PRESENTATION_ACTIONS.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/DATA_CONTEXT_DV_PRESENTATION_ACTIONS.ts new file mode 100644 index 0000000000..1964738e53 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/DATA_CONTEXT_DV_PRESENTATION_ACTIONS.ts @@ -0,0 +1,15 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { createDataContext } from '@cloudbeaver/core-data-context'; + +import type { IResultSetElementKey } from '../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey.js'; +import type { IDataPresentationActions } from './IDataPresentationActions.js'; + +export const DATA_CONTEXT_DV_PRESENTATION_ACTIONS = createDataContext>( + 'data-viewer-database-presentation-actions', +); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_DATA_VIEWER_SIMPLE.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/DATA_CONTEXT_DV_SIMPLE.ts similarity index 73% rename from webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_DATA_VIEWER_SIMPLE.ts rename to webapp/packages/plugin-data-viewer/src/TableViewer/DATA_CONTEXT_DV_SIMPLE.ts index 898176e650..82830f90a0 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_DATA_VIEWER_SIMPLE.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/DATA_CONTEXT_DV_SIMPLE.ts @@ -7,4 +7,4 @@ */ import { createDataContext } from '@cloudbeaver/core-data-context'; -export const DATA_CONTEXT_DATA_VIEWER_SIMPLE = createDataContext('data-viewer-database-simple'); +export const DATA_CONTEXT_DV_SIMPLE = createDataContext('data-viewer-database-simple'); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenu.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenu.tsx index d86808b495..237dc6c753 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenu.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenu.tsx @@ -16,7 +16,7 @@ import { useMenu } from '@cloudbeaver/core-view'; import { DATA_CONTEXT_DV_DDM } from '../../../DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM.js'; import { DATA_CONTEXT_DV_DDM_RESULT_INDEX } from '../../../DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM_RESULT_INDEX.js'; import type { IDatabaseDataModel } from '../../../DatabaseDataModel/IDatabaseDataModel.js'; -import { DATA_CONTEXT_DATA_VIEWER_SIMPLE } from '../../TableHeader/DATA_CONTEXT_DATA_VIEWER_SIMPLE.js'; +import { DATA_CONTEXT_DV_SIMPLE } from '../../DATA_CONTEXT_DV_SIMPLE.js'; import { DATA_VIEWER_DATA_MODEL_ACTIONS_MENU } from './DATA_VIEWER_DATA_MODEL_ACTIONS_MENU.js'; import { REFRESH_MENU_ITEM_REGISTRY } from './RefreshAction/RefreshMenuAction.js'; @@ -35,7 +35,7 @@ export const TableFooterMenu = observer(function TableFooterMenu({ result useDataContextLink(menu.context, (context, id) => { context.set(DATA_CONTEXT_DV_DDM, model, id); context.set(DATA_CONTEXT_DV_DDM_RESULT_INDEX, resultIndex, id); - context.set(DATA_CONTEXT_DATA_VIEWER_SIMPLE, simple, id); + context.set(DATA_CONTEXT_DV_SIMPLE, simple, id); }); return ( diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx index 528118435b..a3484af4e9 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx @@ -14,7 +14,7 @@ import { useMenu } from '@cloudbeaver/core-view'; import { DATA_CONTEXT_DV_DDM } from '../../DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM.js'; import { DATA_CONTEXT_DV_DDM_RESULT_INDEX } from '../../DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM_RESULT_INDEX.js'; -import { DATA_CONTEXT_DATA_VIEWER_SIMPLE } from './DATA_CONTEXT_DATA_VIEWER_SIMPLE.js'; +import { DATA_CONTEXT_DV_SIMPLE } from '../DATA_CONTEXT_DV_SIMPLE.js'; import { DATA_VIEWER_DATA_MODEL_TOOLS_MENU } from './DATA_VIEWER_DATA_MODEL_TOOLS_MENU.js'; import type { ITableHeaderPlaceholderProps } from './TableHeaderService.js'; @@ -25,7 +25,7 @@ export const TableHeaderMenu: PlaceholderComponent useDataContextLink(menu.context, (context, id) => { context.set(DATA_CONTEXT_DV_DDM, model, id); context.set(DATA_CONTEXT_DV_DDM_RESULT_INDEX, resultIndex, id); - context.set(DATA_CONTEXT_DATA_VIEWER_SIMPLE, simple, id); + context.set(DATA_CONTEXT_DV_SIMPLE, simple, id); }); return ; diff --git a/webapp/packages/plugin-data-viewer/src/index.ts b/webapp/packages/plugin-data-viewer/src/index.ts index 6694d88dc1..29950e0e4a 100644 --- a/webapp/packages/plugin-data-viewer/src/index.ts +++ b/webapp/packages/plugin-data-viewer/src/index.ts @@ -15,6 +15,10 @@ export * from './DatabaseDataModel/Actions/ResultSet/DataContext/DATA_CONTEXT_DV export * from './DatabaseDataModel/DataContext/DATA_CONTEXT_DV_PRESENTATION.js'; export * from './DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM.js'; export * from './DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM_RESULT_INDEX.js'; +export * from './TableViewer/DATA_CONTEXT_DV_SIMPLE.js'; +export * from './DatabaseDataModel/Actions/ResultSet/DATA_CONTEXT_DV_RESULT_KEY.js'; +export * from './TableViewer/DATA_CONTEXT_DV_ACTIONS.js'; +export * from './TableViewer/DATA_CONTEXT_DV_PRESENTATION_ACTIONS.js'; export * from './DatabaseDataModel/Actions/ResultSet/compareResultSetRowKeys.js'; export * from './DatabaseDataModel/Actions/ResultSet/createResultSetBlobValue.js'; export * from './DatabaseDataModel/Actions/ResultSet/createResultSetContentValue.js'; @@ -89,3 +93,4 @@ export * from './ValuePanelPresentation/BooleanValue/isBooleanValuePresentationA export * from './useDataViewerCopyHandler.js'; export * from './DataViewerSettingsService.js'; export * from './DATA_EDITOR_SETTINGS_GROUP.js'; +export * from './MENU_DV_CONTEXT_MENU.js';