diff --git a/webapp/common-react/@dbeaver/react-translate/src/TranslateContext.ts b/webapp/common-react/@dbeaver/react-translate/src/TranslateContext.ts index f2605c4a63..eef5a781f9 100644 --- a/webapp/common-react/@dbeaver/react-translate/src/TranslateContext.ts +++ b/webapp/common-react/@dbeaver/react-translate/src/TranslateContext.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -9,11 +9,31 @@ import { createContext } from 'react'; import type { LocalizationToken } from './LocalizationToken.js'; -export function defaultTranslateFn(token: T, fallback?: T): T { - return fallback || token; +export function interpolateTranslation(template: string, args?: Record): string { + if (!args) { + return template; + } + + let result = template; + + for (const [key, value] of Object.entries(args)) { + if (value != null) { + result = result.split(`{arg:${key}}`).join(String(value)); + } + } + + return result; } -export type TranslateFn = (token: T, fallback?: T, args?: Record) => T; +export function defaultTranslateFn(token: T, fallback?: T, args?: Record): T { + const result = fallback || token; + if (result && args) { + return interpolateTranslation(result as string, args) as T; + } + return result; +} + +export type TranslateFn = (token: T, fallback?: T, args?: Record) => T; export const TranslateContext = createContext<{ translate: TranslateFn }>({ - translate: (key, fallback) => fallback || key, + translate: defaultTranslateFn, }); diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Button/Button.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Button/Button.tsx index fd94ee9e73..e942dc5c54 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Button/Button.tsx +++ b/webapp/common-react/@dbeaver/ui-kit/src/Button/Button.tsx @@ -1,11 +1,12 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 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 clsx from 'clsx'; +import { Button as AriaButton, type ButtonProps as UnstyledButtonProps } from '@ariakit/react'; import type { ControlSize } from '../types/controls.js'; import { componentProviderWrapper } from '../componentProviderWrapper.js'; import { ButtonRoot, type ButtonRootProps } from './ButtonRoot.js'; @@ -37,3 +38,6 @@ export function ButtonBaseIcon({ className, children, placement }: ButtonIconPro export const Button = componentProviderWrapper('Button', ButtonBase); export const ButtonIcon = componentProviderWrapper('ButtonIcon', ButtonBaseIcon); + +export const UnstyledButton = AriaButton; +export { type UnstyledButtonProps }; \ No newline at end of file diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Button/_base.css b/webapp/common-react/@dbeaver/ui-kit/src/Button/_base.css index 23a0f00b2f..831cb41fc1 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Button/_base.css +++ b/webapp/common-react/@dbeaver/ui-kit/src/Button/_base.css @@ -1,7 +1,7 @@ @layer base { :root { --dbv-kit-btn-height: var(--dbv-kit-control-height-medium); - --dbv-kit-btn-padding-inline: calc(var(--tw-spacing) * 2.5); + --dbv-kit-btn-padding-inline: calc(var(--tw-spacing) * 3); --dbv-kit-btn-gap: calc(var(--dbv-kit-btn-padding-inline) / 2); --dbv-kit-btn-outline-offset: var(--dbv-kit-control-outline-offset); diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Composite/Composite.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Composite/Composite.tsx new file mode 100644 index 0000000000..d051a16d5b --- /dev/null +++ b/webapp/common-react/@dbeaver/ui-kit/src/Composite/Composite.tsx @@ -0,0 +1,32 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { + Composite as AriakitComposite, + CompositeItem as AriakitCompositeItem, + CompositeProvider as AriakitCompositeProvider, + useCompositeStore, + type CompositeProps, + type CompositeItemProps, + type CompositeProviderProps, + type CompositeStore, +} from '@ariakit/react'; + +export function CompositeProvider({ children, ...props }: CompositeProviderProps) { + return {children}; +} + +export function Composite({ children, ...props }: CompositeProps) { + return {children}; +} + +export function CompositeItem({ children, ...props }: CompositeItemProps) { + return {children}; +} + +export { useCompositeStore, type CompositeProps, type CompositeItemProps, type CompositeProviderProps, type CompositeStore }; \ No newline at end of file diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Menu/Menu.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Menu/Menu.tsx index ab1838aacd..aff7ab7292 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Menu/Menu.tsx +++ b/webapp/common-react/@dbeaver/ui-kit/src/Menu/Menu.tsx @@ -45,6 +45,8 @@ import { type MenuListProps, type MenuBarProps, type MenuItemCheckProps, + type MenuStore, + type MenuStoreState } from '@ariakit/react'; import clsx from 'clsx'; @@ -175,6 +177,8 @@ export { type MenuStoreProps, type MenuArrowProps, type HovercardStoreState, + type MenuStore, + type MenuStoreState }; Menu.Provider = MenuProvider; diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Popover/Popover.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Popover/Popover.tsx index c5e70bb742..94248eaa6a 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/Popover/Popover.tsx +++ b/webapp/common-react/@dbeaver/ui-kit/src/Popover/Popover.tsx @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -11,6 +11,10 @@ import clsx from 'clsx'; import './Popover.css'; +export function PopoverAnchor({ children, ...props }: Ariakit.PopoverAnchorProps) { + return {children}; +} + export function Popover({ children, ...props }: Ariakit.PopoverProviderProps) { return {children}; } @@ -40,3 +44,6 @@ Popover.PopoverArrow = PopoverArrow; Popover.PopoverHeading = PopoverHeading; Popover.PopoverDescription = PopoverDescription; Popover.PopoverContent = PopoverContent; +Popover.Anchor = PopoverAnchor; + +export { usePopoverStore, type PopoverStore } from '@ariakit/react'; \ No newline at end of file diff --git a/webapp/common-react/@dbeaver/ui-kit/src/Portal/Portal.tsx b/webapp/common-react/@dbeaver/ui-kit/src/Portal/Portal.tsx new file mode 100644 index 0000000000..7da9521ae4 --- /dev/null +++ b/webapp/common-react/@dbeaver/ui-kit/src/Portal/Portal.tsx @@ -0,0 +1,15 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { Portal as AriakitPortal, type PortalProps } from '@ariakit/react'; + +export function Portal({ children, ...props }: PortalProps) { + return {children}; +} + +export type { PortalProps }; \ No newline at end of file diff --git a/webapp/common-react/@dbeaver/ui-kit/src/index.ts b/webapp/common-react/@dbeaver/ui-kit/src/index.ts index 5596debaf8..8c6d0485d6 100644 --- a/webapp/common-react/@dbeaver/ui-kit/src/index.ts +++ b/webapp/common-react/@dbeaver/ui-kit/src/index.ts @@ -21,7 +21,7 @@ export { type FocusTrapRegionProps, } from '@ariakit/react'; -export { Button, ButtonBase, type ButtonProps, ButtonIcon, type ButtonIconProps } from './Button/Button.js'; +export { Button, ButtonBase, type ButtonProps, ButtonIcon, type ButtonIconProps, UnstyledButton, type UnstyledButtonProps } from './Button/Button.js'; export { IconButton, IconButtonBase, type IconButtonProps } from './IconButton/IconButton.js'; export { Checkbox, CheckboxBase, type CheckboxProps } from './Checkbox/Checkbox.js'; export { ColorPicker } from './ColorPicker/ColorPicker.js'; @@ -46,7 +46,7 @@ export { } from './Select/Select.js'; export * from './Combobox/Combobox.js'; export * from './Command/Command.js'; -export { Popover } from './Popover/Popover.js'; +export { Popover, usePopoverStore, type PopoverStore } from './Popover/Popover.js'; export { SelectField, type SelectFieldProps, type SelectItem } from './Select/SelectField.js'; export { Spinner, type SpinnerProps } from './Spinner/Spinner.js'; export { Radio, RadioGroup, useRadioContext, useRadioStore, type RadioProviderProps, type RadioProps, type RadioGroupProps } from './Radio/index.js'; @@ -57,3 +57,14 @@ export * from './Menu/Menu.js'; export * from './Disclosure/Disclosure.js'; export * from './Dialog/Dialog.js'; export * from './Tab/Tab.js'; +export { Portal, type PortalProps } from './Portal/Portal.js'; +export { + Composite, + CompositeItem, + CompositeProvider, + useCompositeStore, + type CompositeProps, + type CompositeItemProps, + type CompositeProviderProps, + type CompositeStore, +} from './Composite/Composite.js'; diff --git a/webapp/common-react/package.json b/webapp/common-react/package.json index 3bbab6148d..56740aaaf1 100644 --- a/webapp/common-react/package.json +++ b/webapp/common-react/package.json @@ -11,6 +11,9 @@ "clear": "rimraf -g '@dbeaver/*/{dist,lib}'", "test": "dbeaver-test" }, + "resolutions": { + "happy-dom": ">=20.8.8" + }, "devDependencies": { "@dbeaver/cli": "workspace:^", "@dbeaver/prettier-config": "workspace:^", diff --git a/webapp/common-typescript/@dbeaver/js-helpers/src/formatNumber.ts b/webapp/common-typescript/@dbeaver/js-helpers/src/formatNumber.ts new file mode 100644 index 0000000000..16699a7a95 --- /dev/null +++ b/webapp/common-typescript/@dbeaver/js-helpers/src/formatNumber.ts @@ -0,0 +1,24 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 function formatNumber(n: number, d: number): string { + if (n < 1000) { + return n.toLocaleString(undefined, { maximumFractionDigits: 2 }); + } + + const numStr = n.toString(); + const exponent = numStr.length - (numStr.length % 3); + + const power = Math.pow(10, d); + const rounded = Math.round((n * power) / Math.pow(10, exponent)) / power; + + const units = ' kMBTPE'; + const unit = units[exponent / 3]!; + + return rounded + unit; +} \ No newline at end of file diff --git a/webapp/common-typescript/@dbeaver/js-helpers/src/index.ts b/webapp/common-typescript/@dbeaver/js-helpers/src/index.ts index e8565e0642..8a8f1be87e 100644 --- a/webapp/common-typescript/@dbeaver/js-helpers/src/index.ts +++ b/webapp/common-typescript/@dbeaver/js-helpers/src/index.ts @@ -16,3 +16,4 @@ export * from './memoizeLast.js'; export * from './mutex.js'; export * from './reorderArray.js'; export * from './getLocalizedDisplayName.js'; +export * from './formatNumber.js'; diff --git a/webapp/package.json b/webapp/package.json index 2b49086829..59f95ce006 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -24,7 +24,9 @@ "koa": ">=2.16.4", "immutable": ">=4.3.8", "serialize-javascript": ">=7.0.3", - "flatted": ">=3.4.2" + "flatted": ">=3.4.2", + "picomatch": ">=2.3.2", + "happy-dom": ">=20.8.8" }, "scripts": { "test": "dbeaver-test", @@ -59,7 +61,6 @@ "prettier": "^3.6.2", "react": "^19", "react-dom": "^19", - "reakit": "^1", "rimraf": "^6", "typescript": "^5.8.3", "typescript-plugin-css-modules": "^5", diff --git a/webapp/packages/core-blocks/package.json b/webapp/packages/core-blocks/package.json index a00aa4d2cb..4d8f460cd6 100644 --- a/webapp/packages/core-blocks/package.json +++ b/webapp/packages/core-blocks/package.json @@ -44,8 +44,6 @@ "react-dom": "^19", "react-hotkeys-hook": "5.1.0", "react-minisearch": "^7", - "reakit": "^1", - "reakit-utils": "^0", "subscript": "^9", "tslib": "^2" }, diff --git a/webapp/packages/core-blocks/src/Button.css b/webapp/packages/core-blocks/src/Button.css index ffd49fb1ac..013e968c72 100644 --- a/webapp/packages/core-blocks/src/Button.css +++ b/webapp/packages/core-blocks/src/Button.css @@ -7,7 +7,6 @@ */ @layer components { .dbv-kit-button { - --dbv-kit-btn-padding-inline: 15px; --dbv-kit-btn-outline-color: var(--theme-primary); --dbv-kit-control-disabled-foreground: #3535355e; --dbv-kit-control-disabled-background: #3535351f; @@ -22,7 +21,7 @@ --dbv-kit-btn-primary-border-color: var(--theme-primary); --dbv-kit-btn-secondary-background: transparent; - --dbv-kit-btn-secondary-background-hover: color-mix(in srgb, transparent, var(--theme-primary) 5%); + --dbv-kit-btn-secondary-background-hover: color-mix(in srgb, transparent, var(--theme-primary) 10%); --dbv-kit-btn-secondary-background-active: color-mix(in srgb, transparent, var(--theme-primary) 20%); --dbv-kit-btn-secondary-foreground: var(--theme-link-color); --dbv-kit-btn-secondary-foreground-hover: var(--theme-link-color); @@ -37,7 +36,7 @@ --dbv-kit-btn-ghost-foreground-hover: var(--theme-link-color); --dbv-kit-btn-ghost-foreground-active: var(--theme-link-color); --dbv-kit-btn-ghost-background: transparent; - --dbv-kit-btn-ghost-background-hover: color-mix(in srgb, transparent, var(--theme-primary) 5%); + --dbv-kit-btn-ghost-background-hover: color-mix(in srgb, transparent, var(--theme-primary) 10%); --dbv-kit-btn-ghost-background-active: color-mix(in srgb, transparent, var(--theme-primary) 20%); --dbv-kit-btn-ghost-border-color: transparent; diff --git a/webapp/packages/core-blocks/src/FormControls/InputAutocompletionMenu.module.css b/webapp/packages/core-blocks/src/FormControls/InputAutocompletionMenu.module.css deleted file mode 100644 index d06eb71803..0000000000 --- a/webapp/packages/core-blocks/src/FormControls/InputAutocompletionMenu.module.css +++ /dev/null @@ -1,11 +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. - */ - -.menu { - display: none !important; -} diff --git a/webapp/packages/core-blocks/src/FormControls/InputAutocompletionMenu.tsx b/webapp/packages/core-blocks/src/FormControls/InputAutocompletionMenu.tsx index 7d3b06d3ba..650804cd11 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputAutocompletionMenu.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputAutocompletionMenu.tsx @@ -5,78 +5,40 @@ * 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 { RefObject } from 'react'; -import BaseDropdownStyles from '../FormControls/BaseDropdown.module.css'; -import { IconOrImage } from '../IconOrImage.js'; -import { Menu } from '../Menu/Menu.js'; -import { MenuItem } from '../Menu/MenuItem.js'; -import type { IMenuState } from '../Menu/MenuStateContext.js'; -import { s } from '../s.js'; -import { Text } from '../Text.js'; -import { useS } from '../useS.js'; -import style from './InputAutocompletionMenu.module.css'; +import { observer } from 'mobx-react-lite'; +import { Composite, CompositeItem, Popover } from '@dbeaver/ui-kit'; + import { type InputAutocompleteProposal } from './useInputAutocomplete.js'; +import { useS } from '../useS.js'; +import { BaseDropdownStyles, IconOrImage, s, Text } from '../index.js'; +import { forwardRef } from 'react'; interface AutocompletionProps { - position: { x: number; y: number }; proposals: InputAutocompleteProposal[]; - menuRef: RefObject; className?: string; onSelect?: (proposal: InputAutocompleteProposal) => void; } -export const InputAutocompletionMenu = observer(function InputAutocompletionMenu({ - position, - className, - menuRef, - proposals, - onSelect, -}: AutocompletionProps) { - const styles = useS(style, BaseDropdownStyles); - const contextMenuPosition = { - position, - open: () => {}, - close: () => {}, - }; +export const InputAutocompletionMenu = observer( + forwardRef(function InputAutocompletionMenu({ proposals, onSelect }, ref) { + const styles = useS(BaseDropdownStyles); - function handleSelect(proposal: InputAutocompleteProposal) { - menuRef.current?.hide(); - onSelect?.(proposal); - } - - if (!proposals.length) { - return; - } - - // TODO: migrate to new menu or popover - return ( - 0} - panelAvailable={proposals.length > 0} - className={s(styles, { menu: true }, 'tw:shadow-small', className)} - menuRef={menuRef} - label="Autocompletion" - items={proposals.map(proposal => ( - handleSelect(proposal)} - > - {proposal.icon && ( -
- -
- )} - {proposal.displayString} -
- ))} - modal - /> - ); -}); + return ( + + + {proposals.map(proposal => ( + onSelect?.(proposal)}> + {proposal.icon && ( +
+ +
+ )} + {proposal.displayString} +
+ ))} +
+
+ ); + }), +); diff --git a/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.module.css b/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.module.css index 2d866a5806..8cd1e7c66c 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.module.css +++ b/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.module.css @@ -18,12 +18,3 @@ align-items: center; gap: 2px; } -.iconButton { - width: 12px; - height: 12px; - flex-shrink: 0; - cursor: pointer; - &:hover { - opacity: 0.5; - } -} diff --git a/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.tsx b/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.tsx index d7108bcdc4..06cbc4a78a 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputFileTextContent.tsx @@ -9,10 +9,10 @@ import { observer } from 'mobx-react-lite'; import { type ReactNode, useContext, useState } from 'react'; import { blobToBase64, bytesToSize } from '@cloudbeaver/core-utils'; +import { IconButton } from '@dbeaver/ui-kit'; import { Button } from '../Button.js'; import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps.js'; -import { IconButton } from '../IconButton.js'; import { useTranslate } from '../localization/useTranslate.js'; import { s } from '../s.js'; import { UploadArea } from '../UploadArea.js'; @@ -22,6 +22,7 @@ import { FieldDescription } from './FieldDescription.js'; import { FieldLabel } from './FieldLabel.js'; import { FormContext } from './FormContext.js'; import inputFileTextContentStyles from './InputFileTextContent.module.css'; +import { Icon } from '../Icon.js'; const DEFAULT_MAX_FILE_SIZE = 2048; @@ -139,13 +140,24 @@ export const InputFileTextContent: InputFileTextContentType = observer(function {children} - {description} - {(selected || saved) && } + {(selected || saved) && ( + + + + )} ); diff --git a/webapp/packages/core-blocks/src/FormControls/InputFiles.tsx b/webapp/packages/core-blocks/src/FormControls/InputFiles.tsx index 9940effe85..3342cafc53 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputFiles.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputFiles.tsx @@ -184,7 +184,7 @@ export const InputFiles: InputFilesType = observer( diff --git a/webapp/packages/core-blocks/src/FormControls/useInputAutocomplete.ts b/webapp/packages/core-blocks/src/FormControls/useInputAutocomplete.ts index 1bd33d93e1..7ca873b2ee 100644 --- a/webapp/packages/core-blocks/src/FormControls/useInputAutocomplete.ts +++ b/webapp/packages/core-blocks/src/FormControls/useInputAutocomplete.ts @@ -1,17 +1,17 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 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 { action, computed, observable } from 'mobx'; -import { type RefObject, useEffect, useLayoutEffect, useMemo, useRef } from 'react'; +import { type RefObject, useEffect, useLayoutEffect, useMemo } from 'react'; import { debounce } from '@cloudbeaver/core-utils'; import { isNotNullDefined } from '@dbeaver/js-helpers'; -import type { IMenuState } from '../Menu/MenuStateContext.js'; import type { IContextMenuPositionCoords } from '../Menu/useContextMenuPosition.js'; import { useObservableRef } from '../useObservableRef.js'; import { type SearchStrategy, useSearch } from '../useSearch.js'; @@ -23,6 +23,7 @@ interface InputAutocompleteOptions { separator?: string | RegExp; matchStrategy?: SearchStrategy; predicate?: (suggestion: InputAutocompleteProposal, lastWord?: string) => boolean; + onNavigate?: () => void; } export interface InputAutocompleteProposal { @@ -38,7 +39,6 @@ interface State { proposals: InputAutocompleteProposal[]; position: IContextMenuPositionCoords; inputValue: string; - menuRef: RefObject; } const DEFAULT_SEPARATOR = ' '; @@ -48,7 +48,7 @@ const CONTEXT_INPUT_OFFSET_Y = 3; export const useInputAutocomplete = ( inputRef: RefObject, - { sourceHints, separator = DEFAULT_SEPARATOR, matchStrategy = 'contains', predicate }: InputAutocompleteOptions, + { sourceHints, separator = DEFAULT_SEPARATOR, matchStrategy = 'contains', onNavigate, predicate }: InputAutocompleteOptions, ): Readonly => { const search = useSearch({ sourceHints, @@ -56,7 +56,7 @@ export const useInputAutocomplete = ( matchStrategy, predicate, }); - const menuRef = useRef(null); + const state = useObservableRef( () => ({ position: { x: 0, y: 0 } as IContextMenuPositionCoords, @@ -126,7 +126,7 @@ export const useInputAutocomplete = ( position: observable.ref, inputValue: observable.ref, }, - { inputRef, search, menuRef }, + { inputRef, search, onNavigate }, ); const handleInput = useMemo( @@ -145,12 +145,11 @@ export const useInputAutocomplete = ( function handleKeyDown(event: any) { switch (event.key) { case 'Escape': - state.menuRef.current?.hide(); state.resetState(); break; case 'ArrowDown': case 'ArrowUp': - state.menuRef.current?.first(); + state.onNavigate?.(); break; case 'Tab': state.resetState(); diff --git a/webapp/packages/core-blocks/src/IconButton.tsx b/webapp/packages/core-blocks/src/IconButton.tsx index 0028a8ae22..e5c70ccc68 100644 --- a/webapp/packages/core-blocks/src/IconButton.tsx +++ b/webapp/packages/core-blocks/src/IconButton.tsx @@ -1,13 +1,14 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 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 React from 'react'; -import { type ButtonProps, Button as ReakitButton } from 'reakit'; +import { UnstyledButton, type UnstyledButtonProps } from '@dbeaver/ui-kit'; import { Icon } from './Icon.js'; import IconButtonStyles from './IconButton.module.css'; @@ -16,25 +17,22 @@ import { StaticImage } from './StaticImage.js'; import { useS } from './useS.js'; interface Props { - tag?: 'button' | 'a' | 'div'; name: string; img?: boolean; viewBox?: string; } -export type IconButtonProps = Props & ButtonProps; +export type IconButtonProps = Props & UnstyledButtonProps; -export const IconButton: React.FC = observer(function IconButton({ tag, name, img, viewBox, className, ...rest }) { +export const IconButton: React.FC = observer(function IconButton({ name, img, viewBox, className, ...rest }) { const styles = useS(IconButtonStyles); - const Button = tag ?? ReakitButton; - return ( - + ); }); diff --git a/webapp/packages/core-blocks/src/Menu/Menu.module.css b/webapp/packages/core-blocks/src/Menu/Menu.module.css deleted file mode 100644 index 63a2520c16..0000000000 --- a/webapp/packages/core-blocks/src/Menu/Menu.module.css +++ /dev/null @@ -1,34 +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. - */ -.menuButton { - composes: theme-ripple from global; -} -.menuButton { - background: none; - border: none; - color: inherit; - cursor: pointer; - - & .box { - display: flex; - align-items: center; - flex: 1; - height: inherit; - } -} -.menuButtonLink { - position: absolute; - width: 0; - height: 0; - overflow: hidden; - box-sizing: border-box; - padding: 0; - margin: 0; - border: none; - background: transparent; -} diff --git a/webapp/packages/core-blocks/src/Menu/Menu.tsx b/webapp/packages/core-blocks/src/Menu/Menu.tsx deleted file mode 100644 index 3467f5d984..0000000000 --- a/webapp/packages/core-blocks/src/Menu/Menu.tsx +++ /dev/null @@ -1,202 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 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 React, { forwardRef, useLayoutEffect, useRef, useState } from 'react'; -import { MenuButton, type MenuInitialState, useMenuState } from 'reakit'; -import type { ExtractHTMLAttributes } from 'reakit-utils'; - -import { ErrorBoundary } from '../ErrorBoundary.js'; -import { s } from '../s.js'; -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 IMenuState, MenuStateContext } from './MenuStateContext.js'; -import type { IContextMenuPosition } from './useContextMenuPosition.js'; - -interface IMenuProps extends React.ButtonHTMLAttributes { - contextMenuPosition?: IContextMenuPosition; - label: string; - items: React.ReactNode | (() => React.ReactNode); - menuRef?: React.RefObject; - disclosure?: boolean; - placement?: MenuInitialState['placement']; - submenu?: boolean; - modal?: boolean; - visible?: boolean; - rtl?: boolean; - hasBindings?: boolean; - panelAvailable?: boolean; - getHasBindings?: () => boolean; - onVisibleSwitch?: (visible: boolean) => void; -} - -/** - * @deprecated use the @dbeaver/ui-kit package instead - */ -export const Menu = observer( - forwardRef(function Menu( - { - contextMenuPosition, - label, - items, - menuRef, - disclosure, - children, - placement, - visible, - hasBindings, - panelAvailable, - getHasBindings, - onVisibleSwitch, - modal, - submenu, - rtl, - className, - ...props - }, - ref, - ) { - const innerMenuButtonRef = useRef(null); - const combinedRef = useCombinedRef(ref, innerMenuButtonRef); - const [relativePosition, setRelativePosition] = useState<{ x: number; y: number } | null>(null); - const menuButtonLinkRef = useRef(null); - const menuPanelRef = useRef(null); - const propsRef = useObjectRef({ onVisibleSwitch, visible }); - const menu = useMenuState({ - modal, - placement, - visible, - rtl, - unstable_fixed: true, - }); - const styles = useS(style); - - if (menuRef) { - menuRef.current = menu; - } - - let menuVisible = menu.visible; - - if (panelAvailable === false) { - menuVisible = false; - } - - useLayoutEffect(() => { - if (!menuVisible) { - setRelativePosition(null); - } - - propsRef.onVisibleSwitch?.(menuVisible); - }, [menuVisible]); - - useLayoutEffect(() => { - if (!contextMenuPosition?.position) { - return; - } - - if (menuVisible) { - menu.hide(); - return; - } - - if (innerMenuButtonRef.current) { - menu.show(); - - const boxSize = innerMenuButtonRef.current.getBoundingClientRect(); - setRelativePosition({ - x: contextMenuPosition.position.x - boxSize.x, - y: contextMenuPosition.position.y - boxSize.y, - }); - - contextMenuPosition.position = null; - } - }, [contextMenuPosition?.position, menuVisible]); - - useLayoutEffect(() => { - if (relativePosition) { - if (menuButtonLinkRef.current) { - menuButtonLinkRef.current.style.left = `${relativePosition.x}px`; - menuButtonLinkRef.current.style.top = `${relativePosition.y}px`; - } - } - }); - - const MenuButtonLink = MenuButton; - - if (React.isValidElement(children) && disclosure) { - return ( - - - - {(disclosureProps: ExtractHTMLAttributes) => React.cloneElement(children, { ...disclosureProps, ...(children.props as any) })} - - - {items} - - {relativePosition && ( - - )} - - - ); - } - - return ( - - - -
{children}
-
- - {items} - - {relativePosition && ( - - )} -
-
- ); - }), -); diff --git a/webapp/packages/core-blocks/src/Menu/MenuBarSmallItem.module.css b/webapp/packages/core-blocks/src/Menu/MenuBarSmallItem.module.css deleted file mode 100644 index 983b66aec3..0000000000 --- a/webapp/packages/core-blocks/src/Menu/MenuBarSmallItem.module.css +++ /dev/null @@ -1,35 +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. - */ - -.iconButton { - overflow: hidden; - padding: 0; - margin: 0; - flex-shrink: 0; -} - -.iconBox { - composes: theme-form-element-radius theme-text-primary theme-ripple from global; - box-sizing: border-box; - overflow: hidden; - padding: 4px !important; - margin: 2px !important; - flex-shrink: 0; - height: 24px !important; - display: flex; - cursor: pointer; - align-items: center; - gap: 2px; - user-select: none; - outline: none; - - & .iconLabel { - text-transform: uppercase; - font-weight: 500; - } -} diff --git a/webapp/packages/core-blocks/src/Menu/MenuBarSmallItem.tsx b/webapp/packages/core-blocks/src/Menu/MenuBarSmallItem.tsx deleted file mode 100644 index 44234d1fbe..0000000000 --- a/webapp/packages/core-blocks/src/Menu/MenuBarSmallItem.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 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 { ButtonProps } from 'reakit'; - -import { IconButton } from '../IconButton.js'; -import { s } from '../s.js'; -import { useS } from '../useS.js'; -import moduleStyles from './MenuBarSmallItem.module.css'; - -interface Props extends Omit { - icon?: string; - viewBox?: string; - onClick?: React.MouseEventHandler; -} - -export const MenuBarSmallItem: React.FC> = function MenuBarSmallItem({ - icon, - viewBox, - children, - className, - onClick, - ...rest -}) { - const style = useS(moduleStyles); - // TODO: replace IconButton with StaticImage / Icon - // TODO: use button for icon-box (maybe) - return ( -
- {icon && } - {children &&
{children}
} -
- ); -}; diff --git a/webapp/packages/core-blocks/src/Menu/MenuEmptyItem.tsx b/webapp/packages/core-blocks/src/Menu/MenuEmptyItem.tsx deleted file mode 100644 index 3d46af0a2a..0000000000 --- a/webapp/packages/core-blocks/src/Menu/MenuEmptyItem.tsx +++ /dev/null @@ -1,24 +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 { useTranslate } from '../localization/useTranslate.js'; -import { MenuItem } from './MenuItem.js'; -import { MenuItemElement } from './MenuItemElement.js'; - -export const MenuEmptyItem = observer(function MenuEmptyItem() { - const translate = useTranslate(); - - const label = translate('core_ui_empty'); - - return ( - - - - ); -}); diff --git a/webapp/packages/core-blocks/src/Menu/MenuItem.module.css b/webapp/packages/core-blocks/src/Menu/MenuItem.module.css deleted file mode 100644 index d3f2ca38fd..0000000000 --- a/webapp/packages/core-blocks/src/Menu/MenuItem.module.css +++ /dev/null @@ -1,30 +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. - */ -.menuItem { - composes: theme-ripple from global; - composes: menuItem from './MenuItemElement.module.css'; - - display: flex; - align-items: center; - border: none; - padding: 0; - background: none; - text-align: left; - outline: none; - color: inherit; - cursor: pointer; - white-space: nowrap; - - &.hidden { - display: none; - } - - &:global([aria-selected='true']) { - opacity: 0.7; - } -} diff --git a/webapp/packages/core-blocks/src/Menu/MenuItem.tsx b/webapp/packages/core-blocks/src/Menu/MenuItem.tsx deleted file mode 100644 index 7c6f8d6859..0000000000 --- a/webapp/packages/core-blocks/src/Menu/MenuItem.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 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 React, { useContext } from 'react'; -import { MenuItem as ReakitMenuItem, type CompositeItemOptions } from 'reakit'; - -import { s } from '../s.js'; -import { useCombinedHandler } from '../useCombinedHandler.js'; -import { useS } from '../useS.js'; -import style from './MenuItem.module.css'; -import { MenuStateContext } from './MenuStateContext.js'; -import type { ReakitProxyComponent, ReakitProxyComponentOptions } from './ReakitProxyComponent.js'; - -export type MenuItemOptions = CompositeItemOptions & { - selected?: boolean; - close?: boolean; -}; - -/** - * @deprecated use the @dbeaver/ui-kit package instead - */ -export const MenuItem: ReakitProxyComponent<'button', MenuItemOptions> = observer>( - function MenuItem({ children, hidden, selected, close, onClick, className, ...rest }) { - const menu = useContext(MenuStateContext); - const styles = useS(style); - - const handleClick = useCombinedHandler<[React.MouseEvent]>(onClick, function handleClick() { - if (close) { - menu?.hide(); - } - }); - - const MenuItem = ReakitMenuItem; - - return ( - - ); - }, -) as ReakitProxyComponent<'button', MenuItemOptions>; diff --git a/webapp/packages/core-blocks/src/Menu/MenuPanel.module.css b/webapp/packages/core-blocks/src/Menu/MenuPanel.module.css deleted file mode 100644 index 89eceac29e..0000000000 --- a/webapp/packages/core-blocks/src/Menu/MenuPanel.module.css +++ /dev/null @@ -1,34 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -.menu { - outline: none; - z-index: 999; -} -.menuBox { - composes: theme-background-surface theme-text-on-surface theme-group-element-radius theme-typography--body2 from global; - composes: menuBox from './MenuItemElement.module.css'; - - display: flex; - flex-direction: column; - min-width: 140px; - padding: 6px 0; - - & .menuBox { - margin-top: -6px; - } -} - -.menu.modal .menuBox { - overflow: auto; - max-height: 80vh; - overflow-x: hidden; -} - -.menu.submenu { - margin-top: -6px; -} diff --git a/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx b/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx deleted file mode 100644 index 14974d694d..0000000000 --- a/webapp/packages/core-blocks/src/Menu/MenuPanel.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2026 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 { Children, forwardRef } from 'react'; -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'; -import styleMenuItemElement from './MenuItemElement.module.css'; - -export interface IMenuPanelProps { - label: string; - menu: MenuStateReturn; // from reakit useMenuState - panelAvailable?: boolean; - hasBindings?: boolean; - getHasBindings?: () => boolean; - children: React.ReactNode | (() => React.ReactNode); - rtl?: boolean; - submenu?: boolean; - className?: string; -} - -/** - * @deprecated use the @dbeaver/ui-kit package instead - */ -export const MenuPanel = observer( - forwardRef(function MenuPanel({ label, menu, submenu, panelAvailable = true, rtl, getHasBindings, hasBindings, children, className }, ref) { - const translate = useTranslate(); - const styles = useS(style, styleMenuItemElement); - const visible = menu.visible; - - if (!visible) { - return null; - } - - hasBindings = panelAvailable && (hasBindings || getComputed(() => getHasBindings?.())); - - let renderedChildren: React.ReactNode = <>; - - if (panelAvailable) { - renderedChildren = typeof children === 'function' ? children() : children; - } - - return ( - - -
- {Children.count(renderedChildren) === 0 && } - {renderedChildren} -
-
-
- ); - }), -); diff --git a/webapp/packages/core-blocks/src/Menu/MenuStateContext.ts b/webapp/packages/core-blocks/src/Menu/MenuStateContext.ts deleted file mode 100644 index 8b3280b6b3..0000000000 --- a/webapp/packages/core-blocks/src/Menu/MenuStateContext.ts +++ /dev/null @@ -1,13 +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 { createContext } from 'react'; -import type { MenuStateReturn } from 'reakit'; - -export type IMenuState = MenuStateReturn; - -export const MenuStateContext = createContext(null); diff --git a/webapp/packages/core-blocks/src/Menu/ReakitProxyComponent.ts b/webapp/packages/core-blocks/src/Menu/ReakitProxyComponent.ts deleted file mode 100644 index 9e58300060..0000000000 --- a/webapp/packages/core-blocks/src/Menu/ReakitProxyComponent.ts +++ /dev/null @@ -1,22 +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 { As, PropsWithAs } from 'reakit-utils/types'; - -export type ReakitProxyPropsWithAs = PropsWithAs; - -export type ReakitProxyComponentOptions = ReakitProxyPropsWithAs; - -export type ReakitProxyComponent = { - ( - props: ReakitProxyPropsWithAs & { - as: TT; - }, - context?: any, - ): React.ReactElement | null; - (props: ReakitProxyPropsWithAs, context?: any): React.ReactElement | null; -}; diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index a94c34875f..7fcad9c718 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -58,17 +58,8 @@ export * from './ConnectionImageWithMask/ConnectionImageWithMask.js'; export { default as ConnectionImageWithMaskSvgStyles } from './ConnectionImageWithMask/ConnectionImageWithMaskSvg.module.css'; export * from './Menu/AriaKit/MenuAriaKit.js'; -export * from './Menu/Menu.js'; -export { default as MenuStyles } from './Menu/Menu.module.css'; -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 * from './Menu/MenuItemElement.js'; export { default as MenuItemElementStyles } from './Menu/MenuItemElement.module.css'; -export * from './Menu/MenuPanel.js'; -export { default as MenuPanelStyles } from './Menu/MenuPanel.module.css'; -export * from './Menu/MenuStateContext.js'; export * from './Menu/useContextMenuPosition.js'; export * from './ObjectPropertyInfo/ObjectPropertyInfoForm/ObjectPropertyInfoFormLoader.js'; diff --git a/webapp/packages/core-notifications/package.json b/webapp/packages/core-notifications/package.json index 37c9e02212..0f20a7aa88 100644 --- a/webapp/packages/core-notifications/package.json +++ b/webapp/packages/core-notifications/package.json @@ -25,11 +25,11 @@ "@cloudbeaver/core-blocks": "workspace:*", "@cloudbeaver/core-di": "workspace:*", "@cloudbeaver/core-events": "workspace:*", + "@dbeaver/ui-kit": "workspace:^", "mobx": "^6", "mobx-react-lite": "^4", "react": "^19", "react-dom": "^19", - "reakit": "^1", "tslib": "^2" }, "devDependencies": { diff --git a/webapp/packages/core-notifications/src/Notifications.tsx b/webapp/packages/core-notifications/src/Notifications.tsx index 0730b37924..8a36bc2f98 100644 --- a/webapp/packages/core-notifications/src/Notifications.tsx +++ b/webapp/packages/core-notifications/src/Notifications.tsx @@ -5,12 +5,13 @@ * 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 { Portal } from 'reakit'; import { s, useS } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; +import { Portal } from '@dbeaver/ui-kit'; import styles from './Notifications.module.css'; import { NotificationsItem } from './NotificationsItem/NotificationsItem.js'; diff --git a/webapp/packages/core-notifications/tsconfig.json b/webapp/packages/core-notifications/tsconfig.json index 40adf4cbe0..ca5c074904 100644 --- a/webapp/packages/core-notifications/tsconfig.json +++ b/webapp/packages/core-notifications/tsconfig.json @@ -7,6 +7,9 @@ "composite": true }, "references": [ + { + "path": "../../common-react/@dbeaver/ui-kit" + }, { "path": "../core-blocks" }, diff --git a/webapp/packages/core-ui/package.json b/webapp/packages/core-ui/package.json index a43ad85f91..99d005d47f 100644 --- a/webapp/packages/core-ui/package.json +++ b/webapp/packages/core-ui/package.json @@ -42,7 +42,6 @@ "react-dnd": "^16", "react-dnd-html5-backend": "^16", "react-dom": "^19", - "reakit": "^1", "tslib": "^2" }, "devDependencies": { diff --git a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx index cb970a2fa2..36c547c471 100644 --- a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx @@ -14,7 +14,7 @@ import { MenuButton, MenuProvider, Menu, type HovercardStoreState, useMenuStore, import { RenderMenuItems } from './RenderMenuItems.js'; import { type IMenuContext, MenuContext } from './MenuContext.js'; -export interface IContextMenuNewProps extends Omit { +export interface IContextMenuNewProps extends IContextMenuProps { render?: React.ReactElement; placement?: HovercardStoreState['placement']; showOnHover?: boolean; diff --git a/webapp/packages/core-ui/src/ContextMenu/IContextMenuProps.ts b/webapp/packages/core-ui/src/ContextMenu/IContextMenuProps.ts index 6fe7f03387..da94454062 100644 --- a/webapp/packages/core-ui/src/ContextMenu/IContextMenuProps.ts +++ b/webapp/packages/core-ui/src/ContextMenu/IContextMenuProps.ts @@ -1,12 +1,12 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 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 { ButtonHTMLAttributes } from 'react'; -import type { MenuInitialState } from 'reakit'; import type { IContextMenuPosition } from '@cloudbeaver/core-blocks'; import type { IMenuData } from '@cloudbeaver/core-view'; @@ -21,7 +21,6 @@ export type ContextMenuRenderingChildren = (props: IContextMenuBaseProps) => Rea export interface IContextMenuProps extends Omit, 'children'> { contextMenuPosition?: IContextMenuPosition; menu: IMenuData; - placement?: MenuInitialState['placement']; visible?: boolean; rtl?: boolean; children?: React.ReactNode; diff --git a/webapp/packages/core-utils/package.json b/webapp/packages/core-utils/package.json index f62ccba5bd..c9972bacf6 100644 --- a/webapp/packages/core-utils/package.json +++ b/webapp/packages/core-utils/package.json @@ -23,6 +23,7 @@ "validate-dependencies": "core-cli-validate-dependencies" }, "dependencies": { + "@dbeaver/js-helpers": "workspace:*", "@timohausmann/quadtree-ts": "^2", "fast-deep-equal": "^3", "md5": "^2", diff --git a/webapp/packages/core-utils/src/formatNumber.ts b/webapp/packages/core-utils/src/formatNumber.ts index 1a3d8f9e12..30d442a075 100644 --- a/webapp/packages/core-utils/src/formatNumber.ts +++ b/webapp/packages/core-utils/src/formatNumber.ts @@ -1,24 +1,9 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 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 function formatNumber(n: number, d: number) { - if (n < 1000) { - return n.toLocaleString(undefined, { maximumFractionDigits: 2 }); - } - - const numStr = n.toString(); - const exponent = numStr.length - (numStr.length % 3); - - const power = Math.pow(10, d); - const rounded = Math.round((n * power) / Math.pow(10, exponent)) / power; - - const units = ' kMBTPE'; - const unit = units[exponent / 3]!; - - return rounded + unit; -} +export { formatNumber } from '@dbeaver/js-helpers'; diff --git a/webapp/packages/core-utils/tsconfig.json b/webapp/packages/core-utils/tsconfig.json index 964a4a5f8b..30236ffc9b 100644 --- a/webapp/packages/core-utils/tsconfig.json +++ b/webapp/packages/core-utils/tsconfig.json @@ -13,6 +13,9 @@ { "path": "../../common-typescript/@dbeaver/cli" }, + { + "path": "../../common-typescript/@dbeaver/js-helpers" + }, { "path": "../core-cli" }, diff --git a/webapp/packages/core-view/public/icons/zoom-in.svg b/webapp/packages/core-view/public/icons/zoom-in.svg deleted file mode 100644 index 7ca279793b..0000000000 --- a/webapp/packages/core-view/public/icons/zoom-in.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/webapp/packages/core-view/public/icons/zoom-out.svg b/webapp/packages/core-view/public/icons/zoom-out.svg deleted file mode 100644 index efa287627c..0000000000 --- a/webapp/packages/core-view/public/icons/zoom-out.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/webapp/packages/core-view/public/icons/zoom.svg b/webapp/packages/core-view/public/icons/zoom.svg new file mode 100644 index 0000000000..8f0f823ec6 --- /dev/null +++ b/webapp/packages/core-view/public/icons/zoom.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/webapp/packages/core-view/public/icons/zoom_in.svg b/webapp/packages/core-view/public/icons/zoom_in.svg new file mode 100644 index 0000000000..0a07b3fa4b --- /dev/null +++ b/webapp/packages/core-view/public/icons/zoom_in.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/webapp/packages/core-view/public/icons/zoom_in_m.svg b/webapp/packages/core-view/public/icons/zoom_in_m.svg new file mode 100644 index 0000000000..5b863afa24 --- /dev/null +++ b/webapp/packages/core-view/public/icons/zoom_in_m.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/webapp/packages/core-view/public/icons/zoom_in_sm.svg b/webapp/packages/core-view/public/icons/zoom_in_sm.svg new file mode 100644 index 0000000000..6ffe2e4d23 --- /dev/null +++ b/webapp/packages/core-view/public/icons/zoom_in_sm.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/webapp/packages/core-view/public/icons/zoom_m.svg b/webapp/packages/core-view/public/icons/zoom_m.svg new file mode 100644 index 0000000000..5f66f43286 --- /dev/null +++ b/webapp/packages/core-view/public/icons/zoom_m.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/webapp/packages/core-view/public/icons/zoom_out.svg b/webapp/packages/core-view/public/icons/zoom_out.svg new file mode 100644 index 0000000000..01af24e5f0 --- /dev/null +++ b/webapp/packages/core-view/public/icons/zoom_out.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/webapp/packages/core-view/public/icons/zoom_out_m.svg b/webapp/packages/core-view/public/icons/zoom_out_m.svg new file mode 100644 index 0000000000..72c80d928b --- /dev/null +++ b/webapp/packages/core-view/public/icons/zoom_out_m.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/webapp/packages/core-view/public/icons/zoom_out_sm.svg b/webapp/packages/core-view/public/icons/zoom_out_sm.svg new file mode 100644 index 0000000000..fe110d6b54 --- /dev/null +++ b/webapp/packages/core-view/public/icons/zoom_out_sm.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/webapp/packages/core-view/public/icons/zoom_sm.svg b/webapp/packages/core-view/public/icons/zoom_sm.svg new file mode 100644 index 0000000000..7228cdb884 --- /dev/null +++ b/webapp/packages/core-view/public/icons/zoom_sm.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/webapp/packages/core-view/src/Action/Actions/ACTION_ZOOM_IN.ts b/webapp/packages/core-view/src/Action/Actions/ACTION_ZOOM_IN.ts index 536eac3898..fe01f537f4 100644 --- a/webapp/packages/core-view/src/Action/Actions/ACTION_ZOOM_IN.ts +++ b/webapp/packages/core-view/src/Action/Actions/ACTION_ZOOM_IN.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -10,5 +10,5 @@ import { createAction } from '../createAction.js'; export const ACTION_ZOOM_IN = createAction('zoom-in', { label: 'core_view_zoom_in', tooltip: 'core_view_zoom_in', - icon: '/icons/zoom-in.svg', + icon: '/icons/zoom_in.svg', }); diff --git a/webapp/packages/core-view/src/Action/Actions/ACTION_ZOOM_OUT.ts b/webapp/packages/core-view/src/Action/Actions/ACTION_ZOOM_OUT.ts index 5f63cb7822..8ed984b25e 100644 --- a/webapp/packages/core-view/src/Action/Actions/ACTION_ZOOM_OUT.ts +++ b/webapp/packages/core-view/src/Action/Actions/ACTION_ZOOM_OUT.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -10,5 +10,5 @@ import { createAction } from '../createAction.js'; export const ACTION_ZOOM_OUT = createAction('zoom-out', { label: 'core_view_zoom_out', tooltip: 'core_view_zoom_out', - icon: '/icons/zoom-out.svg', + icon: '/icons/zoom_out.svg', }); diff --git a/webapp/packages/plugin-authentication-administration/package.json b/webapp/packages/plugin-authentication-administration/package.json index 115266a3e5..0a848cc86b 100644 --- a/webapp/packages/plugin-authentication-administration/package.json +++ b/webapp/packages/plugin-authentication-administration/package.json @@ -48,7 +48,6 @@ "mobx-react-lite": "^4", "react": "^19", "react-dom": "^19", - "reakit": "^1", "tslib": "^2" }, "devDependencies": { diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/SSH/SSHKeyUploader.tsx b/webapp/packages/plugin-connections/src/ConnectionForm/SSH/SSHKeyUploader.tsx index f5669f319e..c84af42db1 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/SSH/SSHKeyUploader.tsx +++ b/webapp/packages/plugin-connections/src/ConnectionForm/SSH/SSHKeyUploader.tsx @@ -52,7 +52,7 @@ export const SSHKeyUploader = observer(function SSHKeyUploader({ state, s - diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/package.json b/webapp/packages/plugin-data-viewer-result-set-grouping/package.json index 4b153065e4..5ebc71e008 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/package.json +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/package.json @@ -35,6 +35,7 @@ "@cloudbeaver/core-view": "workspace:*", "@cloudbeaver/plugin-data-viewer": "workspace:*", "@cloudbeaver/plugin-sql-editor": "workspace:*", + "@dbeaver/ui-kit": "workspace:*", "mobx": "^6", "mobx-react-lite": "^4", "react": "^19", diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/GroupingTableItem.tsx b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/GroupingTableItem.tsx index b01f1b26d0..32ead62f19 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/GroupingTableItem.tsx +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/src/DVGroupingColumnEditorDialog/GroupingTableItem.tsx @@ -1,13 +1,15 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 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 { IconButton, TableColumnValue, TableItem } from '@cloudbeaver/core-blocks'; +import { Icon, TableColumnValue, TableItem, useTranslate } from '@cloudbeaver/core-blocks'; +import { IconButton } from '@dbeaver/ui-kit'; interface Props { id: string; @@ -17,13 +19,17 @@ interface Props { } export const GroupingTableItem = observer(function GroupingTableItem({ id, name, className, onDelete }) { + const translate = useTranslate(); + return ( {name} - onDelete(id)} /> + onDelete(id)}> + + ); diff --git a/webapp/packages/plugin-data-viewer-result-set-grouping/tsconfig.json b/webapp/packages/plugin-data-viewer-result-set-grouping/tsconfig.json index 35c15896ac..e189710694 100644 --- a/webapp/packages/plugin-data-viewer-result-set-grouping/tsconfig.json +++ b/webapp/packages/plugin-data-viewer-result-set-grouping/tsconfig.json @@ -7,6 +7,9 @@ "composite": true }, "references": [ + { + "path": "../../common-react/@dbeaver/ui-kit" + }, { "path": "../core-blocks" }, diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx index 8af46221c3..da628777ee 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx @@ -1,10 +1,11 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 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 { useRef } from 'react'; @@ -17,6 +18,7 @@ import { useTranslate, } from '@cloudbeaver/core-blocks'; import { InlineEditor } from '@cloudbeaver/core-ui'; +import { CompositeProvider, Popover, useCompositeStore } from '@dbeaver/ui-kit'; import type { ITableHeaderPlaceholderProps } from './TableHeaderService.js'; import styles from './TableWhereFilter.module.css'; @@ -30,10 +32,15 @@ export const TableWhereFilter: PlaceholderComponent(null); + + const store = useCompositeStore(); const autocompleteState = useInputAutocomplete(inputRef, { separator: AUTOCOMPLETE_WORD_SEPARATOR, sourceHints: data.hintProposals ?? [], matchStrategy: 'fuzzy', + onNavigate: () => { + store.move(store.first()); + }, }); if (!state.supported) { @@ -55,26 +62,30 @@ export const TableWhereFilter: PlaceholderComponent - - + + 0}> + + + + + + ); }); diff --git a/webapp/packages/plugin-help/src/Shortcuts/SHORTCUTS_DATA.ts b/webapp/packages/plugin-help/src/Shortcuts/SHORTCUTS_DATA.ts index 043ec7b998..20fb2c74c7 100644 --- a/webapp/packages/plugin-help/src/Shortcuts/SHORTCUTS_DATA.ts +++ b/webapp/packages/plugin-help/src/Shortcuts/SHORTCUTS_DATA.ts @@ -33,8 +33,8 @@ const KEY_BINDING_SQL_EDITOR_COMMENT = createKeyBinding({ keys: ['mod+/'], }); -const KEY_BINDING_SQL_EDITOR_FIND = createKeyBinding({ - id: 'sql-editor-find', +const KEY_BINDING_FIND = createKeyBinding({ + id: 'find', keys: ['mod+f'], }); @@ -57,6 +57,10 @@ export const DATA_VIEWER_SHORTCUTS: IShortcut[] = [ label: 'data_viewer_shortcut_delete_row', code: transformKeys(KEY_BINDING_DELETE_ROW), }, + { + label: 'plugin_search_panel_find', + code: transformKeys(KEY_BINDING_FIND), + }, { label: 'ui_processing_save', code: transformKeys(KEY_BINDING_SAVE), @@ -109,8 +113,8 @@ export const SQL_EDITOR_SHORTCUTS: IShortcut[] = [ code: transformKeys(KEY_BINDING_REDO), }, { - label: 'sql_editor_shortcut_find', - code: transformKeys(KEY_BINDING_SQL_EDITOR_FIND), + label: 'plugin_search_panel_find', + code: transformKeys(KEY_BINDING_FIND), }, { label: 'sql_editor_shortcut_comment_uncomment_selection', diff --git a/webapp/packages/plugin-help/src/locales/en.ts b/webapp/packages/plugin-help/src/locales/en.ts index c6aa2b5af2..bbfd865412 100644 --- a/webapp/packages/plugin-help/src/locales/en.ts +++ b/webapp/packages/plugin-help/src/locales/en.ts @@ -18,12 +18,13 @@ export default [ ['sql_editor_shortcut_show_execution_plan', 'Show Execution plan'], ['sql_editor_shortcut_format', 'Format script'], ['sql_editor_shortcut_open_editor_in_new_tab', 'Open SQL Editor in the separate browser Tab'], - ['sql_editor_shortcut_find', 'Find'], ['sql_editor_shortcut_comment_uncomment_selection', 'Comment/Uncomment selection'], ['sql_editor_shortcut_start_completion', 'Show autocomplete suggestions'], ['sql_editor_shortcut_accept_completion', 'Accept autocompletion'], ['sql_editor_shortcut_escape', 'Escape editor'], + ['plugin_search_panel_find', 'Find'], + ['navigation_tree_shortcut_enable_filter', 'Enable filtering'], ['plugin_help_multi_tab_support_title', 'Multi-tab is not supported'], @@ -34,5 +35,5 @@ export default [ Your local application settings will be lost after the browser tab is closed. You can load tabs and settings for this tab`, ], - ['plugin_help_multi_tab_support_load_settings', 'Load tabs ans settings'], + ['plugin_help_multi_tab_support_load_settings', 'Load tabs and settings'], ]; diff --git a/webapp/packages/plugin-help/src/locales/fr.ts b/webapp/packages/plugin-help/src/locales/fr.ts index ef26c8ae1e..23147abf82 100644 --- a/webapp/packages/plugin-help/src/locales/fr.ts +++ b/webapp/packages/plugin-help/src/locales/fr.ts @@ -17,16 +17,16 @@ export default [ ['sql_editor_shortcut_show_execution_plan', "Afficher le plan d'exécution"], ['sql_editor_shortcut_format', 'Formater le script'], ['sql_editor_shortcut_open_editor_in_new_tab', "Ouvrir l'éditeur SQL dans un nouvel onglet"], - ['sql_editor_shortcut_find', 'Rechercher'], ['sql_editor_shortcut_comment_uncomment_selection', 'Commenter/Décommenter la sélection'], ['sql_editor_shortcut_start_completion', 'Afficher les suggestions de saisie semi-automatique'], ['sql_editor_shortcut_accept_completion', 'Accepter la saisie semi-automatique'], ['sql_editor_shortcut_escape', "Échapper l'éditeur"], + ['plugin_search_panel_find', 'Rechercher'], ['navigation_tree_shortcut_enable_filter', 'Activer le filtrage'], ['plugin_help_multi_tab_support_title', "Le multi-onglet n'est pas supporté"], [ 'plugin_help_multi_tab_support_description', "Les données ne sont pas connectées à d'autres onglets du navigateur. Tout onglet d'application ouvert ne sera pas sauvegardé et sera probablement perdu après la fermeture de l'onglet. Vos paramètres et données locaux seront perdus après la fermeture de l'onglet. Vous pouvez charger les onglets et les paramètres pour cet onglet.", ], - ['plugin_help_multi_tab_support_load_settings', 'Charger les onglets comme paramètres'], + ['plugin_help_multi_tab_support_load_settings', 'Charger les onglets et les paramètres'], ]; diff --git a/webapp/packages/plugin-help/src/locales/it.ts b/webapp/packages/plugin-help/src/locales/it.ts index bb84316ebd..35daa88e60 100644 --- a/webapp/packages/plugin-help/src/locales/it.ts +++ b/webapp/packages/plugin-help/src/locales/it.ts @@ -18,11 +18,11 @@ export default [ ['sql_editor_shortcut_show_execution_plan', 'Show Execution plan'], ['sql_editor_shortcut_format', 'Format script'], ['sql_editor_shortcut_open_editor_in_new_tab', 'Open SQL Editor in the separate browser Tab'], - ['sql_editor_shortcut_find', 'Find'], ['sql_editor_shortcut_comment_uncomment_selection', 'Comment/Uncomment selection'], ['sql_editor_shortcut_start_completion', 'Show autocomplete suggestions'], ['sql_editor_shortcut_accept_completion', 'Accept autocompletion'], ['sql_editor_shortcut_escape', 'Escape editor'], + ['plugin_search_panel_find', 'Find'], ['navigation_tree_shortcut_enable_filter', 'Enable filtering'], ]; diff --git a/webapp/packages/plugin-help/src/locales/ru.ts b/webapp/packages/plugin-help/src/locales/ru.ts index f11ecee81f..93f64b7281 100644 --- a/webapp/packages/plugin-help/src/locales/ru.ts +++ b/webapp/packages/plugin-help/src/locales/ru.ts @@ -18,11 +18,11 @@ export default [ ['sql_editor_shortcut_show_execution_plan', 'Показать план выполнения'], ['sql_editor_shortcut_format', 'Форматировать скрипт'], ['sql_editor_shortcut_open_editor_in_new_tab', 'Открыть SQL редактор в новой бразуерной вкладке'], - ['sql_editor_shortcut_find', 'Найти'], ['sql_editor_shortcut_comment_uncomment_selection', 'Закомментировать/раскомментировать выделение'], ['sql_editor_shortcut_start_completion', 'Показать подсказки автодополнения'], ['sql_editor_shortcut_accept_completion', 'Принять автодополнение'], ['sql_editor_shortcut_escape', 'Выйти из редактора'], + ['plugin_search_panel_find', 'Найти'], ['navigation_tree_shortcut_enable_filter', 'Включить фильтрацию'], ]; diff --git a/webapp/packages/plugin-help/src/locales/vi.ts b/webapp/packages/plugin-help/src/locales/vi.ts index 2e049fb8f6..ba36545b9e 100644 --- a/webapp/packages/plugin-help/src/locales/vi.ts +++ b/webapp/packages/plugin-help/src/locales/vi.ts @@ -18,11 +18,11 @@ export default [ ['sql_editor_shortcut_show_execution_plan', 'Hiển thị kế hoạch thực thi'], ['sql_editor_shortcut_format', 'Định dạng kịch bản'], ['sql_editor_shortcut_open_editor_in_new_tab', 'Mở Trình chỉnh sửa SQL trong tab riêng biệt'], - ['sql_editor_shortcut_find', 'Tìm kiếm'], ['sql_editor_shortcut_comment_uncomment_selection', 'Bình luận/Bỏ bình luận lựa chọn'], ['sql_editor_shortcut_start_completion', 'Hiển thị gợi ý tự động hoàn thành'], ['sql_editor_shortcut_accept_completion', 'Chấp nhận tự động hoàn thành'], ['sql_editor_shortcut_escape', 'Thoát khỏi trình chỉnh sửa'], + ['plugin_search_panel_find', 'Tìm kiếm'], ['navigation_tree_shortcut_enable_filter', 'Bật lọc'], diff --git a/webapp/packages/plugin-help/src/locales/zh.ts b/webapp/packages/plugin-help/src/locales/zh.ts index f8489b3088..642c60bba2 100644 --- a/webapp/packages/plugin-help/src/locales/zh.ts +++ b/webapp/packages/plugin-help/src/locales/zh.ts @@ -18,11 +18,11 @@ export default [ ['sql_editor_shortcut_show_execution_plan', '显示执行计划'], ['sql_editor_shortcut_format', '格式化脚本'], ['sql_editor_shortcut_open_editor_in_new_tab', '在单独的浏览器标签下打开SQL编辑器'], - ['sql_editor_shortcut_find', '查找'], ['sql_editor_shortcut_comment_uncomment_selection', '注释/取消注释所选内容'], ['sql_editor_shortcut_start_completion', '显示自动完成建议'], ['sql_editor_shortcut_accept_completion', '接受自动完成'], ['sql_editor_shortcut_escape', '退出编辑器'], + ['plugin_search_panel_find', '查找'], ['navigation_tree_shortcut_enable_filter', '启用过滤'], ]; diff --git a/webapp/packages/plugin-log-viewer/package.json b/webapp/packages/plugin-log-viewer/package.json index 69daa1426f..121a73688b 100644 --- a/webapp/packages/plugin-log-viewer/package.json +++ b/webapp/packages/plugin-log-viewer/package.json @@ -34,6 +34,7 @@ "@cloudbeaver/core-utils": "workspace:*", "@cloudbeaver/core-view": "workspace:*", "@cloudbeaver/plugin-tools-panel": "workspace:*", + "@dbeaver/ui-kit": "workspace:^", "mobx": "^6", "mobx-react-lite": "^4", "react": "^19", diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerInfoPanel.module.css b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerInfoPanel.module.css index 697218a078..eda3d55b93 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerInfoPanel.module.css +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerInfoPanel.module.css @@ -26,10 +26,6 @@ padding: 4px; } -.button { - flex: unset; -} - .type { composes: theme-typography--body2 from global; margin: 0; diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerInfoPanel.tsx b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerInfoPanel.tsx index 80adde202b..bc7bb0a2ab 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerInfoPanel.tsx +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerInfoPanel.tsx @@ -8,7 +8,8 @@ import { observer } from 'mobx-react-lite'; import { useCallback } from 'react'; -import { Container, MenuBarSmallItem, s, Textarea, useClipboard, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { Container, s, Textarea, useClipboard, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { Button } from '@dbeaver/ui-kit'; import type { ILogEntry } from './ILogEntry.js'; import classes from './LogViewerInfoPanel.module.css'; @@ -34,12 +35,25 @@ export const LogViewerInfoPanel = observer(function LogViewerInfoPanel({ - + + +
diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.module.css b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.module.css index 3d000a41c9..f18e965c6f 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.module.css +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.module.css @@ -42,11 +42,4 @@ .timestamp { width: 116px; min-width: 116px; -} - -.clearButton { - position: absolute !important; - top: 4px; - right: 16px; - z-index: 100; -} +} \ No newline at end of file diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.tsx b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.tsx index 587b7be20d..4fb754cdcb 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.tsx +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.tsx @@ -7,7 +7,8 @@ */ import { observer } from 'mobx-react-lite'; -import { Container, MenuBarSmallItem, s, Table, TableBody, TableColumnHeader, TableHeader, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { Container, Icon, s, Table, TableBody, TableColumnHeader, TableHeader, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { Button, ButtonIcon } from '@dbeaver/ui-kit'; import type { ILogEntry } from './ILogEntry.js'; import { LogEntry } from './LogEntry.js'; @@ -26,15 +27,18 @@ export const LogViewerTable = observer(function LogViewerTable({ items, s return ( - + + + {translate('ui_clear')} - + diff --git a/webapp/packages/plugin-log-viewer/tsconfig.json b/webapp/packages/plugin-log-viewer/tsconfig.json index 35f4c3fb84..f6812f382d 100644 --- a/webapp/packages/plugin-log-viewer/tsconfig.json +++ b/webapp/packages/plugin-log-viewer/tsconfig.json @@ -10,6 +10,9 @@ { "path": "../../common-react/@dbeaver/react-tests" }, + { + "path": "../../common-react/@dbeaver/ui-kit" + }, { "path": "../../common-typescript/@dbeaver/cli" }, diff --git a/webapp/packages/plugin-object-viewer/package.json b/webapp/packages/plugin-object-viewer/package.json index 14b8772d6c..30cddb3ba2 100644 --- a/webapp/packages/plugin-object-viewer/package.json +++ b/webapp/packages/plugin-object-viewer/package.json @@ -46,7 +46,6 @@ "mobx-react-lite": "^4", "react": "^19", "react-dom": "^19", - "reakit": "^1", "tslib": "^2" }, "devDependencies": { diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/FolderContext.ts b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/FolderContext.ts deleted file mode 100644 index cb4f3f272a..0000000000 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/FolderContext.ts +++ /dev/null @@ -1,11 +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 { createContext } from 'react'; -import type { TabStateReturn } from 'reakit'; - -export const FolderContext = createContext(undefined); diff --git a/webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/SQLCodeEditorPanel.tsx b/webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/SQLCodeEditorPanel.tsx index 0e70e575ab..7d64688899 100644 --- a/webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/SQLCodeEditorPanel.tsx +++ b/webapp/packages/plugin-sql-editor-new/src/SQLEditor/SQLCodeEditorPanel/SQLCodeEditorPanel.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react-lite'; import { useState } from 'react'; -import { MenuBarSmallItem, useExecutor, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { Button, useExecutor, useS, useTranslate } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { DATA_CONTEXT_NAV_NODE, getNodesFromContext, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; @@ -129,14 +129,24 @@ export const SQLCodeEditorPanel: TabContainerPanelComponent {data.isIncomingChanges && ( <> - + - + )} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeBlock.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeBlock.tsx index abd6111b21..395355f184 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeBlock.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeBlock.tsx @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ export const ExecutionPlanTreeBlock = observer(function ExecutionPlanTree
) : ( - {translate('sql_execution_plan_placeholder')} + {translate('plugin_sql_execution_plan_placeholder')} )} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeView.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeView.tsx new file mode 100644 index 0000000000..f2ae91aa78 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ExecutionPlanTreeView.tsx @@ -0,0 +1,20 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { ISqlExecutionPlanViewProps } from './ISqlExecutionPlanViewProps.js'; +import { ExecutionPlanTreeBlock } from './ExecutionPlanTreeBlock.js'; + +export const ExecutionPlanTreeView = observer(function ExecutionPlanTreeView({ + nodes, + query, + onNodeSelect, +}) { + return ; +}); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ISqlExecutionPlanViewProps.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ISqlExecutionPlanViewProps.ts new file mode 100644 index 0000000000..4d295ea10d --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/ISqlExecutionPlanViewProps.ts @@ -0,0 +1,16 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { SqlExecutionPlanNode } from '@cloudbeaver/core-sdk'; + +export interface ISqlExecutionPlanViewProps { + nodes: SqlExecutionPlanNode[]; + query: string; + selectedNode: string | null; + onNodeSelect: (nodeId: string | null) => void; +} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanel.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanel.tsx index 9e64f308c6..8abb0f1cd6 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanel.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/PropertiesPanel/PropertiesPanel.tsx @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -50,11 +50,11 @@ export const PropertiesPanel = observer(function PropertiesPanel({ select ).get(); if (!general.length && !details.length) { - return {translate('sql_execution_plan_properties_panel_placeholder')}; + return {translate('plugin_sql_execution_plan_properties_panel_placeholder')}; } - const nameColumnTitle = translate('sql_execution_plan_properties_panel_name'); - const valueColumnTitle = translate('sql_execution_plan_properties_panel_value'); + const nameColumnTitle = translate('plugin_sql_execution_plan_properties_panel_name'); + const valueColumnTitle = translate('plugin_sql_execution_plan_properties_panel_value'); return ( @@ -63,8 +63,12 @@ export const PropertiesPanel = observer(function PropertiesPanel({ select {valueColumnTitle} - {!!general.length && } - {!!details.length && } + {!!general.length && ( + + )} + {!!details.length && ( + + )}
); diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.module.css b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.module.css index c03d414621..7b3d848fba 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.module.css +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.module.css @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -8,6 +8,45 @@ .pane { &:first-child { + display: flex; + flex-direction: column; overflow: hidden; + + /* Map diagram component variables to CloudBeaver theme */ + + /* Viewport */ + --dbv-plan-viewport-bg: var(--theme-surface); + + /* Node */ + --dbv-plan-node-bg: var(--theme-sub-secondary); + --dbv-plan-node-border-color: var(--theme-input-border); + --dbv-plan-node-selected-border: var(--theme-primary); + --dbv-plan-node-selected-glow: color-mix(in srgb, var(--theme-primary) 30%, transparent); + --dbv-plan-node-heavy-color: var(--theme-negative); + + /* Node caption */ + --dbv-plan-node-caption-bg: var(--theme-background); + --dbv-plan-node-caption-color: var(--theme-on-surface); + + /* Node name */ + --dbv-plan-node-name-color: var(--theme-on-surface); + + /* Node metrics */ + --dbv-plan-node-metric-color: var(--theme-text-secondary-on-light); + --dbv-plan-node-metric-value-color: var(--theme-on-surface); + + /* Node cost bar */ + --dbv-plan-node-cost-bar-bg: var(--theme-input-border); + --dbv-plan-node-cost-bar-color: var(--theme-status); + + /* Edges */ + --dbv-plan-edge-color: var(--theme-text-hint-on-light); + + /* Toolbar */ + --dbv-plan-toolbar-border: var(--theme-input-border); + --dbv-plan-toolbar-bg: var(--theme-surface); + --dbv-plan-toolbar-color: var(--theme-on-surface); + --dbv-plan-toolbar-hover-bg: var(--theme-sub-secondary); + --dbv-plan-toolbar-active-bg: var(--theme-secondary); } } diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.tsx index 64d23e5c43..b177772a0d 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanPanel.tsx @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -10,12 +10,13 @@ import { useState } from 'react'; import { Loader, Pane, ResizerControls, s, Split, useS, useSplitUserState } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; +import { TabList, TabPanelList, TabsState } from '@cloudbeaver/core-ui'; import type { IExecutionPlanTab } from '../../ISqlEditorTabState.js'; -import { ExecutionPlanTreeBlock } from './ExecutionPlanTreeBlock.js'; import { PropertiesPanel } from './PropertiesPanel/PropertiesPanel.js'; -import style from './SqlExecutionPlanPanel.module.css'; import { SqlExecutionPlanService } from './SqlExecutionPlanService.js'; +import { SqlExecutionPlanViewService } from './SqlExecutionPlanViewService.js'; +import style from './SqlExecutionPlanPanel.module.css'; interface Props { executionPlanTab: IExecutionPlanTab; @@ -24,6 +25,7 @@ interface Props { export const SqlExecutionPlanPanel = observer(function SqlExecutionPlanPanel({ executionPlanTab }) { const styles = useS(style); const sqlExecutionPlanService = useService(SqlExecutionPlanService); + const sqlExecutionPlanViewService = useService(SqlExecutionPlanViewService); const data = sqlExecutionPlanService.data.get(executionPlanTab.tabId); const [selectedNode, setSelectedNode] = useState(null); const splitState = useSplitUserState('execution-plan'); @@ -35,7 +37,17 @@ export const SqlExecutionPlanPanel = observer(function SqlExecutionPlanPa return ( - + + + + diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewBootstrap.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewBootstrap.ts new file mode 100644 index 0000000000..2e4165bd9d --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewBootstrap.ts @@ -0,0 +1,30 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { importLazyComponent } from '@cloudbeaver/core-blocks'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; + +import { SqlExecutionPlanViewService } from './SqlExecutionPlanViewService.js'; + +const ExecutionPlanTreeView = importLazyComponent(() => import('./ExecutionPlanTreeView.js').then(m => m.ExecutionPlanTreeView)); + +@injectable(() => [SqlExecutionPlanViewService]) +export class SqlExecutionPlanViewBootstrap extends Bootstrap { + constructor(private readonly sqlExecutionPlanViewService: SqlExecutionPlanViewService) { + super(); + } + + override register(): void { + this.sqlExecutionPlanViewService.tabs.add({ + key: 'table', + name: 'plugin_sql_execution_plan_view_table', + order: 0, + panel: () => ExecutionPlanTreeView, + }); + } +} diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewService.ts new file mode 100644 index 0000000000..7ec9d84f81 --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewService.ts @@ -0,0 +1,21 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 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 { TabsContainer } from '@cloudbeaver/core-ui'; + +import type { ISqlExecutionPlanViewProps } from './ISqlExecutionPlanViewProps.js'; + +@injectable() +export class SqlExecutionPlanViewService { + readonly tabs: TabsContainer; + + constructor() { + this.tabs = new TabsContainer('Execution Plan Views'); + } +} diff --git a/webapp/packages/plugin-sql-editor/src/index.ts b/webapp/packages/plugin-sql-editor/src/index.ts index f6a6a39475..fc2ea4dd5e 100644 --- a/webapp/packages/plugin-sql-editor/src/index.ts +++ b/webapp/packages/plugin-sql-editor/src/index.ts @@ -54,3 +54,6 @@ export * from './SqlEditorService.js'; export * from './SqlEditorSettingsService.js'; export * from './SqlEditorView.js'; export * from './SqlResultTabs/SqlQueryService.js'; +export * from './SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewService.js'; +export type { ISqlExecutionPlanViewProps } from './SqlResultTabs/ExecutionPlan/ISqlExecutionPlanViewProps.js'; + diff --git a/webapp/packages/plugin-sql-editor/src/locales/en.ts b/webapp/packages/plugin-sql-editor/src/locales/en.ts index 311762bf22..faed9e7eeb 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/en.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/en.ts @@ -27,12 +27,13 @@ export default [ ['sql_editor_sql_statistics_unavailable', 'Statistics is not available'], ['sql_editor_sql_execution_executed_queries', 'Executed queries:'], ['sql_editor_sql_execution_executing', 'Executing...'], - ['sql_execution_plan_placeholder', 'Execution plan is empty'], - ['sql_execution_plan_properties_panel_placeholder', 'There are no items to show'], - ['sql_execution_plan_properties_panel_name', 'Name'], - ['sql_execution_plan_properties_panel_value', 'Value'], - ['sql_execution_plan_properties_panel_general', 'General'], - ['sql_execution_plan_properties_panel_details', 'Details'], + ['plugin_sql_execution_plan_placeholder', 'Execution plan is empty'], + ['plugin_sql_execution_plan_properties_panel_placeholder', 'There are no items to show'], + ['plugin_sql_execution_plan_properties_panel_name', 'Name'], + ['plugin_sql_execution_plan_properties_panel_value', 'Value'], + ['plugin_sql_execution_plan_properties_panel_general', 'General'], + ['plugin_sql_execution_plan_properties_panel_details', 'Details'], + ['plugin_sql_execution_plan_view_table', 'Table'], ['sql_editor_upload_script_tooltip', 'Upload sql script'], ['sql_editor_download_script_tooltip', 'Download sql script'], [ diff --git a/webapp/packages/plugin-sql-editor/src/locales/fr.ts b/webapp/packages/plugin-sql-editor/src/locales/fr.ts index 9f566bf708..2d633dc962 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/fr.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/fr.ts @@ -31,12 +31,13 @@ export default [ ['sql_editor_lock_button', "Verrouiller l'éditeur"], ['sql_editor_sql_statistics_unavailable', 'Les statistiques SQL ne sont pas disponibles'], ['sql_editor_queries_tab_title', 'Requêtes exécutées'], - ['sql_execution_plan_placeholder', "Le plan d'exécution est vide"], - ['sql_execution_plan_properties_panel_placeholder', 'Aucun élément à afficher'], - ['sql_execution_plan_properties_panel_name', 'Nom'], - ['sql_execution_plan_properties_panel_value', 'Valeur'], - ['sql_execution_plan_properties_panel_general', 'Général'], - ['sql_execution_plan_properties_panel_details', 'Détails'], + ['plugin_sql_execution_plan_placeholder', "Le plan d'exécution est vide"], + ['plugin_sql_execution_plan_properties_panel_placeholder', 'Aucun élément à afficher'], + ['plugin_sql_execution_plan_properties_panel_name', 'Nom'], + ['plugin_sql_execution_plan_properties_panel_value', 'Valeur'], + ['plugin_sql_execution_plan_properties_panel_general', 'Général'], + ['plugin_sql_execution_plan_properties_panel_details', 'Détails'], + ['plugin_sql_execution_plan_view_table', 'Tableau'], ['sql_editor_upload_script_tooltip', 'Télécharger le script SQL'], ['sql_editor_upload_script', 'Télécharger le script'], ['sql_editor_upload_script_unsaved_changes_dialog_message', "Certains fichiers n'ont pas été enregistrés. Voulez-vous d'abord les enregistrer ?"], diff --git a/webapp/packages/plugin-sql-editor/src/locales/it.ts b/webapp/packages/plugin-sql-editor/src/locales/it.ts index cf2c12f840..0833b07197 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/it.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/it.ts @@ -27,12 +27,13 @@ export default [ ['sql_editor_sql_statistics_unavailable', 'Statistiche non disponibili'], ['sql_editor_sql_execution_executed_queries', 'Executed queries:'], ['sql_editor_sql_execution_executing', 'Esecuzione in corso...'], - ['sql_execution_plan_placeholder', 'Piano di esecuzione vuoto'], - ['sql_execution_plan_properties_panel_placeholder', 'Non ci sono elementi da mostrare'], - ['sql_execution_plan_properties_panel_name', 'Nome'], - ['sql_execution_plan_properties_panel_value', 'Valore'], - ['sql_execution_plan_properties_panel_general', 'Generale'], - ['sql_execution_plan_properties_panel_details', 'Dettagli'], + ['plugin_sql_execution_plan_placeholder', 'Piano di esecuzione vuoto'], + ['plugin_sql_execution_plan_properties_panel_placeholder', 'Non ci sono elementi da mostrare'], + ['plugin_sql_execution_plan_properties_panel_name', 'Nome'], + ['plugin_sql_execution_plan_properties_panel_value', 'Valore'], + ['plugin_sql_execution_plan_properties_panel_general', 'Generale'], + ['plugin_sql_execution_plan_properties_panel_details', 'Dettagli'], + ['plugin_sql_execution_plan_view_table', 'Tabella'], ['sql_editor_close_result_tabs_dialog_title', 'Confirm closing tabs'], ['plugin_sql_editor_action_overlay_title', 'Action required'], ['plugin_sql_editor_action_overlay_description', 'Please reopen editor to continue working.'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/ru.ts b/webapp/packages/plugin-sql-editor/src/locales/ru.ts index 0e16614799..3fc919c7ed 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/ru.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/ru.ts @@ -27,12 +27,13 @@ export default [ ['sql_editor_sql_statistics_unavailable', 'Статистика не доступна'], ['sql_editor_sql_execution_executed_queries', 'Выполненные запросы:'], ['sql_editor_sql_execution_executing', 'Выполняется...'], - ['sql_execution_plan_placeholder', 'План выполнения пуст'], - ['sql_execution_plan_properties_panel_placeholder', 'Информация не доступна'], - ['sql_execution_plan_properties_panel_name', 'Название'], - ['sql_execution_plan_properties_panel_value', 'Значение'], - ['sql_execution_plan_properties_panel_general', 'Общие'], - ['sql_execution_plan_properties_panel_details', 'Дополнительные'], + ['plugin_sql_execution_plan_placeholder', 'План выполнения пуст'], + ['plugin_sql_execution_plan_properties_panel_placeholder', 'Информация не доступна'], + ['plugin_sql_execution_plan_properties_panel_name', 'Название'], + ['plugin_sql_execution_plan_properties_panel_value', 'Значение'], + ['plugin_sql_execution_plan_properties_panel_general', 'Общие'], + ['plugin_sql_execution_plan_properties_panel_details', 'Дополнительные'], + ['plugin_sql_execution_plan_view_table', 'Таблица'], ['sql_editor_upload_script_tooltip', 'Загрузить sql скрипт'], ['sql_editor_download_script_tooltip', 'Скачать sql скрипт'], ['sql_editor_upload_script_unsaved_changes_dialog_message', 'Текущий скрипт будет перезаписан загруженным. Сохранить текущий?'], diff --git a/webapp/packages/plugin-sql-editor/src/locales/vi.ts b/webapp/packages/plugin-sql-editor/src/locales/vi.ts index 513f0746d9..111b4b62b0 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/vi.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/vi.ts @@ -27,12 +27,13 @@ export default [ ['sql_editor_sql_statistics_unavailable', 'Thống kê không khả dụng'], ['sql_editor_sql_execution_executed_queries', 'Các truy vấn đã thực thi:'], ['sql_editor_sql_execution_executing', 'Đang thực thi...'], - ['sql_execution_plan_placeholder', 'Kế hoạch thực thi trống'], - ['sql_execution_plan_properties_panel_placeholder', 'Không có mục nào để hiển thị'], - ['sql_execution_plan_properties_panel_name', 'Tên'], - ['sql_execution_plan_properties_panel_value', 'Giá trị'], - ['sql_execution_plan_properties_panel_general', 'Chung'], - ['sql_execution_plan_properties_panel_details', 'Chi tiết'], + ['plugin_sql_execution_plan_placeholder', 'Kế hoạch thực thi trống'], + ['plugin_sql_execution_plan_properties_panel_placeholder', 'Không có mục nào để hiển thị'], + ['plugin_sql_execution_plan_properties_panel_name', 'Tên'], + ['plugin_sql_execution_plan_properties_panel_value', 'Giá trị'], + ['plugin_sql_execution_plan_properties_panel_general', 'Chung'], + ['plugin_sql_execution_plan_properties_panel_details', 'Chi tiết'], + ['plugin_sql_execution_plan_view_table', 'Bảng'], ['sql_editor_upload_script_tooltip', 'Tải lên kịch bản SQL'], ['sql_editor_download_script_tooltip', 'Tải xuống kịch bản SQL'], [ diff --git a/webapp/packages/plugin-sql-editor/src/locales/zh.ts b/webapp/packages/plugin-sql-editor/src/locales/zh.ts index 7cec11ca77..4a04931be1 100644 --- a/webapp/packages/plugin-sql-editor/src/locales/zh.ts +++ b/webapp/packages/plugin-sql-editor/src/locales/zh.ts @@ -27,12 +27,13 @@ export default [ ['sql_editor_sql_statistics_unavailable', '统计不可用'], ['sql_editor_sql_execution_queries', '查询:'], ['sql_editor_sql_execution_executing', '执行...'], - ['sql_execution_plan_placeholder', '执行计划为空'], - ['sql_execution_plan_properties_panel_placeholder', '没有要显示的项目'], - ['sql_execution_plan_properties_panel_name', '名称'], - ['sql_execution_plan_properties_panel_value', '值'], - ['sql_execution_plan_properties_panel_general', '一般'], - ['sql_execution_plan_properties_panel_details', '详情'], + ['plugin_sql_execution_plan_placeholder', '执行计划为空'], + ['plugin_sql_execution_plan_properties_panel_placeholder', '没有要显示的项目'], + ['plugin_sql_execution_plan_properties_panel_name', '名称'], + ['plugin_sql_execution_plan_properties_panel_value', '值'], + ['plugin_sql_execution_plan_properties_panel_general', '一般'], + ['plugin_sql_execution_plan_properties_panel_details', '详情'], + ['plugin_sql_execution_plan_view_table', '表格'], ['sql_editor_upload_script_tooltip', '上传SQL脚本'], ['sql_editor_download_script_tooltip', '下载SQL脚本'], ['sql_editor_upload_script_unsaved_changes_dialog_message', '您当前的脚本将被上传的脚本覆盖。要先保存吗?'], diff --git a/webapp/packages/plugin-sql-editor/src/module.ts b/webapp/packages/plugin-sql-editor/src/module.ts index 7b4d16819d..3cf6b6541e 100644 --- a/webapp/packages/plugin-sql-editor/src/module.ts +++ b/webapp/packages/plugin-sql-editor/src/module.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ import { OutputLogsService } from './SqlResultTabs/OutputLogs/OutputLogsService. import { OutputLogsResource } from './SqlResultTabs/OutputLogs/OutputLogsResource.js'; import { OutputLogsEventHandler } from './SqlResultTabs/OutputLogs/OutputLogsEventHandler.js'; import { SqlExecutionPlanService } from './SqlResultTabs/ExecutionPlan/SqlExecutionPlanService.js'; +import { SqlExecutionPlanViewService } from './SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewService.js'; +import { SqlExecutionPlanViewBootstrap } from './SqlResultTabs/ExecutionPlan/SqlExecutionPlanViewBootstrap.js'; import { SqlEditorView } from './SqlEditorView.js'; import { SqlEditorSettingsService } from './SqlEditorSettingsService.js'; import { SqlEditorService } from './SqlEditorService.js'; @@ -49,6 +51,8 @@ export default ModuleRegistry.add({ .addSingleton(OutputLogsResource) .addSingleton(OutputLogsEventHandler) .addSingleton(SqlExecutionPlanService) + .addSingleton(SqlExecutionPlanViewService) + .addSingleton(Bootstrap, SqlExecutionPlanViewBootstrap) .addSingleton(SqlEditorView) .addSingleton(SqlEditorSettingsService) .addSingleton(SqlEditorService) diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 0fa0e11e24..d287b3be99 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -1482,8 +1482,6 @@ __metadata: react-dom: "npm:^19" react-hotkeys-hook: "npm:5.1.0" react-minisearch: "npm:^7" - reakit: "npm:^1" - reakit-utils: "npm:^0" rimraf: "npm:^6" subscript: "npm:^9" tslib: "npm:^2" @@ -1826,12 +1824,12 @@ __metadata: "@cloudbeaver/core-di": "workspace:*" "@cloudbeaver/core-events": "workspace:*" "@cloudbeaver/tsconfig": "workspace:*" + "@dbeaver/ui-kit": "workspace:^" "@types/react": "npm:^19" mobx: "npm:^6" mobx-react-lite: "npm:^4" react: "npm:^19" react-dom: "npm:^19" - reakit: "npm:^1" rimraf: "npm:^6" tslib: "npm:^2" typescript: "npm:^5" @@ -2237,7 +2235,6 @@ __metadata: react-dnd: "npm:^16" react-dnd-html5-backend: "npm:^16" react-dom: "npm:^19" - reakit: "npm:^1" rimraf: "npm:^6" tslib: "npm:^2" typescript: "npm:^5" @@ -2254,6 +2251,7 @@ __metadata: "@cloudbeaver/tests-runner": "workspace:*" "@cloudbeaver/tsconfig": "workspace:*" "@dbeaver/cli": "workspace:*" + "@dbeaver/js-helpers": "workspace:*" "@dbeaver/react-tests": "workspace:*" "@timohausmann/quadtree-ts": "npm:^2" "@types/md5": "npm:^2" @@ -2510,7 +2508,6 @@ __metadata: mobx-react-lite: "npm:^4" react: "npm:^19" react-dom: "npm:^19" - reakit: "npm:^1" rimraf: "npm:^6" tslib: "npm:^2" typescript: "npm:^5" @@ -3068,6 +3065,7 @@ __metadata: "@cloudbeaver/plugin-data-viewer": "workspace:*" "@cloudbeaver/plugin-sql-editor": "workspace:*" "@cloudbeaver/tsconfig": "workspace:*" + "@dbeaver/ui-kit": "workspace:*" "@types/react": "npm:^19" mobx: "npm:^6" mobx-react-lite: "npm:^4" @@ -3425,6 +3423,7 @@ __metadata: "@cloudbeaver/tsconfig": "workspace:*" "@dbeaver/cli": "workspace:*" "@dbeaver/react-tests": "workspace:*" + "@dbeaver/ui-kit": "workspace:^" "@types/react": "npm:^19" mobx: "npm:^6" mobx-react-lite: "npm:^4" @@ -3628,7 +3627,6 @@ __metadata: mobx-react-lite: "npm:^4" react: "npm:^19" react-dom: "npm:^19" - reakit: "npm:^1" rimraf: "npm:^6" tslib: "npm:^2" typescript: "npm:^5" @@ -7266,13 +7264,6 @@ __metadata: languageName: node linkType: hard -"@popperjs/core@npm:^2.5.4": - version: 2.11.8 - resolution: "@popperjs/core@npm:2.11.8" - checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63 - languageName: node - linkType: hard - "@react-dnd/asap@npm:^5.0.1": version: 5.0.2 resolution: "@react-dnd/asap@npm:5.0.2" @@ -9864,13 +9855,6 @@ __metadata: languageName: node linkType: hard -"body-scroll-lock@npm:^3.1.5": - version: 3.1.5 - resolution: "body-scroll-lock@npm:3.1.5" - checksum: 10c0/657c948d64ffe17d2c89958f57c2c636203cf5216e3ba33ad76604bf1b5144fd8f4b5d832d09105f8782d5b8bc96f38780331ec55f3af5f27694dff38a0cc9d7 - languageName: node - linkType: hard - "boolbase@npm:^1.0.0": version: 1.0.0 resolution: "boolbase@npm:1.0.0" @@ -10376,7 +10360,6 @@ __metadata: prettier: "npm:^3.6.2" react: "npm:^19" react-dom: "npm:^19" - reakit: "npm:^1" rimraf: "npm:^6" typescript: "npm:^5.8.3" typescript-plugin-css-modules: "npm:^5" @@ -12946,9 +12929,9 @@ __metadata: languageName: node linkType: hard -"happy-dom@npm:^20": - version: 20.8.3 - resolution: "happy-dom@npm:20.8.3" +"happy-dom@npm:>=20.8.8": + version: 20.8.9 + resolution: "happy-dom@npm:20.8.9" dependencies: "@types/node": "npm:>=20.0.0" "@types/whatwg-mimetype": "npm:^3.0.2" @@ -12956,7 +12939,7 @@ __metadata: entities: "npm:^7.0.1" whatwg-mimetype: "npm:^3.0.0" ws: "npm:^8.18.3" - checksum: 10c0/79a2b6d32e2f778b015511c9d2de7f7859954cbd27a30720736e60552a88e0345ffc8b2c5e77140c5f2f7fef95c563aea7ab0b2edba5d8c7edd5676b43ecef55 + checksum: 10c0/12fe9b85891b1289935b2a4f726e2839e7c92e3f55401bd20cb5500763dd994364645acaab20a754fd90d5e1b2ffdf734b39a32ea02704c3e3ee0c4d30cf958e languageName: node linkType: hard @@ -16383,17 +16366,10 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be - languageName: node - linkType: hard - -"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": - version: 4.0.3 - resolution: "picomatch@npm:4.0.3" - checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 +"picomatch@npm:>=2.3.2": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 languageName: node linkType: hard @@ -16872,55 +16848,6 @@ __metadata: languageName: node linkType: hard -"reakit-system@npm:^0.15.2": - version: 0.15.2 - resolution: "reakit-system@npm:0.15.2" - dependencies: - reakit-utils: "npm:^0.15.2" - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: 10c0/bd97e297b972e205656dd99a6955ed263d5102ab24212e55b90a86dd9a4eac056b63ba882af65705036922e9cb599d5903318d1202086a3e4ee45b5cc794d44e - languageName: node - linkType: hard - -"reakit-utils@npm:^0, reakit-utils@npm:^0.15.2": - version: 0.15.2 - resolution: "reakit-utils@npm:0.15.2" - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: 10c0/258654f9510fb7622fc03f81a63021721ace6c424bdff47a8f79e3b974501ef6b1b44fdb78ab01ed1a75d3fd6867d100e97ef74138c0d848c2aa5738477bf853 - languageName: node - linkType: hard - -"reakit-warning@npm:^0.6.2": - version: 0.6.2 - resolution: "reakit-warning@npm:0.6.2" - dependencies: - reakit-utils: "npm:^0.15.2" - peerDependencies: - react: ^16.8.0 || ^17.0.0 - checksum: 10c0/e93856e9dd4b56e7cd36cd654cb5688eb05fc8f75e3d89069352bc61ab6f6cf501892405dd1612442e45880562048af5c17b3e771fd6af79348bc90a20f8d930 - languageName: node - linkType: hard - -"reakit@npm:^1": - version: 1.3.11 - resolution: "reakit@npm:1.3.11" - dependencies: - "@popperjs/core": "npm:^2.5.4" - body-scroll-lock: "npm:^3.1.5" - reakit-system: "npm:^0.15.2" - reakit-utils: "npm:^0.15.2" - reakit-warning: "npm:^0.6.2" - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: 10c0/ce5972b1242bd0704840c77faa6f687cc3bd0e66ac5f6fe84328408d2e624a4532c4bb1d82b6b6dcbc876dc0700c472b420acebf80603d59115a810ae8916e12 - languageName: node - linkType: hard - "recast@npm:^0.23.5": version: 0.23.11 resolution: "recast@npm:0.23.11"