mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into devel
This commit is contained in:
@@ -248,6 +248,7 @@ const defaultColumnDef: ColDef = {
|
||||
|
||||
export const INDEX_COLUMN_DEF: ColDef = {
|
||||
headerName: '#',
|
||||
colId: `${Number.MAX_SAFE_INTEGER}`,
|
||||
field: `${Number.MAX_SAFE_INTEGER}`,
|
||||
valueGetter: 'node.id',
|
||||
width: 70,
|
||||
|
||||
+2
-2
@@ -70,7 +70,7 @@ const headerStyles = css`
|
||||
`;
|
||||
|
||||
export interface IAgColumnClickEvent extends AgEvent{
|
||||
columnIndex: number;
|
||||
columnId: string;
|
||||
isMultiple: boolean;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export function TableColumnHeader(props: HeaderProps) {
|
||||
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const event: IAgColumnClickEvent = Object.freeze({
|
||||
type: COLUMN_CLICK_EVENT_TYPE,
|
||||
columnIndex: parseInt(props.column.getColId()),
|
||||
columnId: props.column.getColId(),
|
||||
isMultiple: e.ctrlKey,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,31 +7,31 @@
|
||||
*/
|
||||
|
||||
export class RowSelection {
|
||||
columns = new Set<number>()
|
||||
columns = new Set<string>()
|
||||
constructor(readonly rowId: number) {}
|
||||
|
||||
add(columnIndexList: number[]) {
|
||||
add(columnIndexList: string[]) {
|
||||
for (const columnId of columnIndexList) {
|
||||
this.columns.add(columnId);
|
||||
}
|
||||
}
|
||||
|
||||
remove(columnIndexList: number[]) {
|
||||
remove(columnIndexList: string[]) {
|
||||
for (const columnId of columnIndexList) {
|
||||
this.columns.delete(columnId);
|
||||
}
|
||||
}
|
||||
|
||||
replace(columnIndexList: number[]) {
|
||||
replace(columnIndexList: string[]) {
|
||||
this.columns.clear();
|
||||
this.add(columnIndexList);
|
||||
}
|
||||
|
||||
isSelected(columnIndex: number) {
|
||||
isSelected(columnIndex: string) {
|
||||
return this.columns.has(columnIndex);
|
||||
}
|
||||
|
||||
isRangeSelected(columnIndexList: number[]) {
|
||||
isRangeSelected(columnIndexList: string[]) {
|
||||
return columnIndexList.every(column => this.columns.has(column));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,28 +22,28 @@ export class TableSelection {
|
||||
this.selectedMap.clear();
|
||||
}
|
||||
|
||||
selectCell(rowId: number, columnIndex: number, isMultiple: boolean, ignoreSelected = false) {
|
||||
selectCell(rowId: number, columnId: string, isMultiple: boolean, ignoreSelected = false) {
|
||||
if (!isMultiple) {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
const rowSelection = this.selectedMap.get(rowId);
|
||||
|
||||
if (!ignoreSelected && rowSelection?.isSelected(columnIndex)) {
|
||||
if (!ignoreSelected && rowSelection?.isSelected(columnId)) {
|
||||
this.unselectColumn(
|
||||
this.selectedMap,
|
||||
rowSelection,
|
||||
[columnIndex]
|
||||
[columnId]
|
||||
);
|
||||
} else {
|
||||
this.selectColumn(rowId, rowSelection, [columnIndex]);
|
||||
this.selectColumn(rowId, rowSelection, [columnId]);
|
||||
}
|
||||
}
|
||||
|
||||
selectRange(
|
||||
startPosition: number,
|
||||
endPosition: number,
|
||||
columns: number[],
|
||||
columns: string[],
|
||||
isMultiple: boolean
|
||||
) {
|
||||
const firstRow = Math.min(startPosition, endPosition);
|
||||
@@ -70,13 +70,13 @@ export class TableSelection {
|
||||
}
|
||||
}
|
||||
|
||||
isCellSelected(rowId: number, columnIndex: number): boolean {
|
||||
isCellSelected(rowId: number, columnId: string): boolean {
|
||||
const current = this.selectedMap.get(rowId);
|
||||
|
||||
return current?.isSelected(columnIndex) || false;
|
||||
return current?.isSelected(columnId) || false;
|
||||
}
|
||||
|
||||
isRangeSelected(startPosition: number, endPosition: number, columns: number[]): boolean {
|
||||
isRangeSelected(startPosition: number, endPosition: number, columns: string[]): boolean {
|
||||
const start = Math.min(startPosition, endPosition);
|
||||
const end = Math.max(startPosition, endPosition);
|
||||
|
||||
@@ -89,19 +89,19 @@ export class TableSelection {
|
||||
return true;
|
||||
}
|
||||
|
||||
private unselectColumn(source: Map<number, RowSelection>, rowSelection: RowSelection, columnIndexList: number[]) {
|
||||
rowSelection.remove(columnIndexList);
|
||||
private unselectColumn(source: Map<number, RowSelection>, rowSelection: RowSelection, columns: string[]) {
|
||||
rowSelection.remove(columns);
|
||||
if (rowSelection.columns.size === 0) {
|
||||
source.delete(rowSelection.rowId);
|
||||
}
|
||||
}
|
||||
|
||||
private selectColumn(rowId: number, rowSelection: RowSelection | undefined = undefined, columnIndexList: number[]) {
|
||||
private selectColumn(rowId: number, rowSelection: RowSelection | undefined = undefined, columns: string[]) {
|
||||
if (!rowSelection) {
|
||||
rowSelection = new RowSelection(rowId);
|
||||
this.selectedMap.set(rowId, rowSelection);
|
||||
}
|
||||
|
||||
rowSelection.add(columnIndexList);
|
||||
rowSelection.add(columns);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ const EVENT_KEY_CODE = {
|
||||
interface TemporarySelectionRange {
|
||||
firstRow: number;
|
||||
lastRow: number;
|
||||
columns: number[];
|
||||
columns: string[];
|
||||
isMultiple: boolean;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export class RangeController implements IRangeController {
|
||||
|
||||
private copySelectedData() {
|
||||
const selectedRows = this.selection!.getSelectedRows();
|
||||
const selectedColumns = new Set<number>();
|
||||
const selectedColumns = new Set<string>();
|
||||
|
||||
for (const selectedRow of selectedRows) {
|
||||
for (const columnId of selectedRow.columns) {
|
||||
@@ -120,7 +120,7 @@ export class RangeController implements IRangeController {
|
||||
|
||||
const columns = this.columnController
|
||||
.getAllDisplayedColumns()
|
||||
.filter(column => selectedColumns.has(parseInt(column.getColId())));
|
||||
.filter(column => selectedColumns.has(column.getColId()));
|
||||
|
||||
let data = '';
|
||||
for (const selectedRow of selectedRows) {
|
||||
@@ -128,7 +128,7 @@ export class RangeController implements IRangeController {
|
||||
if (column !== columns[0]) {
|
||||
data += '\t';
|
||||
}
|
||||
if (selectedRow.isSelected(parseInt(column.getColId()))) {
|
||||
if (selectedRow.isSelected(column.getColId())) {
|
||||
const rowNode = this.rowPositionUtils.getRowNode({
|
||||
rowIndex: selectedRow.rowId,
|
||||
rowPinned: undefined,
|
||||
@@ -164,7 +164,7 @@ export class RangeController implements IRangeController {
|
||||
return;
|
||||
}
|
||||
const lastRowId = this.gridApi.getInfiniteRowCount() - 1 - (this.rowModel.isLastRowFound() ? 0 : 1);
|
||||
if (event.columnIndex === parseInt(INDEX_COLUMN_DEF.field!)) {
|
||||
if (event.columnId === INDEX_COLUMN_DEF.colId!) {
|
||||
this.selection.selectRange(
|
||||
0,
|
||||
lastRowId,
|
||||
@@ -172,7 +172,7 @@ export class RangeController implements IRangeController {
|
||||
event.isMultiple
|
||||
);
|
||||
} else {
|
||||
this.selection.selectRange(0, lastRowId, [event.columnIndex], event.isMultiple);
|
||||
this.selection.selectRange(0, lastRowId, [event.columnId], event.isMultiple);
|
||||
}
|
||||
this.restoreFocus();
|
||||
this.dispatchChangedEvent(false, true);
|
||||
@@ -183,7 +183,7 @@ export class RangeController implements IRangeController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.column.getColId() === INDEX_COLUMN_DEF.field) {
|
||||
if (event.column.getColId() === INDEX_COLUMN_DEF.colId) {
|
||||
this.selection.selectRange(
|
||||
event.rowIndex,
|
||||
event.rowIndex,
|
||||
@@ -201,16 +201,16 @@ export class RangeController implements IRangeController {
|
||||
this.lastSelectedCell = cell;
|
||||
this.lastFocus = cell;
|
||||
if (!this.gridOptionsWrapper.isEnableRangeSelection()
|
||||
|| cell.column.getColDef().field === INDEX_COLUMN_DEF.field) {
|
||||
|| cell.column.getColId() === INDEX_COLUMN_DEF.colId) {
|
||||
return;
|
||||
}
|
||||
this.selection.selectCell(cell.rowIndex, parseInt(cell.column.getColId()), appendRange);
|
||||
this.selection.selectCell(cell.rowIndex, cell.column.getColId(), appendRange);
|
||||
this.dispatchChangedEvent(false, true);
|
||||
}
|
||||
|
||||
getCellRangeCount(cell: CellPosition): number {
|
||||
const columnIndex = parseInt(cell.column.getColId());
|
||||
if (this.isCellInTemporaryRange(cell.rowIndex, columnIndex)) {
|
||||
const columnId = cell.column.getColId();
|
||||
if (this.isCellInTemporaryRange(cell.rowIndex, columnId)) {
|
||||
const isRangeSelected = this.selection?.isRangeSelected(
|
||||
this.temporaryRange!.firstRow,
|
||||
this.temporaryRange!.lastRow,
|
||||
@@ -218,7 +218,7 @@ export class RangeController implements IRangeController {
|
||||
);
|
||||
return isRangeSelected ? 0 : 1;
|
||||
}
|
||||
if (this.selection?.isCellSelected(cell.rowIndex, parseInt(cell.column.getColId()))) {
|
||||
if (this.selection?.isCellSelected(cell.rowIndex, cell.column.getColId())) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ export class RangeController implements IRangeController {
|
||||
const startRow = this.lastSelectedCell !== undefined ? this.lastSelectedCell.rowIndex : position.rowIndex;
|
||||
const endRow = position.rowIndex;
|
||||
let columns = this.getColumnsBetween((this.lastSelectedCell || position).column, position.column);
|
||||
const isRowsSelection = columns.includes(parseInt(INDEX_COLUMN_DEF.field!));
|
||||
const isRowsSelection = columns.includes(INDEX_COLUMN_DEF.colId!);
|
||||
|
||||
if (isRowsSelection) {
|
||||
columns = this.getColumnsWithoutIndex();
|
||||
@@ -293,9 +293,9 @@ export class RangeController implements IRangeController {
|
||||
this.eventService.dispatchEvent(event);
|
||||
}
|
||||
|
||||
private getColumnsBetween(firstColumn: Column, secondColumn: Column): number[] {
|
||||
private getColumnsBetween(firstColumn: Column, secondColumn: Column): string[] {
|
||||
if (firstColumn === secondColumn) {
|
||||
return [parseInt(firstColumn.getColId())];
|
||||
return [firstColumn.getColId()];
|
||||
}
|
||||
|
||||
const columns = this.columnController.getAllDisplayedColumns();
|
||||
@@ -309,14 +309,14 @@ export class RangeController implements IRangeController {
|
||||
const firstIndex = Math.min(firstOffset, secondOffset);
|
||||
const lastIndex = Math.max(firstOffset, secondOffset);
|
||||
|
||||
return columns.slice(firstIndex, lastIndex + 1).map(column => parseInt(column.getColId()));
|
||||
return columns.slice(firstIndex, lastIndex + 1).map(column => column.getColId());
|
||||
}
|
||||
|
||||
private getColumnsWithoutIndex() {
|
||||
return this.columnController
|
||||
.getAllDisplayedColumns()
|
||||
.map(column => parseInt(column.getColId()))
|
||||
.filter(column => column !== parseInt(INDEX_COLUMN_DEF.field!));
|
||||
.map(column => column.getColId())
|
||||
.filter(column => column !== INDEX_COLUMN_DEF.colId!);
|
||||
}
|
||||
|
||||
getRangeStartRow(cellRange: CellRange): RowPosition {
|
||||
@@ -338,10 +338,10 @@ export class RangeController implements IRangeController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (startDraggingCell.column.getColId() !== INDEX_COLUMN_DEF.field) {
|
||||
if (startDraggingCell.column.getColId() !== INDEX_COLUMN_DEF.colId) {
|
||||
this.selection.selectCell(
|
||||
startDraggingCell.rowIndex,
|
||||
parseInt(startDraggingCell.column.getColId()),
|
||||
startDraggingCell.column.getColId(),
|
||||
true,
|
||||
true
|
||||
);
|
||||
@@ -404,7 +404,7 @@ export class RangeController implements IRangeController {
|
||||
}
|
||||
|
||||
let columns = this.getColumnsBetween(this.startDraggingCell.column, this.endDraggingCell.column);
|
||||
const isRowsSelection = columns.includes(parseInt(INDEX_COLUMN_DEF.field!));
|
||||
const isRowsSelection = columns.includes(INDEX_COLUMN_DEF.colId!);
|
||||
const startRow = this.startDraggingCell.rowIndex;
|
||||
const endRow = this.endDraggingCell.rowIndex;
|
||||
|
||||
@@ -438,7 +438,7 @@ export class RangeController implements IRangeController {
|
||||
this.temporaryRange = null;
|
||||
}
|
||||
|
||||
private isCellInTemporaryRange(rowId: number, column: number) {
|
||||
private isCellInTemporaryRange(rowId: number, column: string) {
|
||||
if (!this.temporaryRange
|
||||
|| this.temporaryRange.firstRow > rowId || this.temporaryRange.lastRow < rowId
|
||||
|| !this.temporaryRange.columns.includes(column)
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@ export class ConnectionSchemaManagerService {
|
||||
try {
|
||||
await this.connectionsManagerService.loadConnectionInfoAsync(connectionId);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load connection info');
|
||||
this.notificationService.logException(exception, 'Can\'t load connection info', true);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ export class ConnectionSelectorController {
|
||||
}
|
||||
|
||||
get isConnectionSelectorVisible() {
|
||||
return !!this.connectionSelectorService.currentConnectionId;
|
||||
return !!this.currentConnection;
|
||||
}
|
||||
get isObjectContainerSelectorVisible() {
|
||||
return !!this.connectionSelectorService.currentObjectCatalogId
|
||||
|
||||
+10
-5
@@ -173,22 +173,27 @@ export class ConnectionsManagerService {
|
||||
}
|
||||
|
||||
async closeNavNodeConnectionAsync(navNodeId: string): Promise<void> {
|
||||
const node = this.navNodeManagerService.getNode(navNodeId);
|
||||
if (!node) {
|
||||
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(navNodeId);
|
||||
const connection = this.getConnectionById(connectionId);
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(navNodeId);
|
||||
await this.graphQLService.gql.closeConnection({ id: connectionId });
|
||||
await this.afterConnectionClose(connectionId);
|
||||
await this.connectionInfo.refresh(true, { connectionId, close: true });
|
||||
|
||||
if (node.objectFeatures.includes('dataSourceTemporary')) {
|
||||
if (connection.features.includes(EConnectionFeature.temporary)) {
|
||||
const node = this.navNodeManagerService.getNode(navNodeId);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
await this.navNodeManagerService.refreshTree(node.parentId);
|
||||
} else {
|
||||
await this.navNodeManagerService.refreshNode(navNodeId);
|
||||
await this.navNodeManagerService.removeTree(navNodeId);
|
||||
}
|
||||
await this.navNodeManagerService.removeTree(navNodeId);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, `Can't close connection: ${navNodeId}`);
|
||||
}
|
||||
|
||||
@@ -20,22 +20,28 @@ import { useStyles } from '@dbeaver/core/theming';
|
||||
import { EditorDialog } from './EditorDialog';
|
||||
import { InlineEditorStyles } from './styles';
|
||||
|
||||
export type InlineEditorControls = 'right' | 'top' | 'bottom'
|
||||
export type InlineEditorControls = 'right' | 'top' | 'bottom' | 'inside'
|
||||
|
||||
export type InlineEditorProps = {
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
controlsPosition?: InlineEditorControls;
|
||||
simple?: boolean;
|
||||
onChange(value: string): void;
|
||||
onSave(): void;
|
||||
onReject(): void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const InlineEditor = observer(function InlineEditor({
|
||||
value,
|
||||
placeholder,
|
||||
controlsPosition = 'right',
|
||||
simple,
|
||||
onChange,
|
||||
onSave,
|
||||
onReject,
|
||||
className,
|
||||
}: InlineEditorProps) {
|
||||
const commonDialogService = useService(CommonDialogService);
|
||||
|
||||
@@ -66,14 +72,21 @@ export const InlineEditor = observer(function InlineEditor({
|
||||
}, []);
|
||||
|
||||
return styled(useStyles(InlineEditorStyles))(
|
||||
<editor as="div">
|
||||
<editor as="div" className={className}>
|
||||
<editor-container as="div">
|
||||
<input type="text" value={value} onChange={handleChange} onKeyDown={handleKeyDown} ref={inputRef}/>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
ref={inputRef}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</editor-container>
|
||||
<editor-actions as="div" {...use({ position: controlsPosition })}>
|
||||
<editor-action as="div" onClick={onSave}><Icon name="apply" viewBox="0 0 12 10" /></editor-action>
|
||||
<editor-action as="div" onClick={onReject}><Icon name="reject" viewBox="0 0 11 11" /></editor-action>
|
||||
<editor-action as="div" onClick={handlePopup}><Icon name="edit" viewBox="0 0 13 13" /></editor-action>
|
||||
{!simple && <editor-action as="div" onClick={handlePopup}><Icon name="edit" viewBox="0 0 13 13" /></editor-action>}
|
||||
</editor-actions>
|
||||
</editor>
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ export const InlineEditorStyles = composes(
|
||||
editor {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: solid 1px #01cca3;
|
||||
@@ -36,6 +37,7 @@ export const InlineEditorStyles = composes(
|
||||
editor-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
|
||||
& input {
|
||||
color: inherit;
|
||||
@@ -66,6 +68,14 @@ export const InlineEditorStyles = composes(
|
||||
border-bottom-color: #01cca3 !important;
|
||||
}
|
||||
|
||||
editor-actions[|position=inside] {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
editor-actions[|position=bottom],
|
||||
editor-actions[|position=top] {
|
||||
right: -1px;
|
||||
|
||||
+18
-15
@@ -10,6 +10,7 @@ import { observer } from 'mobx-react';
|
||||
import { useCallback } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { InlineEditor } from '@dbeaver/core/app';
|
||||
import { InputField, IconButton, SubmittingForm } from '@dbeaver/core/blocks';
|
||||
import { useTranslate } from '@dbeaver/core/localization';
|
||||
import { composes, useStyles } from '@dbeaver/core/theming';
|
||||
@@ -18,8 +19,8 @@ import { TableViewerModel } from '../TableViewerModel';
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
IconButton {
|
||||
composes: theme-text-primary theme-ripple from global;
|
||||
InlineEditor {
|
||||
composes: theme-background-surface theme-text-on-surface from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
@@ -29,15 +30,10 @@ const styles = composes(
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
InputField {
|
||||
InlineEditor {
|
||||
flex: 1;
|
||||
}
|
||||
IconButton {
|
||||
margin-right: 18px;
|
||||
position: relative;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: block;
|
||||
margin: 0 12px;
|
||||
}
|
||||
`
|
||||
);
|
||||
@@ -54,18 +50,25 @@ export const TableWhereFilter = observer(function TableWhereFilter({
|
||||
(value: string) => context.setQueryWhereFilter(value),
|
||||
[context]
|
||||
);
|
||||
const resetFilter = useCallback(
|
||||
() => {
|
||||
context.setQueryWhereFilter('');
|
||||
context.applyQueryFilters();
|
||||
},
|
||||
[context]
|
||||
);
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<SubmittingForm onSubmit={() => context.applyQueryFilters()}>
|
||||
<InputField
|
||||
type='text'
|
||||
name='whereFilter'
|
||||
placeholder={translate('table_header_sql_expression')}
|
||||
<InlineEditor
|
||||
value={context.getQueryWhereFilter() || ''}
|
||||
onSave={() => context.applyQueryFilters()}
|
||||
onReject={resetFilter}
|
||||
onChange={handleChange}
|
||||
mod='surface'
|
||||
placeholder={translate('table_header_sql_expression')}
|
||||
controlsPosition='inside'
|
||||
simple
|
||||
/>
|
||||
<IconButton name='apply' viewBox='' onClick={() => context.applyQueryFilters()}/>
|
||||
</SubmittingForm>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -165,7 +165,7 @@ export class ObjectViewerTabService {
|
||||
const nodeInfo = this.navNodeManagerService
|
||||
.getNodeContainerInfo(context.handlerState.objectId);
|
||||
|
||||
if (nodeInfo.catalogId) {
|
||||
if (!nodeInfo.catalogId) {
|
||||
return;
|
||||
}
|
||||
return nodeInfo.catalogId;
|
||||
|
||||
Reference in New Issue
Block a user