Merge branch 'devel' into 5645-cb-sql-editor---ctrlz-during-autosave-scrolls-the-script-to-the-top

This commit is contained in:
Evgenia
2025-06-17 13:13:07 +02:00
committed by GitHub
61 changed files with 1589 additions and 1207 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -15,7 +15,7 @@
Root;
}
.mlln6zg7-0-0-beta-50 {
.mlln6zg7-0-0-beta-55 {
@layer rdg.MeasuringCell {
contain: strict;
grid-row: 1;
@@ -24,7 +24,7 @@
}
.cj343x07-0-0-beta-50 {
.cj343x07-0-0-beta-55 {
@layer rdg.Cell {
/* max-content does not work with size containment
* dynamically switching between different containment styles incurs a heavy relayout penalty
@@ -53,7 +53,7 @@
}
}
.csofj7r7-0-0-beta-50 {
.csofj7r7-0-0-beta-55 {
@layer rdg.Cell {
position: sticky;
/* Should have a higher value than 0 to show up above unfrozen cells */
@@ -66,8 +66,33 @@
}
}
.ch2wcw87-0-0-beta-55 {
@layer rdg.DragHandle {
--rdg-drag-handle-size: 8px;
z-index: 0;
cursor: move;
inline-size: var(--rdg-drag-handle-size);
block-size: var(--rdg-drag-handle-size);
background-color: var(--rdg-selection-color);
place-self: end;
.c1bn88vv7-0-0-beta-50 {
&:hover {
--rdg-drag-handle-size: 16px;
border: 2px solid var(--rdg-selection-color);
background-color: var(--rdg-background-color);
}
}
}
.c1wvphzh7-0-0-beta-55 {
@layer rdg.DragHandle {
z-index: 1;
position: sticky;
}
}
.c1bn88vv7-0-0-beta-55 {
@layer rdg.CheckboxInput {
display: block;
margin: auto;
@@ -86,13 +111,13 @@
}
.g1s9ylgp7-0-0-beta-50 {
.g1s9ylgp7-0-0-beta-55 {
@layer rdg.GroupCellContent {
outline: none;
}
}
.cz54e4y7-0-0-beta-50 {
.cz54e4y7-0-0-beta-55 {
@layer rdg.GroupCellCaret {
margin-inline-start: 4px;
stroke: currentColor;
@@ -107,13 +132,13 @@
}
.h44jtk67-0-0-beta-50 {
.h44jtk67-0-0-beta-55 {
@layer rdg.SortableHeaderCell {
display: flex;
}
}
.hcgkhxz7-0-0-beta-50 {
.hcgkhxz7-0-0-beta-55 {
@layer rdg.SortableHeaderCellName {
flex-grow: 1;
overflow: clip;
@@ -122,69 +147,33 @@
}
.c6ra8a37-0-0-beta-50 {
.c6ra8a37-0-0-beta-55 {
@layer rdg.Cell {
background-color: #ccccff;
}
}
.cq910m07-0-0-beta-50 {
@layer rdg.Cell {
background-color: #ccccff;
&.c6ra8a37-0-0-beta-50 {
background-color: #9999ff;
}
}
}
.c1w9bbhr7-0-0-beta-50 {
@layer rdg.DragHandle {
--rdg-drag-handle-size: 8px;
z-index: 0;
cursor: move;
inline-size: var(--rdg-drag-handle-size);
block-size: var(--rdg-drag-handle-size);
background-color: var(--rdg-selection-color);
place-self: end;
&:hover {
--rdg-drag-handle-size: 16px;
border: 2px solid var(--rdg-selection-color);
background-color: var(--rdg-background-color);
}
}
}
.c1creorc7-0-0-beta-50 {
@layer rdg.DragHandle {
z-index: 1;
position: sticky;
}
}
.cis5rrm7-0-0-beta-50 {
.cis5rrm7-0-0-beta-55 {
@layer rdg.EditCell {
padding: 0;
}
}
.c6l2wv17-0-0-beta-50 {
.c6l2wv17-0-0-beta-55 {
@layer rdg.HeaderCell {
cursor: pointer;
}
}
.c1kqdw7y7-0-0-beta-50 {
.c1kqdw7y7-0-0-beta-55 {
@layer rdg.HeaderCell {
touch-action: none;
}
}
.r1y6ywlx7-0-0-beta-50 {
.r1y6ywlx7-0-0-beta-55 {
@layer rdg.HeaderCell {
cursor: col-resize;
position: absolute;
@@ -195,16 +184,29 @@
}
}
.c1bezg5o7-0-0-beta-50 {
opacity: 0.5;
.c1bezg5o7-0-0-beta-55 {
@layer rdg.HeaderCell {
background-color: var(--rdg-header-draggable-background-color);
}
}
.c1vc96037-0-0-beta-50 {
background-color: var(--rdg-header-draggable-background-color);
.c1vc96037-0-0-beta-55 {
@layer rdg.HeaderCell {
background-color: var(--rdg-header-draggable-background-color);
}
}
.d8rwc9w7-0-0-beta-55 {
@layer rdg.HeaderCell {
border-radius: 4px;
width: fit-content;
outline: 2px solid hsl(207, 100%, 50%);
outline-offset: -2px;
}
}
.r1upfr807-0-0-beta-50 {
.r1upfr807-0-0-beta-55 {
@layer rdg.Row {
display: contents;
background-color: var(--rdg-background-color);
@@ -223,14 +225,14 @@
}
}
.r190mhd37-0-0-beta-50 {
.r190mhd37-0-0-beta-55 {
@layer rdg.FocusSink {
outline: 2px solid var(--rdg-selection-color);
outline-offset: -2px;
}
}
.r139qu9m7-0-0-beta-50 {
.r139qu9m7-0-0-beta-55 {
@layer rdg.FocusSink {
&::before {
content: '';
@@ -244,26 +246,26 @@
}
.h10tskcx7-0-0-beta-50 {
.h10tskcx7-0-0-beta-55 {
@layer rdg.HeaderRow {
display: contents;
background-color: var(--rdg-header-background-color);
font-weight: bold;
& > .cj343x07-0-0-beta-50 {
& > .cj343x07-0-0-beta-55 {
/* Should have a higher value than 1 to show up above regular cells and the focus sink */
z-index: 2;
position: sticky;
}
& > .csofj7r7-0-0-beta-50 {
& > .csofj7r7-0-0-beta-55 {
z-index: 3;
}
}
}
.a3ejtar7-0-0-beta-50 {
.a3ejtar7-0-0-beta-55 {
@layer rdg.SortIcon {
fill: currentColor;
@@ -274,7 +276,7 @@
}
.rnvodz57-0-0-beta-50 {
.rnvodz57-0-0-beta-55 {
@layer rdg.Defaults {
*,
*::before,
@@ -285,7 +287,7 @@
@layer rdg.Root {
--rdg-color: #000; --rdg-border-color: #ddd; --rdg-summary-border-color: #aaa; --rdg-background-color: hsl(0deg 0% 100%); --rdg-header-background-color: hsl(0deg 0% 97.5%); --rdg-header-draggable-background-color: hsl(0deg 0% 90.5%); --rdg-row-hover-background-color: hsl(0deg 0% 96%); --rdg-row-selected-background-color: hsl(207deg 76% 92%); --rdg-row-selected-hover-background-color: hsl(207deg 76% 88%); --rdg-checkbox-focus-color: hsl(207deg 100% 69%);
--rdg-selection-color: #66afe9;
--rdg-selection-color: hsl(207, 75%, 66%);
--rdg-font-size: 14px;
--rdg-cell-frozen-box-shadow: 2px 0 5px -2px rgba(136, 136, 136, 0.3);
@@ -334,30 +336,30 @@
}
> :nth-last-child(1 of .rdg-top-summary-row) {
> .cj343x07-0-0-beta-50 {
> .cj343x07-0-0-beta-55 {
border-block-end: 2px solid var(--rdg-summary-border-color);
}
}
> :nth-child(1 of .rdg-bottom-summary-row) {
> .cj343x07-0-0-beta-50 {
> .cj343x07-0-0-beta-55 {
border-block-start: 2px solid var(--rdg-summary-border-color);
}
}
}
}
.vlqv91k7-0-0-beta-50 {
.vlqv91k7-0-0-beta-55 {
@layer rdg.Root {
user-select: none;
& .r1upfr807-0-0-beta-50 {
& .r1upfr807-0-0-beta-55 {
cursor: move;
}
}
}
.f1lsfrzw7-0-0-beta-50 {
.f1lsfrzw7-0-0-beta-55 {
@layer rdg.FocusSink {
grid-column: 1/-1;
pointer-events: none;
@@ -366,7 +368,7 @@
}
}
.f1cte0lg7-0-0-beta-50 {
.f1cte0lg7-0-0-beta-55 {
@layer rdg.FocusSink {
/* Should have a higher value than 3 to show up above header and summary rows */
z-index: 3;
@@ -374,7 +376,7 @@
}
.s8wc6fl7-0-0-beta-50 {
.s8wc6fl7-0-0-beta-55 {
@layer rdg.SummaryCell {
inset-block-start: var(--rdg-summary-row-top);
inset-block-end: var(--rdg-summary-row-bottom);
@@ -382,42 +384,42 @@
}
.skuhp557-0-0-beta-50 {
.skuhp557-0-0-beta-55 {
@layer rdg.SummaryRow {
> .cj343x07-0-0-beta-50 {
> .cj343x07-0-0-beta-55 {
position: sticky;
}
}
}
.tf8l5ub7-0-0-beta-50 {
.tf8l5ub7-0-0-beta-55 {
@layer rdg.SummaryRow {
> .cj343x07-0-0-beta-50 {
> .cj343x07-0-0-beta-55 {
z-index: 2;
}
> .csofj7r7-0-0-beta-50 {
> .csofj7r7-0-0-beta-55 {
z-index: 3;
}
}
}
.g1yxluv37-0-0-beta-50 {
.g1yxluv37-0-0-beta-55 {
@layer rdg.GroupedRow {
&:not([aria-selected='true']) {
background-color: var(--rdg-header-background-color);
}
> .cj343x07-0-0-beta-50:not(:last-child, .csofj7r7-0-0-beta-50),
> :nth-last-child(n + 2 of .csofj7r7-0-0-beta-50) {
> .cj343x07-0-0-beta-55:not(:last-child, .csofj7r7-0-0-beta-55),
> :nth-last-child(n + 2 of .csofj7r7-0-0-beta-55) {
border-inline-end: none;
}
}
}
.t7vyx3i7-0-0-beta-50 {
.t7vyx3i7-0-0-beta-55 {
@layer rdg.TextEditor {
appearance: none;
File diff suppressed because one or more lines are too long
@@ -1,17 +1,17 @@
{
"name": "react-data-grid",
"version": "7.0.0-beta.50",
"version": "7.0.0-beta.55",
"type": "module",
"exports": {
"./lib/styles.css": "./lib/styles.css",
".": {
"types": "./lib/index.d.ts",
"default": "./lib/bundle.js"
"default": "./lib/index.js"
}
},
"browser": "./lib/bundle.js",
"main": "./lib/bundle.js",
"module": "./lib/bundle.js",
"browser": "./lib/index.js",
"main": "./lib/index.js",
"module": "./lib/index.js",
"types": "./lib/index.d.ts",
"files": [
"lib"
@@ -15,13 +15,18 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
const tooltip = useGridReactiveValue(cellContext?.cellTooltip, rowIdx, dataColIdx);
function handleClick(event: React.MouseEvent<HTMLDivElement>) {
props.onClick?.(
props.onCellClick?.(
{
rowIdx,
row: props.row,
column: props.column,
selectCell(enableEditor) {
props.selectCell({ rowIdx, idx: virtualColIdx }, enableEditor);
props.selectCell(
{ rowIdx, idx: virtualColIdx },
{
enableEditor,
},
);
},
},
createCellMouseEvent(event),
@@ -29,13 +34,13 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
}
function handleDoubleClick(event: React.MouseEvent<HTMLDivElement>) {
props.onDoubleClick?.(
props.onCellDoubleClick?.(
{
rowIdx,
row: props.row,
column: props.column,
selectCell(enableEditor) {
props.selectCell({ rowIdx, idx: virtualColIdx }, enableEditor);
props.selectCell({ rowIdx, idx: virtualColIdx }, { enableEditor });
},
},
createCellMouseEvent(event),
@@ -43,13 +48,13 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
}
function handleContextMenu(event: React.MouseEvent<HTMLDivElement>) {
props.onContextMenu?.(
props.onCellContextMenu?.(
{
rowIdx,
row: props.row,
column: props.column,
selectCell(enableEditor) {
props.selectCell({ rowIdx, idx: virtualColIdx }, enableEditor);
props.selectCell({ rowIdx, idx: virtualColIdx }, { enableEditor });
},
},
createCellMouseEvent(event),
@@ -57,7 +62,16 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
}
const mappedProps = useMemo(
() => ({ ...props, isFocused: props.isCellSelected, onClick: handleClick, onDoubleClick: handleDoubleClick, onContextMenu: handleContextMenu }),
() => ({
...props,
onCellClick: undefined,
onCellContextMenu: undefined,
onCellDoubleClick: undefined,
isFocused: props.isCellSelected,
onClick: handleClick,
onDoubleClick: handleDoubleClick,
onContextMenu: handleContextMenu,
}),
Object.values(props),
);
@@ -68,9 +82,9 @@ export const BaseCell = memo(function BaseCell<TRow, TSummaryRow>(props: CellRen
<Cell
{...props}
title={tooltip}
onClick={(_, event) => onClick?.(event)}
onDoubleClick={(_, event) => onDoubleClick?.(event)}
onContextMenu={(_, event) => onContextMenu?.(event)}
onCellClick={(_, event) => onClick?.(event)}
onCellDoubleClick={(_, event) => onDoubleClick?.(event)}
onCellContextMenu={(_, event) => onContextMenu?.(event)}
{...rest}
isCellSelected={props.isCellSelected || rest.isFocused || false}
/>
@@ -1,5 +1,5 @@
import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
import { DataGrid as DataGridBase, type ColumnOrColumnGroup, type CellSelectArgs, type DataGridHandle } from 'react-data-grid';
import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { DataGrid as DataGridBase, type ColumnOrColumnGroup, type CellSelectArgs, type DataGridHandle, type ColumnWidth, type ColumnWidths } from 'react-data-grid';
import { rowRenderer } from './renderers/rowRenderer.js';
import { cellRenderer } from './renderers/cellRenderer.js';
import { DataGridCellHeaderContext, type IDataGridHeaderCellContext } from './DataGridHeaderCellContext.js';
@@ -76,10 +76,10 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
},
ref,
) {
const [columnWidths, setColumnWidths] = useState<ColumnWidths>(() => new Map<string, ColumnWidth>());
const rowsCount = useGridReactiveValue(rowCount);
const columnsCount = useGridReactiveValue(columnCount);
const gridKey = useRef(0);
const rowsCountRef = useRef(rowsCount);
const innerGridRef = useRef<DataGridHandle>(null);
const columns = new Array<ColumnOrColumnGroup<IInnerRow, unknown>>(columnsCount)
@@ -110,7 +110,9 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
innerGridRef.current?.scrollToCell({ idx: position.colIdx && dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx });
},
openEditor: (position: ICellPosition) => {
innerGridRef.current?.selectCell({ idx: dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx }, true);
innerGridRef.current?.selectCell({ idx: dndHeaderContext.getDataColIdx(position.colIdx), rowIdx: position.rowIdx }, {
enableEditor: true,
});
},
}));
@@ -119,12 +121,11 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
rowsCountRef.current = rowsCount;
if (previousRowCount === 0) {
// we trigger columns size recalculation when rows are added
gridKey.current++;
setColumnWidths(new Map<string, ColumnWidth>());
}
}
const rows = useMemo(
let rows = useMemo(
() =>
new Array<IInnerRow>(rowsCount).fill({ idx: 0 }).map((_, i) => ({
idx: i,
@@ -140,29 +141,36 @@ export const DataGrid = forwardRef<DataGridRef, DataGridProps>(function DataGrid
onCellKeyDown?.({ colIdx: dndHeaderContext.getDataColIdx(args.column.idx), rowIdx: args.rowIdx }, event);
}
const isMeasurementRender = columnWidths.size === 0 && columnCount.get() > 0;
if (isMeasurementRender) {
rows = rows.slice(0, 100);
}
return (
<HeaderDnDContext value={dndHeaderContext}>
<DataGridRowContext value={{ rowCount, onScrollToBottom }}>
<DataGridCellContext value={{ cell, cellText, cellElement, cellTooltip, onCellChange }}>
<DataGridCellHeaderContext value={{ headerElement, headerText, getHeaderDnD, columnSortable, onColumnSort, columnSortingState }}>
<DataGridBase
key={gridKey.current}
ref={innerGridRef}
columns={dndHeaderContext.columns}
enableVirtualization={!isMeasurementRender}
rows={rows}
className={className}
headerRowHeight={getHeaderHeight?.()}
onScroll={onScroll}
rowHeight={getRowHeight ? row => getRowHeight(row.idx) : undefined}
rowKeyGetter={getRowId ? row => getRowId(row.idx) : undefined}
onSelectedCellChange={handleCellFocus}
onCellKeyDown={handleCellKeyDown}
columnWidths={columnWidths}
renderers={{
renderRow: rowRenderer,
renderCell: cellRenderer,
noRowsFallback: children,
}}
minimumRowsToRender={100}
onScroll={onScroll}
onSelectedCellChange={handleCellFocus}
onCellKeyDown={handleCellKeyDown}
onColumnWidthsChange={setColumnWidths}
/>
</DataGridCellHeaderContext>
</DataGridCellContext>
+3 -3
View File
@@ -6052,14 +6052,14 @@ __metadata:
linkType: hard
"react-data-grid@file:./artifacts::locator=%40dbeaver%2Freact-data-grid%40workspace%3A%40dbeaver%2Freact-data-grid":
version: 7.0.0-beta.50
resolution: "react-data-grid@file:./artifacts#./artifacts::hash=f67a36&locator=%40dbeaver%2Freact-data-grid%40workspace%3A%40dbeaver%2Freact-data-grid"
version: 7.0.0-beta.55
resolution: "react-data-grid@file:./artifacts#./artifacts::hash=2f312f&locator=%40dbeaver%2Freact-data-grid%40workspace%3A%40dbeaver%2Freact-data-grid"
dependencies:
clsx: "npm:^2.0.0"
peerDependencies:
react: ^19.0
react-dom: ^19.0
checksum: 10c0/5591a564d6c3f4772dd87b8bfda111b0aafb8f748f1337f492db7ab5fc3f5dd5da3088aa4856ce581d943202ca8ed508e2828fc3e1453441b3061fa56a62ae95
checksum: 10c0/f0f3685c1ebcd3e0d3f33f189016a541eee963db1beb3f751209ef6eeb39497a08e1a583900a42a596b017e5daf6935d9ab9cd444e3b16ed905399e941eeadbb
languageName: node
linkType: hard
@@ -293,7 +293,7 @@ public class ServletAppUtils {
String forwardedScheme = request.getHeader(HEADER_FORWARDED_SCHEME);
String forwardedHost = request.getHeader(HEADER_FORWARDED_HOST);
if (CommonUtils.isNotEmpty(forwardedScheme) && CommonUtils.isNotEmpty(forwardedHost)) {
origin = forwardedHost + "://" + forwardedScheme;
origin = forwardedScheme + "://" + forwardedHost;
}
if (CommonUtils.isEmpty(origin)) {
URI requestUrl = URI.create(request.getRequestURL().toString());
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="48 48 102 102">
<path
fill="currentColor"
fill-rule="evenodd"
d="M131.718 94.87c0-16.77-14.844-31.37-31.541-31.37-12.3 0-23.27 7.468-27.992 18.057-.085-.002-.082-.003-.082-.003h-.083c-12.964 0-23.52 11.681-23.52 24.704 0 9.268 3.844 16.482 11.59 20.335v-.001c8.353 4.904 14.445-11.424 16.3-16.394.253-.679.427-1.146.517-1.318l.063.135c.622 1.352 1.773 3.849 7.32 3.964 0 0-6.732-3.314-3.727-12.108 3.004-8.795 7.445-16.259 13.543-19.71 6.098-3.45 12.259-3.216 17.927-.786 0 0-8.553-7.898-18.382-1.929-5.646 3.43-9.405 7.236-11.993 11.136-1.6.287-3.455.171-4.747.09-.46-.028-.85-.052-1.131-.052-1.352 0-3.439.92-2.496 4.255.408 1.44 1.132 2.889 1.796 4.217.84 1.68 1.585 3.169 1.469 4.206-.232 1.306-.333 2.482-.297 3.519-.121.193-.324.592-.602 1.138-2.3 4.515-9.738 19.115-18.74 11.338.134.119.267.269.398.446-6.944-6.717-5.559-20.578-.4-25.422 8.716-8.182 18.414-6.105 18.414-6.105l1.204-2.7c.146-.328.303-.7.475-1.11 2.197-5.221 6.913-16.434 24.024-16.22 19.515.245 24.829 20.966 24.836 27.875l-.001.084-.011 4.881 4.842-.423c.383-.033.764-.05 1.131-.05 7.088 0 14.325 5.538 14.325 12.657s-7.237 10.828-14.325 10.828h-10.553c-7.571-1.029-5.436-4.752-3.506-8.115.18-.313.358-.624.526-.929 2.368-4.289 3.177-7.02 3.687-10.959.306-2.368.081-4.141.016-4.568-.01-.065-.017-.1-.017-.1s-.008.133-.033.359l.033-.587s-1.494 6.033-4.441 8.197c-.686.504-1.124.838-1.406 1.063.297-1.125 1.41-5.866-.181-8.463 0 0-.66 10.908-6.45 11.488-5.791.579-13.952-5.372-16.285-7.762.104.497.22.963.342 1.414l-.115-.131s.097.225.246.61c.207.712.436 1.362.68 1.964.812 2.617 1.677 6.633.387 9.151-.17.331-.688.795-1.314 1.354-2.66 2.376-7.252 6.479 4.782 9.494 1.255.41 2.676.737 4.251.891h.006c.628.061 1.282.095 1.959.095h27.386c9.555 0 17.3-5.444 17.3-15.04 0-9.597-7.849-17.59-17.404-17.59zm-12.824-9.6c-1.825-.686-6.514-.223-9.809 1.07-1.616.633-2.898 1.465-3.339 2.46-1.209 2.732 6.197 7.476 9.181 6.602 1.422-.415 3.464-3.455 4.375-6.115.647-1.892.722-3.59-.408-4.016zm-16.56 2.251c-.845-.108-2.593-.346-3.152-.981-1.248-1.42-3.249-1.8-4.704-1.48-1.54.34-2.362 2.143-1.302 4.384.475 1.007.354 2.222-.13 3.129 1.515-1.419 2.931-2.574 3.983-3.129 2.071-1.095 3.908-1.662 5.306-1.923h-.001zm15.125 20.777s-1.512 5.162-2.57 7.513c-.815 1.814-1.143 1.915-1.664 2.074-.074.023-.153.047-.237.077.223-.567.392-1.291.657-2.43l.002-.012.109-.467c.4-1.713 0-2.164 0-2.164-.524 2.563-1.338 4.394-2.031 5.595-.897.302-2.002.496-2.805.174-1.133-.455-1.326-5.807-1.326-6.11 4.932.341 7.56-1.67 9.864-4.25h.001z"
clip-rule="evenodd"
/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+4 -3
View File
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* 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.
@@ -20,13 +20,14 @@ interface Props {
big?: boolean;
className?: string;
children?: React.ReactNode;
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
}
export const Cell = observer<Props>(function Cell({ before, after, description, className, ripple = true, big, children }) {
export const Cell = observer<Props>(function Cell({ before, after, description, className, ripple = true, big, children, onClick }) {
const styles = useS(style);
return (
<div className={s(styles, { ripple, big }, className)}>
<div className={s(styles, { ripple, big }, className)} onClick={onClick}>
<Container className={s(styles, { main: true })} gap parent center dense>
{before && (
<Container className={s(styles, { before: true })} keepSize>
@@ -6,40 +6,27 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { isDefined } from '@dbeaver/js-helpers';
import { useAutoLoad } from '../Loader/useAutoLoad.js';
import { usePlaceholder } from './usePlaceholder.js';
import type { PlaceholderContainer, PlaceholderElement } from './PlaceholderContainer.js';
type Props<T extends Record<string, any>> = T & {
container: PlaceholderContainer<T>;
elements?: PlaceholderElement<T>[];
empty?: React.ReactNode;
};
export const Placeholder = observer(function Placeholder<T extends Record<string, any>>({ container, elements: extraElements, ...rest }: Props<T>) {
let elements = container.get();
export const Placeholder = observer(function Placeholder<T extends Record<string, any>>({
container,
elements: extraElements,
empty,
...rest
}: Props<T>) {
const elements = usePlaceholder({ container, extraElements, props: rest as any as T });
if (extraElements) {
elements = [...elements, ...extraElements].sort((a, b) => {
if (a.order === b.order) {
return 0;
}
return (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER);
});
if (elements.length === 0 && empty) {
return <>{empty}</>;
}
useAutoLoad(
Placeholder,
elements
.map(element => element.getLoaders?.(rest as unknown as T))
.flat()
.filter(isDefined),
);
elements = elements.filter(placeholder => !placeholder.isHidden?.(rest as unknown as T));
return (
<>
{elements.map(({ id, component: Component }) => (
@@ -0,0 +1,41 @@
/*
* 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 { isDefined } from '@dbeaver/js-helpers';
import { useAutoLoad } from '../Loader/useAutoLoad.js';
import type { PlaceholderContainer, PlaceholderElement } from './PlaceholderContainer.js';
interface Options<T extends Record<string, any>> {
container: PlaceholderContainer<T>;
extraElements?: PlaceholderElement<T>[];
props: T;
}
export function usePlaceholder<T extends Record<string, any>>({ container, extraElements, props }: Options<T>): PlaceholderElement<T>[] {
let elements = container.get();
if (extraElements) {
elements = [...elements, ...extraElements].sort((a, b) => {
if (a.order === b.order) {
return 0;
}
return (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER);
});
}
useAutoLoad(
usePlaceholder,
elements
.map(element => element.getLoaders?.(props))
.flat()
.filter(isDefined),
);
return elements.filter(placeholder => !placeholder.isHidden?.(props));
}
@@ -6,7 +6,6 @@
* you may not use this file except in compliance with the License.
*/
.treeNodeControl {
composes: theme-ripple theme-ripple-selectable from global;
box-sizing: border-box;
height: 22px;
display: flex;
+1
View File
@@ -81,6 +81,7 @@ export * from './Overlay/OverlayHeaderTitle.js';
export * from './Overlay/OverlayMessage.js';
export * from './Placeholder/Placeholder.js';
export * from './Placeholder/usePlaceholder.js';
export * from './Placeholder/PlaceholderContainer.js';
export * from './PropertiesTable/PropertiesTable.js';
@@ -7,6 +7,7 @@
*/
export const WEBSITE_LINKS = {
ROOT_PAGE: 'https://dbeaver.com/',
DOCS_PAGE: 'https://dbeaver.com/docs/cloudbeaver/',
DATA_EDITOR_DOCUMENTATION_PAGE: 'https://dbeaver.com/docs/cloudbeaver/Data-editor/',
SQL_EDITOR_DOCUMENTATION_PAGE: 'https://dbeaver.com/docs/cloudbeaver/SQL-Editor/',
SERVER_CONFIGURATION_RESOURCE_QUOTAS_PAGE: 'https://dbeaver.com/docs/cloudbeaver/Server-configuration/#resource-quotas',
@@ -34,6 +34,7 @@
"@cloudbeaver/core-utils": "workspace:*",
"@cloudbeaver/core-view": "workspace:*",
"@cloudbeaver/plugin-connections": "workspace:*",
"@cloudbeaver/plugin-navigation-tabs": "workspace:*",
"@cloudbeaver/plugin-navigation-tree": "workspace:*",
"mobx": "^6",
"mobx-react-lite": "^4",
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* 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.
@@ -9,4 +9,5 @@ import { createAction } from '@cloudbeaver/core-view';
export const ACTION_CONNECTION_CUSTOM = createAction('connection-custom', {
label: 'plugin_connection_custom_action_custom_label',
tooltip: 'plugin_connection_custom_action_custom_tooltip',
});
@@ -16,11 +16,13 @@ import { CachedMapAllKey, getCachedMapResourceLoaderState } from '@cloudbeaver/c
import { ActionService, DATA_CONTEXT_MENU, type IAction, MenuService } from '@cloudbeaver/core-view';
import { ACTION_TREE_CREATE_CONNECTION, MENU_CONNECTIONS, MENU_TREE_CREATE_CONNECTION } from '@cloudbeaver/plugin-connections';
import { DATA_CONTEXT_ELEMENTS_TREE, MENU_ELEMENTS_TREE_TOOLS, TreeSelectionService } from '@cloudbeaver/plugin-navigation-tree';
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
import { ACTION_CONNECTION_CUSTOM } from './Actions/ACTION_CONNECTION_CUSTOM.js';
import { CustomConnectionSettingsService } from './CustomConnectionSettingsService.js';
const DriverSelectorDialog = importLazyComponent(() => import('./DriverSelector/DriverSelectorDialog.js').then(m => m.DriverSelectorDialog));
const WelcomeNewConnection = importLazyComponent(() => import('./WelcomeNewConnection.js').then(m => m.WelcomeNewConnection));
@injectable()
export class CustomConnectionPluginBootstrap extends Bootstrap {
@@ -32,11 +34,13 @@ export class CustomConnectionPluginBootstrap extends Bootstrap {
private readonly connectionsManagerService: ConnectionsManagerService,
private readonly customConnectionSettingsService: CustomConnectionSettingsService,
private readonly treeSelectionService: TreeSelectionService,
private readonly navigationTabsService: NavigationTabsService,
) {
super();
}
override register(): void | Promise<void> {
this.navigationTabsService.welcomeContainer.add(WelcomeNewConnection, undefined, () => this.isConnectionFeatureDisabled(true));
this.menuService.addCreator({
menus: [MENU_CONNECTIONS],
getItems: (context, items) => [...items, ACTION_CONNECTION_CUSTOM],
@@ -127,7 +131,7 @@ export class CustomConnectionPluginBootstrap extends Bootstrap {
return false;
}
private async openConnectionsDialog(projectId?: string, folderPath?: string) {
async openConnectionsDialog(projectId?: string, folderPath?: string): Promise<void> {
await this.commonDialogService.open(DriverSelectorDialog, {
projectId,
folderPath,
@@ -0,0 +1,29 @@
/*
* 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 { Cell, IconOrImage, useTranslate } from '@cloudbeaver/core-blocks';
import { observer } from 'mobx-react-lite';
import { ACTION_CONNECTION_CUSTOM } from './Actions/ACTION_CONNECTION_CUSTOM.js';
import { useService } from '@cloudbeaver/core-di';
import { CustomConnectionPluginBootstrap } from './CustomConnectionPluginBootstrap.js';
export const WelcomeNewConnection = observer(function WelcomeNewConnection() {
const customConnectionPluginBootstrap = useService(CustomConnectionPluginBootstrap);
const translate = useTranslate();
return (
<Cell
before={<IconOrImage icon="/icons/plugin_connection_new.svg" />}
description={translate(ACTION_CONNECTION_CUSTOM.info.tooltip)}
className="tw:cursor-pointer tw:rounded-sm tw:overflow-hidden"
big
onClick={() => customConnectionPluginBootstrap.openConnectionsDialog()}
>
{translate(ACTION_CONNECTION_CUSTOM.info.label)}
</Cell>
);
});
@@ -1,4 +1,5 @@
export default [
['plugin_connection_custom_action_custom_label', 'New Connection'],
['plugin_connection_custom_action_custom_tooltip', 'Create a new connection'],
['plugin_connection_custom_settings_disabled_name', 'Disable custom connections'],
];
@@ -1,4 +1,5 @@
export default [
['plugin_connection_custom_action_custom_label', 'Nouvelle connexion'],
['plugin_connection_custom_settings_disabled_name', 'Disable custom connections'],
['plugin_connection_custom_action_custom_tooltip', 'Créer une nouvelle connexion'],
['plugin_connection_custom_settings_disabled_name', 'Désactiver les connexions personnalisées'],
];
@@ -1,4 +1,5 @@
export default [
['plugin_connection_custom_action_custom_label', 'New Connection'],
['plugin_connection_custom_action_custom_tooltip', 'Create a new connection'],
['plugin_connection_custom_settings_disabled_name', 'Disable custom connections'],
];
@@ -1,4 +1,5 @@
export default [
['plugin_connection_custom_action_custom_label', 'Новое подключение'],
['plugin_connection_custom_action_custom_tooltip', 'Создать новое подключение'],
['plugin_connection_custom_settings_disabled_name', 'Отключить пользовательские подключения'],
];
@@ -1 +1,4 @@
export default [['plugin_connection_custom_action_custom_label', 'Kết nối mới']];
export default [
['plugin_connection_custom_action_custom_label', 'Kết nối mới'],
['plugin_connection_custom_action_custom_tooltip', 'Tạo một kết nối mới'],
];
@@ -1,4 +1,5 @@
export default [
['plugin_connection_custom_action_custom_label', '新建连接'],
['plugin_connection_custom_settings_disabled_name', 'Disable custom connections'],
['plugin_connection_custom_action_custom_tooltip', '创建一个新的连接'],
['plugin_connection_custom_settings_disabled_name', '禁用自定义连接'],
];
@@ -55,6 +55,9 @@
{
"path": "../plugin-connections"
},
{
"path": "../plugin-navigation-tabs"
},
{
"path": "../plugin-navigation-tree"
}
@@ -7,14 +7,12 @@
*/
.container {
width: 100%;
cursor: pointer;
}
.iconOrImage {
cursor: auto;
width: 10px;
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* 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.
@@ -40,7 +40,6 @@ export const TableIndexColumnHeader = observer(function TableIndexColumnHeader()
{readonly && (
<IconOrImage title={translate('data_grid_table_readonly_tooltip')} icon="/icons/lock.png" className={s(styles, { iconOrImage: true })} />
)}
#
</div>
);
});
+2
View File
@@ -31,11 +31,13 @@
"@cloudbeaver/core-utils": "workspace:*",
"@cloudbeaver/core-view": "workspace:*",
"@cloudbeaver/plugin-data-spreadsheet-new": "workspace:*",
"@cloudbeaver/plugin-navigation-tabs": "workspace:*",
"@cloudbeaver/plugin-navigation-tree": "workspace:*",
"@cloudbeaver/plugin-object-viewer-nav-tree-link": "workspace:*",
"@cloudbeaver/plugin-sql-editor": "workspace:*",
"@cloudbeaver/plugin-sql-editor-navigation-tab-script": "workspace:*",
"@cloudbeaver/plugin-top-app-bar": "workspace:*",
"mobx-react-lite": "^4",
"react": "^19",
"tslib": "^2"
},
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<path d="M21,1c-2.09,0-3.93,1.07-5,2.69-1.07-1.62-2.91-2.69-5-2.69H1v27h10c1.45,0,2.89.7,4,1.42v.58h2v-.58c1.11-.72,2.55-1.42,4-1.42h10V1h-10ZM11,26H3V3h8c2.21,0,4,1.79,4,4v20.1c-1.17-.61-2.56-1.1-4-1.1ZM29,26h-8c-1.44,0-2.83.48-4,1.1V7c0-2.21,1.79-4,4-4h8v23Z" fill="#3387c5"/>
<rect x="5" y="8" width="8" height="2" fill="#3387c5"/>
<rect x="5" y="12" width="4" height="2" fill="#3387c5"/>
<rect x="5" y="16" width="8" height="2" fill="#3387c5"/>
<rect x="5" y="20" width="4" height="2" fill="#3387c5"/>
<rect x="19" y="8" width="8" height="2" fill="#3387c5"/>
<rect x="19" y="12" width="4" height="2" fill="#3387c5"/>
<rect x="19" y="16" width="8" height="2" fill="#3387c5"/>
<rect x="19" y="20" width="4" height="2" fill="#3387c5"/>
</svg>

After

Width:  |  Height:  |  Size: 894 B

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M15.67,1c-1.53,0-2.88.79-3.67,1.97-.79-1.19-2.13-1.97-3.67-1.97H1v19.8h7.33c1.06,0,2.12.51,2.93,1.04v.16h1.47v-.16c.81-.53,1.87-1.04,2.93-1.04h7.33V1h-7.33ZM8.33,19.33H2.47V2.47h5.87c1.62,0,2.93,1.32,2.93,2.93v14.74c-.86-.45-1.88-.8-2.93-.8ZM21.53,19.33h-5.87c-1.06,0-2.07.35-2.93.8V5.4c0-1.62,1.32-2.93,2.93-2.93h5.87v16.87Z" fill="#3387c5"/>
<rect x="3.93" y="6.13" width="5.87" height="1.47" fill="#3387c5"/>
<rect x="3.93" y="9.07" width="2.93" height="1.47" fill="#3387c5"/>
<rect x="3.93" y="12" width="5.87" height="1.47" fill="#3387c5"/>
<rect x="3.93" y="14.93" width="2.93" height="1.47" fill="#3387c5"/>
<rect x="14.2" y="6.13" width="5.87" height="1.47" fill="#3387c5"/>
<rect x="14.2" y="9.07" width="2.93" height="1.47" fill="#3387c5"/>
<rect x="14.2" y="12" width="5.87" height="1.47" fill="#3387c5"/>
<rect x="14.2" y="14.93" width="2.93" height="1.47" fill="#3387c5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M10.38,0c-.98,0-1.62.5-1.98,1.26-.53-.76-1.37-1.26-2.34-1.26H1v14h5.05c1.95,0,1.95,1,1.95,1h1s0-1,2-1h5V0h-5.62ZM6.05,13H2V1h4.05c1.09,0,1.95.97,1.95,2.2v10.46c-.57-.38-1.24-.66-1.95-.66ZM15,13h-4c-2,0-1.6.44-2,.66V3.2c0-1,.24-2.2,1.38-2.2h4.62v12Z" fill="#3387c5"/>
<rect x="3" y="4" width="4" height="1" fill="#3387c5"/>
<rect x="3" y="6" width="2" height="1" fill="#3387c5"/>
<rect x="3" y="8" width="4" height="1" fill="#3387c5"/>
<rect x="3" y="10" width="2" height="1" fill="#3387c5"/>
<rect x="10" y="4" width="4" height="1" fill="#3387c5"/>
<rect x="10" y="6" width="2" height="1" fill="#3387c5"/>
<rect x="10" y="8" width="4" height="1" fill="#3387c5"/>
<rect x="10" y="10" width="2" height="1" fill="#3387c5"/>
</svg>

After

Width:  |  Height:  |  Size: 887 B

@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* 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.
@@ -14,10 +14,12 @@ import { ENotificationType, type INotification, NotificationService } from '@clo
import { ScreenService } from '@cloudbeaver/core-routing';
import { ActionService, menuExtractItems, MenuService } from '@cloudbeaver/core-view';
import { MENU_APP_STATE } from '@cloudbeaver/plugin-top-app-bar';
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
import { ACTION_APP_HELP } from './actions/ACTION_APP_HELP.js';
const ShortcutsDialog = importLazyComponent(() => import('./Shortcuts/ShortcutsDialog.js').then(m => m.ShortcutsDialog));
const WelcomeDocs = importLazyComponent(() => import('./WelcomeDocs.js').then(m => m.WelcomeDocs));
@injectable()
export class PluginBootstrap extends Bootstrap {
@@ -29,6 +31,7 @@ export class PluginBootstrap extends Bootstrap {
private readonly commonDialogService: CommonDialogService,
private readonly notificationService: NotificationService,
private readonly localStorageSaveService: LocalStorageSaveService,
private readonly navigationTabsService: NavigationTabsService,
) {
super();
this.errorNotification = null;
@@ -37,6 +40,7 @@ export class PluginBootstrap extends Bootstrap {
override async load(): Promise<void> {}
override register(): void {
this.navigationTabsService.welcomeContainer.add(WelcomeDocs, undefined);
this.addTopAppMenuItems();
this.addMultiTabSupportNotification();
}
@@ -0,0 +1,27 @@
/*
* 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 { Cell, IconOrImage, Link, useTranslate } from '@cloudbeaver/core-blocks';
import { observer } from 'mobx-react-lite';
import { WEBSITE_LINKS } from '@cloudbeaver/core-links';
export const WelcomeDocs = observer(function WelcomeDocs() {
const translate = useTranslate();
return (
<Link href={WEBSITE_LINKS.DOCS_PAGE} target="_blank" wrapper>
<Cell
before={<IconOrImage icon="/icons/documentation_link.svg" />}
description={translate('plugin_help_welcome_docs_description')}
className="tw:cursor-pointer tw:rounded-sm tw:overflow-hidden"
big
>
{translate('plugin_help_welcome_docs_label')}
</Cell>
</Link>
);
});
@@ -1,6 +1,9 @@
export default [
['shortcuts_title', 'Shortcuts'],
['plugin_help_welcome_docs_label', 'Documentation'],
['plugin_help_welcome_docs_description', 'Learn how to use CloudBeaver'],
['data_viewer_shortcut_start_inline_editing', 'Open Inline Editor for the selected Cell'],
['data_viewer_shortcut_add_new_row', 'Add a new Row'],
['data_viewer_shortcut_duplicate_row', 'Duplicate row'],
@@ -1,5 +1,9 @@
export default [
['shortcuts_title', 'Raccourcis'],
['plugin_help_welcome_docs_label', 'Documentation'],
['plugin_help_welcome_docs_description', 'Apprenez à utiliser CloudBeaver'],
['data_viewer_shortcut_start_inline_editing', "Ouvrir l'éditeur en ligne pour la cellule sélectionnée"],
['data_viewer_shortcut_add_new_row', 'Ajouter une nouvelle ligne'],
['data_viewer_shortcut_duplicate_row', 'Dupliquer la ligne'],
@@ -1,6 +1,9 @@
export default [
['shortcuts_title', 'Shortcuts'],
['plugin_help_welcome_docs_label', 'Documentation'],
['plugin_help_welcome_docs_description', 'Learn how to use CloudBeaver'],
['data_viewer_shortcut_start_inline_editing', 'Open Inline Editor for the selected Cell'],
['data_viewer_shortcut_add_new_row', 'Add a new Row'],
['data_viewer_shortcut_duplicate_row', 'Duplicate row'],
@@ -1,6 +1,9 @@
export default [
['shortcuts_title', 'Горячие клавиши'],
['plugin_help_welcome_docs_label', 'Документация'],
['plugin_help_welcome_docs_description', 'Узнайте, как использовать CloudBeaver'],
['data_viewer_shortcut_start_inline_editing', 'Начать редактирование ячейки во встроенном редакторе'],
['data_viewer_shortcut_add_new_row', 'Добавить новую строку'],
['data_viewer_shortcut_duplicate_row', 'Продублировать строку'],
@@ -1,6 +1,9 @@
export default [
['shortcuts_title', 'Shortcuts'],
['plugin_help_welcome_docs_label', 'Documentation'],
['plugin_help_welcome_docs_description', 'Learn how to use CloudBeaver'],
['data_viewer_shortcut_start_inline_editing', 'Mở Trình chỉnh sửa inline cho ô được chọn'],
['data_viewer_shortcut_add_new_row', 'Thêm một hàng mới'],
['data_viewer_shortcut_duplicate_row', 'Sao chép hàng'],
@@ -1,6 +1,9 @@
export default [
['shortcuts_title', '快捷操作'],
['plugin_help_welcome_docs_label', 'Documentation'],
['plugin_help_welcome_docs_description', 'Learn how to use CloudBeaver'],
['data_viewer_shortcut_start_inline_editing', '为所选单元格打开内联编辑器'],
['data_viewer_shortcut_add_new_row', '添加行'],
['data_viewer_shortcut_duplicate_row', '复制行'],
@@ -46,6 +46,9 @@
{
"path": "../plugin-data-spreadsheet-new"
},
{
"path": "../plugin-navigation-tabs"
},
{
"path": "../plugin-navigation-tree"
},
@@ -27,8 +27,10 @@
"@cloudbeaver/core-events": "workspace:*",
"@cloudbeaver/core-executor": "workspace:*",
"@cloudbeaver/core-extensions": "workspace:*",
"@cloudbeaver/core-localization": "workspace:*",
"@cloudbeaver/core-projects": "workspace:*",
"@cloudbeaver/core-resource": "workspace:*",
"@cloudbeaver/core-root": "workspace:*",
"@cloudbeaver/core-storage": "workspace:*",
"@cloudbeaver/core-ui": "workspace:*",
"@cloudbeaver/core-utils": "workspace:*",
@@ -0,0 +1,38 @@
/*
* 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 { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { LocalizationService } from '@cloudbeaver/core-localization';
@injectable()
export class LocaleService extends Bootstrap {
constructor(private readonly localizationService: LocalizationService) {
super();
}
override register(): void {
this.localizationService.addProvider(this.provider.bind(this));
}
private async provider(locale: string) {
switch (locale) {
case 'ru':
return (await import('./locales/ru.js')).default;
case 'it':
return (await import('./locales/it.js')).default;
case 'zh':
return (await import('./locales/zh.js')).default;
case 'fr':
return (await import('./locales/fr.js')).default;
case 'vi':
return (await import('./locales/vi.js')).default;
default:
return (await import('./locales/en.js')).default;
}
}
}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* 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.
@@ -9,7 +9,7 @@ import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useState } from 'react';
import { UserInfoResource } from '@cloudbeaver/core-authentication';
import { Loader, s, SContext, type StyleRegistry, TextPlaceholder, useExecutor, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { Loader, s, SContext, type StyleRegistry, useExecutor, useS } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { type ITabData, TabPanel, TabsBox, TabStyles } from '@cloudbeaver/core-ui';
import { CaptureView } from '@cloudbeaver/core-view';
@@ -19,6 +19,7 @@ import styles from './shared/NavigationTabsBar.module.css';
import NavigationTabsBarTab from './shared/NavigationTabsBarTab.module.css';
import { TabHandlerPanel } from './Tabs/TabHandlerPanel.js';
import { TabHandlerTab } from './Tabs/TabHandlerTab.js';
import { NavigationWelcomeScreen } from './NavigationWelcomeScreen.js';
interface Props {
className?: string;
@@ -40,7 +41,6 @@ export const NavigationTabsBar = observer<Props>(function NavigationTabsBar({ cl
// TODO: we get exception when after closing the restored page trying to open another
// it's related to hooks order and state restoration
const [restoring, setRestoring] = useState(false);
const translate = useTranslate();
const style = useS(styles);
const handleSelect = useCallback((tabId: string) => navigation.selectTab(tabId), [navigation]);
@@ -79,7 +79,7 @@ export const NavigationTabsBar = observer<Props>(function NavigationTabsBar({ cl
}, []);
if (navigation.tabIdList.length === 0) {
return <TextPlaceholder>{translate('app_shared_navigationTabsBar_placeholder')}</TextPlaceholder>;
return <NavigationWelcomeScreen />;
}
return (
@@ -0,0 +1,43 @@
/*
* 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 { Icon, Placeholder, TextPlaceholder, usePlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { observer } from 'mobx-react-lite';
import { NavigationTabsService } from '../NavigationTabsService.js';
export const NavigationWelcomeScreen = observer(function NavigationWelcomeScreen() {
const navigationTabsService = useService(NavigationTabsService);
const translate = useTranslate();
const elements = usePlaceholder({
container: navigationTabsService.welcomeContainer,
props: {},
});
return (
<div className="tw:flex tw:flex-col tw:h-full tw:w-full tw:relative tw:pl-6 tw:pt-8 tw:lg:pl-12 tw:lg:pt-16">
<div className="tw:absolute tw:inset-0 tw:overflow-hidden tw:flex tw:items-center tw:justify-center">
<Icon name="/icons/logo_current_sm.svg" className="tw:w-64 tw:h-64 tw:opacity-5" viewBox="0 0 24 24" />
</div>
<div className="tw:relative tw:flex tw:flex-col tw:mb-8">
<div className="tw:text-xl tw:font-semibold tw:text-left">{translate('product_full_name')}</div>
</div>
{elements.length && (
<div className="tw:relative">
<h2 className="tw:text-lg tw:font-semibold tw:mb-4">{translate('plugin_navigation_tabs_welcome_start')}</h2>
<div className="tw:grid tw:grid-cols-[repeat(auto-fit,minmax(150px,300px))] tw:gap-2">
<Placeholder
container={navigationTabsService.welcomeContainer}
empty={<TextPlaceholder>{translate('app_shared_navigationTabsBar_placeholder')}</TextPlaceholder>}
/>
</div>
</div>
)}
</div>
);
});
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* 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.
@@ -17,6 +17,7 @@ import { resourceKeyList, type ResourceKeySimple, ResourceKeyUtils } from '@clou
import { StorageService } from '@cloudbeaver/core-storage';
import { isArraysEqual, MetadataMap, TempMap } from '@cloudbeaver/core-utils';
import { ACTION_OPEN_IN_TAB, type IActiveView, View } from '@cloudbeaver/core-view';
import { PlaceholderContainer } from '@cloudbeaver/core-blocks';
import type { ITab, ITabMetadata } from './ITab.js';
import { TabHandler, type TabHandlerEvent, type TabHandlerOptions, type TabSyncHandlerEvent } from './TabHandler.js';
@@ -100,6 +101,7 @@ export class NavigationTabsService extends View<ITab> {
readonly onTabClose: ISyncExecutor<ITab | undefined>;
readonly onInit: ISyncExecutor<boolean>;
readonly onStateUpdate: ISyncExecutor;
readonly welcomeContainer: PlaceholderContainer;
private readonly handlers: Map<string, TabHandler>;
private readonly tabsMap: Map<string, ITab>;
@@ -122,6 +124,7 @@ export class NavigationTabsService extends View<ITab> {
this.tabsMap = new Map<string, ITab>();
this.state = new Map<string, TabsState>();
this.historyState = new Map<string, INavigatorHistory>();
this.welcomeContainer = new PlaceholderContainer();
this.onTabSelect = new SyncExecutor();
this.onTabClose = new SyncExecutor();
@@ -0,0 +1 @@
export default [['plugin_navigation_tabs_welcome_start', 'Start']];
@@ -0,0 +1 @@
export default [['plugin_navigation_tabs_welcome_start', 'Démarrer']];
@@ -0,0 +1 @@
export default [['plugin_navigation_tabs_welcome_start', 'Inizia']];
@@ -0,0 +1 @@
export default [['plugin_navigation_tabs_welcome_start', 'Начать']];
@@ -0,0 +1 @@
export default [['plugin_navigation_tabs_welcome_start', 'Bắt đầu']];
@@ -0,0 +1 @@
export default [['plugin_navigation_tabs_welcome_start', '开始']];
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* 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.
@@ -12,5 +12,6 @@ export const navigationTabsPlugin: PluginManifest = {
providers: [
() => import('./PluginBootstrap.js').then(m => m.PluginBootstrap),
() => import('./NavigationTabs/NavigationTabsService.js').then(m => m.NavigationTabsService),
() => import('./LocaleService.js').then(m => m.LocaleService),
],
};
@@ -34,12 +34,18 @@
{
"path": "../core-extensions"
},
{
"path": "../core-localization"
},
{
"path": "../core-projects"
},
{
"path": "../core-resource"
},
{
"path": "../core-root"
},
{
"path": "../core-storage"
},
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { RenameDialog } from '@cloudbeaver/core-blocks';
import { importLazyComponent, RenameDialog } from '@cloudbeaver/core-blocks';
import {
type Connection,
ConnectionInfoResource,
@@ -45,6 +45,8 @@ import { SQL_EDITOR_SOURCE_ACTION } from './SQL_EDITOR_SOURCE_ACTION.js';
import { SqlEditorNavigatorService } from './SqlEditorNavigatorService.js';
import { SqlEditorTabService } from './SqlEditorTabService.js';
const WelcomeNewSqlEditor = importLazyComponent(() => import('./WelcomeNewSqlEditor.js').then(m => m.WelcomeNewSqlEditor));
interface IActiveConnectionContext {
connectionKey?: IConnectionInfoParams;
catalogId?: string;
@@ -72,6 +74,7 @@ export class SqlEditorBootstrap extends Bootstrap {
}
override register(): void {
this.navigationTabsService.welcomeContainer.add(WelcomeNewSqlEditor, undefined, () => this.sqlEditorSettingsService.disabled);
this.registerTopAppBarItem();
this.menuService.addCreator({
@@ -237,10 +240,10 @@ export class SqlEditorBootstrap extends Bootstrap {
return false;
},
handler: (context, action) => {
handler: async (context, action) => {
switch (action) {
case ACTION_SQL_EDITOR_NEW: {
this.openSQLEditor();
await this.openSQLEditor();
break;
}
}
@@ -277,10 +280,10 @@ export class SqlEditorBootstrap extends Bootstrap {
};
}
private openSQLEditor() {
async openSQLEditor(): Promise<void> {
const connectionContext = this.getActiveConnectionContext();
this.sqlEditorNavigatorService.openNewEditor({
await this.sqlEditorNavigatorService.openNewEditor({
dataSourceKey: LocalStorageSqlDataSource.key,
connectionKey: connectionContext.connectionKey,
catalogId: connectionContext.catalogId,
@@ -0,0 +1,29 @@
/*
* 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 { Cell, IconOrImage, useTranslate } from '@cloudbeaver/core-blocks';
import { observer } from 'mobx-react-lite';
import { useService } from '@cloudbeaver/core-di';
import { SqlEditorBootstrap } from './SqlEditorBootstrap.js';
import { ACTION_SQL_EDITOR_NEW } from './ACTION_SQL_EDITOR_NEW.js';
export const WelcomeNewSqlEditor = observer(function WelcomeNewSqlEditor() {
const sqlEditorBootstrap = useService(SqlEditorBootstrap);
const translate = useTranslate();
return (
<Cell
before={<IconOrImage icon="/icons/sql_script.svg" />}
description={translate(ACTION_SQL_EDITOR_NEW.info.tooltip)}
className="tw:cursor-pointer tw:rounded-sm tw:overflow-hidden"
big
onClick={() => sqlEditorBootstrap.openSQLEditor()}
>
{translate(ACTION_SQL_EDITOR_NEW.info.label)}
</Cell>
);
});
@@ -21,6 +21,9 @@ export const manualChunks = (): PluginOption => [
output: {
...config.build?.rollupOptions?.output,
manualChunks(id, { getModuleInfo }) {
// Normalize path separators for cross-platform compatibility
const normalizedId = id.replace(/\\/g, '/');
function isModuleSync(moduleId: string) {
// Use a non-recursive approach to detect circular dependencies
const visited = new Set<string>();
@@ -79,41 +82,41 @@ export const manualChunks = (): PluginOption => [
return syncModules.has(moduleId);
}
const langMatch = /[\\/]locales[\\/](\w+)\.js/.exec(id);
const langMatch = /[\\/]locales[\\/](\w+)\.js/.exec(normalizedId);
if (langMatch) {
const language = langMatch[1]; // e.g. "en"
return `locales/${language}`;
}
if (id.includes('packages/core-')) {
if (normalizedId.includes('packages/core-')) {
if (!isModuleSync(id)) {
return 'core-async';
}
return 'core';
}
if (id.includes('packages/plugin-')) {
if (normalizedId.includes('packages/plugin-')) {
if (!isModuleSync(id)) {
return 'plugins-async';
}
return 'plugins';
}
if (id.includes('packages/product-')) {
if (normalizedId.includes('packages/product-')) {
if (!isModuleSync(id)) {
return 'products-async';
}
return 'products';
}
if (id.includes('@dbeaver/')) {
if (normalizedId.includes('@dbeaver/')) {
if (!isModuleSync(id)) {
return 'dbeaver-async';
}
return 'dbeaver';
}
if (id.includes('node_modules')) {
if (normalizedId.includes('node_modules')) {
if (!isModuleSync(id)) {
return 'vendor-async';
}
+8 -3
View File
@@ -2615,6 +2615,7 @@ __metadata:
"@cloudbeaver/core-utils": "workspace:*"
"@cloudbeaver/core-view": "workspace:*"
"@cloudbeaver/plugin-connections": "workspace:*"
"@cloudbeaver/plugin-navigation-tabs": "workspace:*"
"@cloudbeaver/plugin-navigation-tree": "workspace:*"
"@cloudbeaver/tsconfig": "workspace:*"
"@types/react": "npm:^19"
@@ -3150,6 +3151,7 @@ __metadata:
"@cloudbeaver/core-utils": "workspace:*"
"@cloudbeaver/core-view": "workspace:*"
"@cloudbeaver/plugin-data-spreadsheet-new": "workspace:*"
"@cloudbeaver/plugin-navigation-tabs": "workspace:*"
"@cloudbeaver/plugin-navigation-tree": "workspace:*"
"@cloudbeaver/plugin-object-viewer-nav-tree-link": "workspace:*"
"@cloudbeaver/plugin-sql-editor": "workspace:*"
@@ -3157,6 +3159,7 @@ __metadata:
"@cloudbeaver/plugin-top-app-bar": "workspace:*"
"@cloudbeaver/tsconfig": "workspace:*"
"@types/react": "npm:^19"
mobx-react-lite: "npm:^4"
react: "npm:^19"
tslib: "npm:^2"
typescript: "npm:^5"
@@ -3257,8 +3260,10 @@ __metadata:
"@cloudbeaver/core-events": "workspace:*"
"@cloudbeaver/core-executor": "workspace:*"
"@cloudbeaver/core-extensions": "workspace:*"
"@cloudbeaver/core-localization": "workspace:*"
"@cloudbeaver/core-projects": "workspace:*"
"@cloudbeaver/core-resource": "workspace:*"
"@cloudbeaver/core-root": "workspace:*"
"@cloudbeaver/core-storage": "workspace:*"
"@cloudbeaver/core-ui": "workspace:*"
"@cloudbeaver/core-utils": "workspace:*"
@@ -15836,14 +15841,14 @@ __metadata:
linkType: hard
"react-data-grid@file:./artifacts::locator=%40dbeaver%2Freact-data-grid%40workspace%3A..%2Fcommon-react%2F%40dbeaver%2Freact-data-grid":
version: 7.0.0-beta.50
resolution: "react-data-grid@file:./artifacts#./artifacts::hash=f67a36&locator=%40dbeaver%2Freact-data-grid%40workspace%3A..%2Fcommon-react%2F%40dbeaver%2Freact-data-grid"
version: 7.0.0-beta.55
resolution: "react-data-grid@file:./artifacts#./artifacts::hash=2f312f&locator=%40dbeaver%2Freact-data-grid%40workspace%3A..%2Fcommon-react%2F%40dbeaver%2Freact-data-grid"
dependencies:
clsx: "npm:^2.0.0"
peerDependencies:
react: ^19.0
react-dom: ^19.0
checksum: 10c0/5591a564d6c3f4772dd87b8bfda111b0aafb8f748f1337f492db7ab5fc3f5dd5da3088aa4856ce581d943202ca8ed508e2828fc3e1453441b3061fa56a62ae95
checksum: 10c0/f0f3685c1ebcd3e0d3f33f189016a541eee963db1beb3f751209ef6eeb39497a08e1a583900a42a596b017e5daf6935d9ab9cd444e3b16ed905399e941eeadbb
languageName: node
linkType: hard