Merge branch 'devel' into 8433-cb-replace-material-ui-with-native-css-variables-and-tailwind-utilities
@@ -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<T extends LocalizationToken | undefined>(token: T, fallback?: T): T {
|
||||
return fallback || token;
|
||||
export function interpolateTranslation(template: string, args?: Record<string | number, unknown>): 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 = <T extends LocalizationToken | undefined>(token: T, fallback?: T, args?: Record<string | number, any>) => T;
|
||||
export function defaultTranslateFn<T extends LocalizationToken | undefined>(token: T, fallback?: T, args?: Record<string | number, unknown>): T {
|
||||
const result = fallback || token;
|
||||
if (result && args) {
|
||||
return interpolateTranslation(result as string, args) as T;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export type TranslateFn = <T extends LocalizationToken | undefined>(token: T, fallback?: T, args?: Record<string | number, unknown>) => T;
|
||||
export const TranslateContext = createContext<{ translate: TranslateFn }>({
|
||||
translate: (key, fallback) => fallback || key,
|
||||
translate: defaultTranslateFn,
|
||||
});
|
||||
|
||||
@@ -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 };
|
||||
@@ -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);
|
||||
|
||||
@@ -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 <AriakitCompositeProvider {...props}>{children}</AriakitCompositeProvider>;
|
||||
}
|
||||
|
||||
export function Composite({ children, ...props }: CompositeProps) {
|
||||
return <AriakitComposite {...props}>{children}</AriakitComposite>;
|
||||
}
|
||||
|
||||
export function CompositeItem({ children, ...props }: CompositeItemProps) {
|
||||
return <AriakitCompositeItem {...props}>{children}</AriakitCompositeItem>;
|
||||
}
|
||||
|
||||
export { useCompositeStore, type CompositeProps, type CompositeItemProps, type CompositeProviderProps, type CompositeStore };
|
||||
@@ -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;
|
||||
|
||||
@@ -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 <Ariakit.PopoverAnchor {...props}>{children}</Ariakit.PopoverAnchor>;
|
||||
}
|
||||
|
||||
export function Popover({ children, ...props }: Ariakit.PopoverProviderProps) {
|
||||
return <Ariakit.PopoverProvider {...props}>{children}</Ariakit.PopoverProvider>;
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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 <AriakitPortal {...props}>{children}</AriakitPortal>;
|
||||
}
|
||||
|
||||
export type { PortalProps };
|
||||
@@ -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';
|
||||
|
||||
@@ -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:^",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -16,3 +16,4 @@ export * from './memoizeLast.js';
|
||||
export * from './mutex.js';
|
||||
export * from './reorderArray.js';
|
||||
export * from './getLocalizedDisplayName.js';
|
||||
export * from './formatNumber.js';
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<IMenuState>;
|
||||
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<HTMLDivElement, AutocompletionProps>(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 (
|
||||
<Menu
|
||||
contextMenuPosition={contextMenuPosition}
|
||||
visible={proposals.length > 0}
|
||||
panelAvailable={proposals.length > 0}
|
||||
className={s(styles, { menu: true }, 'tw:shadow-small', className)}
|
||||
menuRef={menuRef}
|
||||
label="Autocompletion"
|
||||
items={proposals.map(proposal => (
|
||||
<MenuItem
|
||||
key={proposal.displayString}
|
||||
id={proposal.displayString}
|
||||
type="button"
|
||||
title={proposal.title}
|
||||
className={s(styles, { menuItem: true })}
|
||||
onClick={event => handleSelect(proposal)}
|
||||
>
|
||||
{proposal.icon && (
|
||||
<div className={s(styles, { itemIcon: true })}>
|
||||
<IconOrImage icon={proposal.icon} className={s(styles, { iconOrImage: true })} />
|
||||
</div>
|
||||
)}
|
||||
<Text truncate>{proposal.displayString}</Text>
|
||||
</MenuItem>
|
||||
))}
|
||||
modal
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Popover.PopoverContent ref={ref} className={s(styles, { menu: true })} gutter={4} modal={false} autoFocusOnShow={false} unmountOnHide>
|
||||
<Composite>
|
||||
{proposals.map(proposal => (
|
||||
<CompositeItem key={proposal.title} className={s(styles, { menuItem: true }, 'tw:w-full')} onClick={() => onSelect?.(proposal)}>
|
||||
{proposal.icon && (
|
||||
<div className={s(styles, { itemIcon: true })}>
|
||||
<IconOrImage icon={proposal.icon} className={s(styles, { iconOrImage: true })} />
|
||||
</div>
|
||||
)}
|
||||
<Text truncate>{proposal.displayString}</Text>
|
||||
</CompositeItem>
|
||||
))}
|
||||
</Composite>
|
||||
</Popover.PopoverContent>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -18,12 +18,3 @@
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
.iconButton {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
</FieldLabel>
|
||||
<UploadArea title={tooltip} disabled={disabled} accept={accept} reset onChange={handleChange}>
|
||||
<Button className="tw:inline-flex" icon="/icons/import.svg" tag="div" variant="secondary" disabled={disabled}>
|
||||
<Button className="tw:w-max" icon="/icons/import.svg" tag="div" variant="secondary" disabled={disabled}>
|
||||
{translate('ui_upload_file')}
|
||||
</Button>
|
||||
</UploadArea>
|
||||
<FieldDescription className={s(styles, { fieldDescription: true })}>
|
||||
{description}
|
||||
{(selected || saved) && <IconButton className={s(styles, { iconButton: true })} disabled={disabled} name="cross" onClick={removeFile} />}
|
||||
{(selected || saved) && (
|
||||
<IconButton
|
||||
className="tw:shrink-0"
|
||||
variant="secondary"
|
||||
disabled={disabled}
|
||||
size="small"
|
||||
aria-label={translate('ui_remove')}
|
||||
onClick={removeFile}
|
||||
>
|
||||
<Icon name="cross" width={12} height={12} />
|
||||
</IconButton>
|
||||
)}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
);
|
||||
|
||||
@@ -184,7 +184,7 @@ export const InputFiles: InputFilesType = observer(
|
||||
<UploadArea ref={ref} {...rest} name={name} value={value} required={required} reset onChange={handleChange}>
|
||||
<Button
|
||||
iconPlacement="start"
|
||||
className="tw:inline-flex tw:justify-center"
|
||||
className="tw:w-max tw:justify-center"
|
||||
icon="/icons/import.svg"
|
||||
tag="div"
|
||||
loading={loading}
|
||||
|
||||
@@ -143,7 +143,7 @@ export const Textarea: TextareaType = observer(function Textarea({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button className="tw:inline-flex" tag="div" disabled={rest.disabled || rest.readOnly} variant="secondary">
|
||||
<Button className="tw:w-max" tag="div" disabled={rest.disabled || rest.readOnly} variant="secondary">
|
||||
{translate('ui_file')}
|
||||
</Button>
|
||||
</UploadArea>
|
||||
|
||||
@@ -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<IMenuState>;
|
||||
}
|
||||
|
||||
const DEFAULT_SEPARATOR = ' ';
|
||||
@@ -48,7 +48,7 @@ const CONTEXT_INPUT_OFFSET_Y = 3;
|
||||
|
||||
export const useInputAutocomplete = (
|
||||
inputRef: RefObject<HTMLInputElement | HTMLTextAreaElement | null>,
|
||||
{ sourceHints, separator = DEFAULT_SEPARATOR, matchStrategy = 'contains', predicate }: InputAutocompleteOptions,
|
||||
{ sourceHints, separator = DEFAULT_SEPARATOR, matchStrategy = 'contains', onNavigate, predicate }: InputAutocompleteOptions,
|
||||
): Readonly<State> => {
|
||||
const search = useSearch({
|
||||
sourceHints,
|
||||
@@ -56,7 +56,7 @@ export const useInputAutocomplete = (
|
||||
matchStrategy,
|
||||
predicate,
|
||||
});
|
||||
const menuRef = useRef<IMenuState>(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();
|
||||
|
||||
@@ -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<IconButtonProps> = observer(function IconButton({ tag, name, img, viewBox, className, ...rest }) {
|
||||
export const IconButton: React.FC<IconButtonProps> = observer(function IconButton({ name, img, viewBox, className, ...rest }) {
|
||||
const styles = useS(IconButtonStyles);
|
||||
|
||||
const Button = tag ?? ReakitButton;
|
||||
|
||||
return (
|
||||
<Button tabIndex={0} {...rest} className={s(styles, { iconButton: true }, className)}>
|
||||
<UnstyledButton tabIndex={0} {...rest} className={s(styles, { iconButton: true }, className)}>
|
||||
<div className={s(styles, { iconBox: true })}>
|
||||
{img && <StaticImage className={s(styles, { staticImage: true })} icon={name} />}
|
||||
{!img && <Icon className={s(styles, { icon: true })} name={name} viewBox={viewBox} />}
|
||||
</div>
|
||||
</Button>
|
||||
</UnstyledButton>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<any> {
|
||||
contextMenuPosition?: IContextMenuPosition;
|
||||
label: string;
|
||||
items: React.ReactNode | (() => React.ReactNode);
|
||||
menuRef?: React.RefObject<IMenuState | null>;
|
||||
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<IMenuProps, HTMLButtonElement>(
|
||||
forwardRef(function Menu(
|
||||
{
|
||||
contextMenuPosition,
|
||||
label,
|
||||
items,
|
||||
menuRef,
|
||||
disclosure,
|
||||
children,
|
||||
placement,
|
||||
visible,
|
||||
hasBindings,
|
||||
panelAvailable,
|
||||
getHasBindings,
|
||||
onVisibleSwitch,
|
||||
modal,
|
||||
submenu,
|
||||
rtl,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const innerMenuButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const combinedRef = useCombinedRef(ref, innerMenuButtonRef);
|
||||
const [relativePosition, setRelativePosition] = useState<{ x: number; y: number } | null>(null);
|
||||
const menuButtonLinkRef = useRef<HTMLButtonElement>(null);
|
||||
const menuPanelRef = useRef<HTMLDivElement>(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 (
|
||||
<ErrorBoundary>
|
||||
<MenuStateContext.Provider value={menu}>
|
||||
<MenuButton
|
||||
key={relativePosition ? 'link' : 'main'}
|
||||
ref={combinedRef}
|
||||
tabIndex={0}
|
||||
className={s(styles, { menuButton: true }, className)}
|
||||
{...menu}
|
||||
visible={menuVisible}
|
||||
{...props}
|
||||
{...(children.props as any)}
|
||||
>
|
||||
{(disclosureProps: ExtractHTMLAttributes<any>) => React.cloneElement(children, { ...disclosureProps, ...(children.props as any) })}
|
||||
</MenuButton>
|
||||
<MenuPanel
|
||||
ref={menuPanelRef}
|
||||
label={label}
|
||||
menu={menu}
|
||||
rtl={rtl}
|
||||
submenu={submenu}
|
||||
panelAvailable={panelAvailable}
|
||||
hasBindings={hasBindings}
|
||||
getHasBindings={getHasBindings}
|
||||
>
|
||||
{items}
|
||||
</MenuPanel>
|
||||
{relativePosition && (
|
||||
<MenuButtonLink ref={menuButtonLinkRef} className={s(styles, { menuButtonLink: true })} {...menu} visible={menuVisible} />
|
||||
)}
|
||||
</MenuStateContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<MenuStateContext.Provider value={menu}>
|
||||
<MenuButton
|
||||
key={relativePosition ? 'link' : 'main'}
|
||||
ref={combinedRef}
|
||||
tabIndex={0}
|
||||
className={s(styles, { menuButton: true }, className)}
|
||||
{...menu}
|
||||
visible={menuVisible}
|
||||
{...props}
|
||||
>
|
||||
<div className={s(styles, { box: true }, className)}>{children}</div>
|
||||
</MenuButton>
|
||||
<MenuPanel
|
||||
ref={menuPanelRef}
|
||||
label={label}
|
||||
menu={menu}
|
||||
rtl={rtl}
|
||||
submenu={submenu}
|
||||
panelAvailable={panelAvailable}
|
||||
hasBindings={hasBindings}
|
||||
getHasBindings={getHasBindings}
|
||||
>
|
||||
{items}
|
||||
</MenuPanel>
|
||||
{relativePosition && (
|
||||
<MenuButtonLink ref={menuButtonLinkRef} className={s(styles, { menuButtonLink: true })} {...menu} visible={menuVisible} />
|
||||
)}
|
||||
</MenuStateContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}),
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ButtonProps, 'style' | 'icon'> {
|
||||
icon?: string;
|
||||
viewBox?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const MenuBarSmallItem: React.FC<React.PropsWithChildren<Props>> = 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 (
|
||||
<div className={s(style, { iconBox: true }, className)} tabIndex={0} onClick={onClick}>
|
||||
{icon && <IconButton className={s(style, { iconButton: true })} name={icon} viewBox={viewBox} {...rest} />}
|
||||
{children && <div className={s(style, { iconLabel: true })}>{children}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<MenuItem id="empty" aria-label={label} disabled>
|
||||
<MenuItemElement label={label} />
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ReakitProxyComponentOptions<'button', MenuItemOptions>>(
|
||||
function MenuItem({ children, hidden, selected, close, onClick, className, ...rest }) {
|
||||
const menu = useContext(MenuStateContext);
|
||||
const styles = useS(style);
|
||||
|
||||
const handleClick = useCombinedHandler<[React.MouseEvent<HTMLButtonElement>]>(onClick, function handleClick() {
|
||||
if (close) {
|
||||
menu?.hide();
|
||||
}
|
||||
});
|
||||
|
||||
const MenuItem = ReakitMenuItem;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
{...menu}
|
||||
aria-selected={selected}
|
||||
{...rest}
|
||||
className={s(styles, { menuItem: true, hidden }, className)}
|
||||
disabled={selected || rest.disabled}
|
||||
style={{ pointerEvents: 'auto' }}
|
||||
focusable
|
||||
onClick={handleClick}
|
||||
>
|
||||
{children}
|
||||
</MenuItem>
|
||||
);
|
||||
},
|
||||
) as ReakitProxyComponent<'button', MenuItemOptions>;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<IMenuPanelProps, HTMLDivElement>(
|
||||
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 (
|
||||
<ErrorBoundary>
|
||||
<Menu
|
||||
ref={ref}
|
||||
className={s(styles, { menu: true, modal: menu.modal, submenu }, className)}
|
||||
{...menu}
|
||||
aria-label={translate(label)}
|
||||
visible={panelAvailable}
|
||||
>
|
||||
<div dir={rtl ? 'rtl' : undefined} data-s-has-bindings={hasBindings} className={s(styles, { menuBox: true }, 'tw:shadow-medium')}>
|
||||
{Children.count(renderedChildren) === 0 && <MenuEmptyItem />}
|
||||
{renderedChildren}
|
||||
</div>
|
||||
</Menu>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}),
|
||||
);
|
||||
@@ -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<IMenuState | null>(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<O, T extends As> = PropsWithAs<O, T>;
|
||||
|
||||
export type ReakitProxyComponentOptions<T extends As, O> = ReakitProxyPropsWithAs<O, T>;
|
||||
|
||||
export type ReakitProxyComponent<T extends As, O> = {
|
||||
<TT extends As = T>(
|
||||
props: ReakitProxyPropsWithAs<O, TT> & {
|
||||
as: TT;
|
||||
},
|
||||
context?: any,
|
||||
): React.ReactElement<any, any> | null;
|
||||
(props: ReakitProxyPropsWithAs<O, T>, context?: any): React.ReactElement<any, any> | null;
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../../common-react/@dbeaver/ui-kit"
|
||||
},
|
||||
{
|
||||
"path": "../core-blocks"
|
||||
},
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
"react-dnd": "^16",
|
||||
"react-dnd-html5-backend": "^16",
|
||||
"react-dom": "^19",
|
||||
"reakit": "^1",
|
||||
"tslib": "^2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -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<IContextMenuProps, 'placement'> {
|
||||
export interface IContextMenuNewProps extends IContextMenuProps {
|
||||
render?: React.ReactElement;
|
||||
placement?: HovercardStoreState['placement'];
|
||||
showOnHover?: boolean;
|
||||
|
||||
@@ -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<ButtonHTMLAttributes<any>, 'children'> {
|
||||
contextMenuPosition?: IContextMenuPosition;
|
||||
menu: IMenuData;
|
||||
placement?: MenuInitialState['placement'];
|
||||
visible?: boolean;
|
||||
rtl?: boolean;
|
||||
children?: React.ReactNode;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
{
|
||||
"path": "../../common-typescript/@dbeaver/cli"
|
||||
},
|
||||
{
|
||||
"path": "../../common-typescript/@dbeaver/js-helpers"
|
||||
},
|
||||
{
|
||||
"path": "../core-cli"
|
||||
},
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.5 6.5C10.5 8.70914 8.70914 10.5 6.5 10.5C4.29086 10.5 2.5 8.70914 2.5 6.5C2.5 4.29086 4.29086 2.5 6.5 2.5C8.70914 2.5 10.5 4.29086 10.5 6.5ZM9.68256 10.9862C8.78427 11.6246 7.68597 12 6.5 12C3.46243 12 1 9.53757 1 6.5C1 3.46243 3.46243 1 6.5 1C9.53757 1 12 3.46243 12 6.5C12 7.88792 11.4859 9.15577 10.6378 10.1235L10.7678 10.1464L15.0104 14.3891L13.9497 15.4497L9.70711 11.2071L9.68256 10.9862ZM4.8 7.29999V5.79999H5.8V4.79999H7.3V5.79999H8.3V7.29999H7.3V8.29999H5.8V7.29999H4.8Z" fill="#398FCA"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 655 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 10.5C8.70914 10.5 10.5 8.70914 10.5 6.5C10.5 4.29086 8.70914 2.5 6.5 2.5C4.29086 2.5 2.5 4.29086 2.5 6.5C2.5 8.70914 4.29086 10.5 6.5 10.5ZM6.5 12C7.68597 12 8.78427 11.6246 9.68256 10.9862L9.70711 11.2071L13.9497 15.4497L15.0104 14.3891L10.7678 10.1464L10.6378 10.1235C11.4859 9.15577 12 7.88792 12 6.5C12 3.46243 9.53757 1 6.5 1C3.46243 1 1 3.46243 1 6.5C1 9.53757 3.46243 12 6.5 12ZM4.8 5.79999V7.29999H8.3V5.79999H4.8Z" fill="#398FCA"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 598 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7928_11650)">
|
||||
<path d="M12.0001 3.00001C16.9601 3.00001 21.0001 7.04003 21.0001 12.0001C21.0001 16.9601 16.9601 21.0001 12.0001 21.0001C7.04003 21.0001 3.00001 16.9601 3.00001 12.0001C3.00001 7.04003 7.04003 3.00001 12.0001 3.00001ZM12.0001 1C5.92002 1 1 5.92002 1 12.0001C1 18.0801 5.92002 23.0001 12.0001 23.0001C18.0801 23.0001 23.0001 18.0801 23.0001 12.0001C23.0001 5.92002 18.0801 1 12.0001 1Z" fill="#3387C5"/>
|
||||
<path d="M19 19L30.0001 30" stroke="#3387C5" stroke-width="2" stroke-miterlimit="10"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7928_11650">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 742 B |
@@ -0,0 +1,13 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7923_11529)">
|
||||
<path d="M12.0001 3.00001C16.9601 3.00001 21.0001 7.04003 21.0001 12.0001C21.0001 16.9601 16.9601 21.0001 12.0001 21.0001C7.04003 21.0001 3.00001 16.9601 3.00001 12.0001C3.00001 7.04003 7.04003 3.00001 12.0001 3.00001ZM12.0001 1C5.92002 1 1 5.92002 1 12.0001C1 18.0801 5.92002 23.0001 12.0001 23.0001C18.0801 23.0001 23.0001 18.0801 23.0001 12.0001C23.0001 5.92002 18.0801 1 12.0001 1Z" fill="#3387C5"/>
|
||||
<path d="M19 19L30.0001 30" stroke="#3387C5" stroke-width="2" stroke-miterlimit="10"/>
|
||||
<path d="M13 6H11V18.0001H13V6Z" fill="#3387C5"/>
|
||||
<path d="M18.0001 11H6V13H18.0001V11Z" fill="#3387C5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7923_11529">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 848 B |
@@ -0,0 +1,13 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7923_11590)">
|
||||
<path d="M9.64291 2.57142C13.5401 2.57142 16.7144 5.74569 16.7144 9.64282C16.7144 13.5399 13.5401 16.7142 9.64291 16.7142C5.74574 16.7142 2.57144 13.5399 2.57144 9.64282C2.57144 5.74569 5.74574 2.57142 9.64291 2.57142ZM9.64291 1C4.86574 1 1 4.8657 1 9.64282C1 14.4199 4.86574 18.2856 9.64291 18.2856C14.4201 18.2856 18.2858 14.4199 18.2858 9.64282C18.2858 4.8657 14.4201 1 9.64291 1Z" fill="#3387C5"/>
|
||||
<path d="M15.1426 15.1426L22.9998 22.9997" stroke="#3387C5" stroke-width="1.5" stroke-miterlimit="10"/>
|
||||
<path d="M10.4279 4.92871H8.85645V14.3572H10.4279V4.92871Z" fill="#3387C5"/>
|
||||
<path d="M14.3573 8.85645H4.92871V10.4279H14.3573V8.85645Z" fill="#3387C5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7923_11590">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 911 B |
@@ -0,0 +1,18 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7923_11544)">
|
||||
<g clip-path="url(#clip1_7923_11544)">
|
||||
<path d="M6.28559 1.14283C9.11982 1.14283 11.4283 3.45136 11.4283 6.28559C11.4283 9.11982 9.11982 11.4283 6.28559 11.4283C3.45136 11.4283 1.14283 9.11982 1.14283 6.28559C1.14283 3.45136 3.45136 1.14283 6.28559 1.14283ZM6.28559 0C2.81137 0 0 2.81137 0 6.28559C0 9.7598 2.81137 12.5712 6.28559 12.5712C9.7598 12.5712 12.5712 9.7598 12.5712 6.28559C12.5712 2.81137 9.7598 0 6.28559 0Z" fill="#3387C5"/>
|
||||
<path d="M10.2861 10.2861L16.0003 16.0003" stroke="#3387C5" stroke-miterlimit="10"/>
|
||||
<path d="M6.85768 2.85645H5.71484V9.71345H6.85768V2.85645Z" fill="#3387C5"/>
|
||||
<path d="M9.71345 5.71484H2.85645V6.85768H9.71345V5.71484Z" fill="#3387C5"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7923_11544">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_7923_11544">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1023 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7928_11656)">
|
||||
<path d="M9.64291 2.57142C13.5401 2.57142 16.7144 5.74569 16.7144 9.64282C16.7144 13.5399 13.5401 16.7142 9.64291 16.7142C5.74574 16.7142 2.57144 13.5399 2.57144 9.64282C2.57144 5.74569 5.74574 2.57142 9.64291 2.57142ZM9.64291 1C4.86574 1 1 4.8657 1 9.64282C1 14.4199 4.86574 18.2856 9.64291 18.2856C14.4201 18.2856 18.2858 14.4199 18.2858 9.64282C18.2858 4.8657 14.4201 1 9.64291 1Z" fill="#3387C5"/>
|
||||
<path d="M15.1426 15.1426L22.9998 22.9997" stroke="#3387C5" stroke-width="1.5" stroke-miterlimit="10"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7928_11656">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 757 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7928_11629)">
|
||||
<path d="M12.0001 3.00001C16.9601 3.00001 21.0001 7.04003 21.0001 12.0001C21.0001 16.9601 16.9601 21.0001 12.0001 21.0001C7.04003 21.0001 3.00001 16.9601 3.00001 12.0001C3.00001 7.04003 7.04003 3.00001 12.0001 3.00001ZM12.0001 1C5.92002 1 1 5.92002 1 12.0001C1 18.0801 5.92002 23.0001 12.0001 23.0001C18.0801 23.0001 23.0001 18.0801 23.0001 12.0001C23.0001 5.92002 18.0801 1 12.0001 1Z" fill="#3387C5"/>
|
||||
<path d="M19 19L30.0001 30" stroke="#3387C5" stroke-width="2" stroke-miterlimit="10"/>
|
||||
<path d="M18.0001 11H6V13H18.0001V11Z" fill="#3387C5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7928_11629">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 798 B |
@@ -0,0 +1,12 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7928_11636)">
|
||||
<path d="M9.64291 2.57142C13.5401 2.57142 16.7144 5.74569 16.7144 9.64282C16.7144 13.5399 13.5401 16.7142 9.64291 16.7142C5.74574 16.7142 2.57144 13.5399 2.57144 9.64282C2.57144 5.74569 5.74574 2.57142 9.64291 2.57142ZM9.64291 1C4.86574 1 1 4.8657 1 9.64282C1 14.4199 4.86574 18.2856 9.64291 18.2856C14.4201 18.2856 18.2858 14.4199 18.2858 9.64282C18.2858 4.8657 14.4201 1 9.64291 1Z" fill="#3387C5"/>
|
||||
<path d="M15.1426 15.1426L22.9998 22.9997" stroke="#3387C5" stroke-width="1.5" stroke-miterlimit="10"/>
|
||||
<path d="M14.3573 8.85645H4.92871V10.4279H14.3573V8.85645Z" fill="#3387C5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7928_11636">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 834 B |
@@ -0,0 +1,17 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7928_11643)">
|
||||
<g clip-path="url(#clip1_7928_11643)">
|
||||
<path d="M6.28559 1.14283C9.11982 1.14283 11.4283 3.45136 11.4283 6.28559C11.4283 9.11982 9.11982 11.4283 6.28559 11.4283C3.45136 11.4283 1.14283 9.11982 1.14283 6.28559C1.14283 3.45136 3.45136 1.14283 6.28559 1.14283ZM6.28559 0C2.81137 0 0 2.81137 0 6.28559C0 9.7598 2.81137 12.5712 6.28559 12.5712C9.7598 12.5712 12.5712 9.7598 12.5712 6.28559C12.5712 2.81137 9.7598 0 6.28559 0Z" fill="#3387C5"/>
|
||||
<path d="M10.2861 10.2861L16.0003 16.0003" stroke="#3387C5" stroke-miterlimit="10"/>
|
||||
<path d="M9.71345 5.71484H2.85645V6.85768H9.71345V5.71484Z" fill="#3387C5"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7928_11643">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_7928_11643">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 946 B |
@@ -0,0 +1,16 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7928_11662)">
|
||||
<g clip-path="url(#clip1_7928_11662)">
|
||||
<path d="M6.28559 1.14283C9.11982 1.14283 11.4283 3.45136 11.4283 6.28559C11.4283 9.11982 9.11982 11.4283 6.28559 11.4283C3.45136 11.4283 1.14283 9.11982 1.14283 6.28559C1.14283 3.45136 3.45136 1.14283 6.28559 1.14283ZM6.28559 0C2.81137 0 0 2.81137 0 6.28559C0 9.7598 2.81137 12.5712 6.28559 12.5712C9.7598 12.5712 12.5712 9.7598 12.5712 6.28559C12.5712 2.81137 9.7598 0 6.28559 0Z" fill="#3387C5"/>
|
||||
<path d="M10.2861 10.2861L16.0003 16.0003" stroke="#3387C5" stroke-miterlimit="10"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7928_11662">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_7928_11662">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 869 B |
@@ -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',
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
"mobx-react-lite": "^4",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"reakit": "^1",
|
||||
"tslib": "^2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -52,7 +52,7 @@ export const SSHKeyUploader = observer<Props>(function SSHKeyUploader({ state, s
|
||||
</Textarea>
|
||||
<GroupItem>
|
||||
<UploadArea disabled={disabled || readonly} reset onChange={handleKeyUpload}>
|
||||
<Button className="tw:inline-flex" tag="div" disabled={disabled || readonly} variant="secondary">
|
||||
<Button className="tw:w-max" tag="div" disabled={disabled || readonly} variant="secondary">
|
||||
{translate('ui_file')}
|
||||
</Button>
|
||||
</UploadArea>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<Props>(function GroupingTableItem({ id, name, className, onDelete }) {
|
||||
const translate = useTranslate();
|
||||
|
||||
return (
|
||||
<TableItem className={className} item={id} title={name}>
|
||||
<TableColumnValue width="100%" flex>
|
||||
{name}
|
||||
</TableColumnValue>
|
||||
<TableColumnValue>
|
||||
<IconButton name="cross-bold" onClick={() => onDelete(id)} />
|
||||
<IconButton size='small' aria-label={translate('ui_remove')} onClick={() => onDelete(id)}>
|
||||
<Icon name='cross-bold' width={16} height={16} />
|
||||
</IconButton>
|
||||
</TableColumnValue>
|
||||
</TableItem>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../../common-react/@dbeaver/ui-kit"
|
||||
},
|
||||
{
|
||||
"path": "../core-blocks"
|
||||
},
|
||||
|
||||
@@ -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<ITableHeaderPlaceholderProps
|
||||
const state = useWhereFilter(model, resultIndex);
|
||||
const data = useTableViewerHeaderData({ model, resultIndex });
|
||||
const inputRef = useRef<HTMLInputElement>(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<ITableHeaderPlaceholderProps
|
||||
|
||||
return (
|
||||
<Container className={styles['imbeddedEditor']}>
|
||||
<InlineEditor
|
||||
ref={inputRef}
|
||||
className={styles['inlineEditor']}
|
||||
name="data_where"
|
||||
value={state.filter}
|
||||
placeholder={translate(state.constraints?.supported ? 'table_header_sql_expression' : 'table_header_sql_expression_not_supported')}
|
||||
controlsPosition="inside"
|
||||
edited={!!state.filter}
|
||||
disableSave={!state.applicableFilter}
|
||||
disabled={state.disabled}
|
||||
simple
|
||||
onSave={onSave}
|
||||
onChange={state.set}
|
||||
/>
|
||||
<InputAutocompletionMenu
|
||||
position={autocompleteState.position}
|
||||
proposals={autocompleteState.proposals}
|
||||
menuRef={autocompleteState.menuRef}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
<CompositeProvider store={store}>
|
||||
<Popover placement='bottom-start' open={autocompleteState.proposals.length > 0}>
|
||||
<Popover.Anchor>
|
||||
<InlineEditor
|
||||
ref={inputRef}
|
||||
className={styles['inlineEditor']}
|
||||
name="data_where"
|
||||
value={state.filter}
|
||||
placeholder={translate(state.constraints?.supported ? 'table_header_sql_expression' : 'table_header_sql_expression_not_supported')}
|
||||
controlsPosition="inside"
|
||||
edited={!!state.filter}
|
||||
disableSave={!state.applicableFilter}
|
||||
disabled={state.disabled}
|
||||
simple
|
||||
onSave={onSave}
|
||||
onChange={state.set}
|
||||
/>
|
||||
</Popover.Anchor>
|
||||
<InputAutocompletionMenu
|
||||
proposals={autocompleteState.proposals}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
</Popover>
|
||||
</CompositeProvider>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'],
|
||||
];
|
||||
|
||||
@@ -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'],
|
||||
];
|
||||
|
||||
@@ -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'],
|
||||
];
|
||||
|
||||
@@ -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', 'Включить фильтрацию'],
|
||||
];
|
||||
|
||||
@@ -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'],
|
||||
|
||||
|
||||
@@ -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', '启用过滤'],
|
||||
];
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -26,10 +26,6 @@
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: unset;
|
||||
}
|
||||
|
||||
.type {
|
||||
composes: theme-typography--body2 from global;
|
||||
margin: 0;
|
||||
|
||||
@@ -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<Props>(function LogViewerInfoPanel({
|
||||
<Container className={s(styles, { panelWrapper: true }, className)} vertical>
|
||||
<Container className={s(styles, { buttons: true })} noGrow noWrap>
|
||||
<Container keepSize noWrap>
|
||||
<MenuBarSmallItem className={s(styles, { button: true })} title={translate('ui_copy_to_clipboard')} onClick={copyMessage}>
|
||||
<Button
|
||||
className='tw:flex-none'
|
||||
variant='ghost'
|
||||
size='small'
|
||||
title={translate('ui_copy_to_clipboard')}
|
||||
onClick={copyMessage}
|
||||
>
|
||||
{translate('ui_copy_to_clipboard')}
|
||||
</MenuBarSmallItem>
|
||||
<MenuBarSmallItem className={s(styles, { button: true })} title={translate('ui_close')} onClick={onClose}>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className='tw:flex-none'
|
||||
variant='ghost'
|
||||
size='small'
|
||||
title={translate('ui_close')}
|
||||
onClick={onClose}
|
||||
>
|
||||
{translate('ui_close')}
|
||||
</MenuBarSmallItem>
|
||||
</Button>
|
||||
</Container>
|
||||
</Container>
|
||||
<div className={s(styles, { contentWrapper: true })}>
|
||||
|
||||
@@ -42,11 +42,4 @@
|
||||
.timestamp {
|
||||
width: 116px;
|
||||
min-width: 116px;
|
||||
}
|
||||
|
||||
.clearButton {
|
||||
position: absolute !important;
|
||||
top: 4px;
|
||||
right: 16px;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
@@ -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<Props>(function LogViewerTable({ items, s
|
||||
|
||||
return (
|
||||
<Container className={s(style, { wrapper: true }, className)}>
|
||||
<MenuBarSmallItem
|
||||
className={s(style, { clearButton: true })}
|
||||
icon="trash"
|
||||
viewBox="0 0 24 24"
|
||||
<Button
|
||||
className='tw:max-w-max tw:!absolute tw:top-1 tw:right-4 tw:z-[100]'
|
||||
variant='ghost'
|
||||
size='small'
|
||||
title={translate('plugin_log_viewer_clear_log')}
|
||||
onClick={onClear}
|
||||
>
|
||||
<ButtonIcon placement="start">
|
||||
<Icon className='tw:w-4' name="trash" viewBox="0 0 24 24" />
|
||||
</ButtonIcon>
|
||||
{translate('ui_clear')}
|
||||
</MenuBarSmallItem>
|
||||
</Button>
|
||||
<Container className={s(style, { tableWrapper: true })} overflow>
|
||||
<Table className={s(style, { table: true })}>
|
||||
<TableHeader fixed>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"path": "../../common-react/@dbeaver/react-tests"
|
||||
},
|
||||
{
|
||||
"path": "../../common-react/@dbeaver/ui-kit"
|
||||
},
|
||||
{
|
||||
"path": "../../common-typescript/@dbeaver/cli"
|
||||
},
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
"mobx-react-lite": "^4",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"reakit": "^1",
|
||||
"tslib": "^2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -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<TabStateReturn | undefined>(undefined);
|
||||
@@ -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<ISqlEditorModeProps>
|
||||
{data.isIncomingChanges && (
|
||||
<>
|
||||
<ReactCodemirrorPanel className={styles['reactCodemirrorPanel']} top>
|
||||
<MenuBarSmallItem title={translate('plugin_sql_editor_new_merge_conflict_keep_current_tooltip')} onClick={keepCurrent}>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
title={translate('plugin_sql_editor_new_merge_conflict_keep_current_tooltip')}
|
||||
onClick={keepCurrent}
|
||||
>
|
||||
{translate('plugin_sql_editor_new_merge_conflict_keep_current_label')}
|
||||
</MenuBarSmallItem>
|
||||
</Button>
|
||||
</ReactCodemirrorPanel>
|
||||
<ReactCodemirrorPanel className={styles['reactCodemirrorPanel']} top incomingView>
|
||||
<MenuBarSmallItem title={translate('plugin_sql_editor_new_merge_conflict_accept_incoming_tooltip')} onClick={applyIncoming}>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
title={translate('plugin_sql_editor_new_merge_conflict_accept_incoming_tooltip')}
|
||||
onClick={applyIncoming}
|
||||
>
|
||||
{translate('plugin_sql_editor_new_merge_conflict_accept_incoming_label')}
|
||||
</MenuBarSmallItem>
|
||||
</Button>
|
||||
</ReactCodemirrorPanel>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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<Props>(function ExecutionPlanTree
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
<TextPlaceholder>{translate('sql_execution_plan_placeholder')}</TextPlaceholder>
|
||||
<TextPlaceholder>{translate('plugin_sql_execution_plan_placeholder')}</TextPlaceholder>
|
||||
)}
|
||||
</Pane>
|
||||
<ResizerControls className={styles['resizerControls']} />
|
||||
|
||||
@@ -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<ISqlExecutionPlanViewProps>(function ExecutionPlanTreeView({
|
||||
nodes,
|
||||
query,
|
||||
onNodeSelect,
|
||||
}) {
|
||||
return <ExecutionPlanTreeBlock nodeList={nodes} query={query} onNodeSelect={onNodeSelect} />;
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Props>(function PropertiesPanel({ select
|
||||
).get();
|
||||
|
||||
if (!general.length && !details.length) {
|
||||
return <TextPlaceholder>{translate('sql_execution_plan_properties_panel_placeholder')}</TextPlaceholder>;
|
||||
return <TextPlaceholder>{translate('plugin_sql_execution_plan_properties_panel_placeholder')}</TextPlaceholder>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<Table className={className}>
|
||||
@@ -63,8 +63,12 @@ export const PropertiesPanel = observer<Props>(function PropertiesPanel({ select
|
||||
<TableColumnHeader title={valueColumnTitle}>{valueColumnTitle}</TableColumnHeader>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{!!general.length && <PropertiesPanelItemsGroup properties={general} name={translate('sql_execution_plan_properties_panel_general')} />}
|
||||
{!!details.length && <PropertiesPanelItemsGroup properties={details} name={translate('sql_execution_plan_properties_panel_details')} />}
|
||||
{!!general.length && (
|
||||
<PropertiesPanelItemsGroup properties={general} name={translate('plugin_sql_execution_plan_properties_panel_general')} />
|
||||
)}
|
||||
{!!details.length && (
|
||||
<PropertiesPanelItemsGroup properties={details} name={translate('plugin_sql_execution_plan_properties_panel_details')} />
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Props>(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<string | null>(null);
|
||||
const splitState = useSplitUserState('execution-plan');
|
||||
@@ -35,7 +37,17 @@ export const SqlExecutionPlanPanel = observer<Props>(function SqlExecutionPlanPa
|
||||
return (
|
||||
<Split {...splitState} mode={selectedNode ? splitState.mode : 'minimize'} disable={!selectedNode} sticky={30}>
|
||||
<Pane className={s(styles, { pane: true })}>
|
||||
<ExecutionPlanTreeBlock nodeList={data.executionPlan.nodes} query={data.executionPlan.query} onNodeSelect={setSelectedNode} />
|
||||
<TabsState
|
||||
container={sqlExecutionPlanViewService.tabs}
|
||||
nodes={data.executionPlan.nodes}
|
||||
query={data.executionPlan.query}
|
||||
selectedNode={selectedNode}
|
||||
lazy
|
||||
onNodeSelect={setSelectedNode}
|
||||
>
|
||||
<TabList underline />
|
||||
<TabPanelList />
|
||||
</TabsState>
|
||||
</Pane>
|
||||
<ResizerControls />
|
||||
<Pane className={s(styles, { pane: true })} basis="30%" main>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<ISqlExecutionPlanViewProps>;
|
||||
|
||||
constructor() {
|
||||
this.tabs = new TabsContainer('Execution Plan Views');
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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'],
|
||||
[
|
||||
|
||||
@@ -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 ?"],
|
||||
|
||||
@@ -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.'],
|
||||
|
||||
@@ -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', 'Текущий скрипт будет перезаписан загруженным. Сохранить текущий?'],
|
||||
|
||||
@@ -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'],
|
||||
[
|
||||
|
||||
@@ -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', '您当前的脚本将被上传的脚本覆盖。要先保存吗?'],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||