Compare commits

...

30 Commits

Author SHA1 Message Date
Jan Prochazka be9df93a7e electron paste WIP 2024-12-18 15:13:12 +01:00
Jan Prochazka 8ff30e426e fix - expand limited when accessing bykeyboard nav 2024-12-18 14:13:01 +01:00
Jan Prochazka 7cdbef609e arm-64 builds 2024-12-18 10:59:21 +01:00
Jan Prochazka f6195a468d v6.1.0 2024-12-18 10:37:54 +01:00
Jan Prochazka c23bf72d55 changelog 2024-12-18 10:35:54 +01:00
Jan Prochazka c02441402b fixed search in list 2024-12-18 10:22:02 +01:00
Jan Prochazka b9a8764b55 fixed search 2024-12-18 10:01:52 +01:00
Jan Prochazka a2374c1981 v6.0.1-beta.6 2024-12-18 09:39:57 +01:00
Jan Prochazka 9cfd5af704 prevent jump to first item when focusing because of mouse 2024-12-18 09:37:40 +01:00
Jan Prochazka a6f473b8ed better connection UX 2024-12-18 09:08:42 +01:00
Jan Prochazka e0a74402cb dropdown for default database 2024-12-18 08:43:51 +01:00
Jan Prochazka c6e57b278e use default database 2024-12-18 08:18:09 +01:00
Jan Prochazka e63f1f8f09 clickAction refactor, settings - open detail after keyboard navigation 2024-12-18 08:08:45 +01:00
SPRINX0\prochazka 57da9c9885 changelog wip 2024-12-17 17:17:31 +01:00
SPRINX0\prochazka e6a3acf4c2 search GUI improved 2024-12-17 16:27:54 +01:00
SPRINX0\prochazka 537869e862 data grid - no rows info 2024-12-17 16:03:21 +01:00
SPRINX0\prochazka ae2ff7b3b1 fixed display CLOB an NCLOB columns in Oracle #944 2024-12-17 15:31:10 +01:00
SPRINX0\prochazka 1db01dbdb1 optimalization 2024-12-17 14:59:44 +01:00
SPRINX0\prochazka 7988438dc7 fix search connections 2024-12-17 14:43:05 +01:00
SPRINX0\prochazka 3b32823f94 v6.0.1-beta.5 2024-12-17 14:29:56 +01:00
SPRINX0\prochazka 3370c754f2 try to fix postgres plugin 2024-12-17 14:29:45 +01:00
SPRINX0\prochazka 2d84e5a611 dataGrid align numbers right #957 2024-12-17 13:43:21 +01:00
SPRINX0\prochazka 8d5f73849e upgraded ace-builds #954 2024-12-17 12:42:09 +01:00
SPRINX0\prochazka 7a5019164a configurable search in connections 2024-12-17 12:26:50 +01:00
SPRINX0\prochazka f5733ea2d7 handle camelCase in tokenizer 2024-12-17 10:16:19 +01:00
SPRINX0\prochazka 92e13220d8 tokenized search in references 2024-12-17 10:12:45 +01:00
SPRINX0\prochazka 2a5fdd852a search tokenizer optimalization 2024-12-17 10:09:52 +01:00
SPRINX0\prochazka 7759fd862f ability to disable tab preview mode 2024-12-17 09:34:16 +01:00
SPRINX0\prochazka ca5dd0ac30 v6.0.1-beta.4 2024-12-16 17:01:39 +01:00
SPRINX0\prochazka 18be29fd88 Merge branch 'feature/search' 2024-12-16 17:01:26 +01:00
37 changed files with 435 additions and 142 deletions
+1
View File
@@ -123,6 +123,7 @@ jobs:
cp ../dbgate-merged/app/dist/*win_arm64.zip artifacts/dbgate-premium-windows-latest-arm64.zip || true
cp ../dbgate-merged/app/dist/*-mac_universal.dmg artifacts/dbgate-premium-latest.dmg || true
cp ../dbgate-merged/app/dist/*-mac_x64.dmg artifacts/dbgate-premium-latest-x64.dmg || true
cp ../dbgate-merged/app/dist/*-mac_arm64.dmg artifacts/dbgate-premium-latest-arm64.dmg || true
mv ../dbgate-merged/app/dist/*.exe artifacts/ || true
mv ../dbgate-merged/app/dist/*.zip artifacts/ || true
+1
View File
@@ -98,6 +98,7 @@ jobs:
cp app/dist/*win_arm64.zip artifacts/dbgate-windows-latest-arm64.zip || true
cp app/dist/*-mac_universal.dmg artifacts/dbgate-latest.dmg || true
cp app/dist/*-mac_x64.dmg artifacts/dbgate-latest-x64.dmg || true
cp app/dist/*-mac_arm64.dmg artifacts/dbgate-latest-arm64.dmg || true
mv app/dist/*.exe artifacts/ || true
mv app/dist/*.zip artifacts/ || true
+16
View File
@@ -8,6 +8,22 @@ Builds:
- linux - application for linux
- win - application for Windows
### 6.1.0
- ADDED: Fulltext search in DB model and connections, highlight searched names
- ADDED: Tab preview mode configuration #963
- CHANGED: Single-click to open server connection/database + ability to configure this #959
- ADDED: Option to align numbers to right in data grid #957
- FIXED: Cursor Becomes Stuck When Escaping "Case" #954
- ADDED: Postgres GEOGRAPHY types are shown on map, event when executing query #948
- FIXED: Error displaying CLOB and NCLOB in Oracle
- FIXED: Analysing of foreign keys in Postgres and MS SQL, when the same FKS are used across different schemas
- ADDED: Support of views, procedures, functions to Oracle. Added integration tests for Oracle
- ADDED: Display "No rows" message, quick add new row
- ADDED: Choose default database from list
- ADDED: Default database is automatically selected on connect
- ADDED: Apple-Silicon-only build for Mac #949
- ADDED: Display comment into tables and column list #755
### 6.0.0
- ADDED: Order or filter the indexes for huge tables #922
- ADDED: Empty string filters
+1 -1
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "6.0.1-beta.3",
"version": "6.1.0",
"name": "dbgate-all",
"workspaces": [
"packages/*",
+2 -2
View File
@@ -222,7 +222,7 @@ module.exports = {
},
test_meta: true,
test(connection) {
test({ connection, requestDbList }) {
const subprocess = fork(
global['API_PACKAGE'] || process.argv[1],
[
@@ -237,7 +237,7 @@ module.exports = {
}
);
pipeForkLogs(subprocess);
subprocess.send(connection);
subprocess.send({ connection, requestDbList });
return new Promise(resolve => {
subprocess.on('message', resp => {
if (handleProcessCommunication(resp, subprocess)) return;
+8 -2
View File
@@ -16,13 +16,19 @@ Platform: ${process.platform}
function start() {
childProcessChecker();
process.on('message', async connection => {
process.on('message', async args => {
// @ts-ignore
const { connection, requestDbList } = args;
if (handleProcessCommunication(connection)) return;
try {
const driver = requireEngineDriver(connection);
const dbhan = await connectUtility(driver, connection, 'app');
const res = await driver.getVersion(dbhan);
process.send({ msgtype: 'connected', ...res });
let databases = undefined;
if (requestDbList) {
databases = await driver.listDatabases(dbhan);
}
process.send({ msgtype: 'connected', ...res, databases });
await driver.close(dbhan);
} catch (e) {
console.error(e);
+16 -5
View File
@@ -90,11 +90,16 @@ export function tokenizeBySearchFilter(text: string, filter: string): { text: st
for (const item of res) {
const indexes = [];
for (const char of token) {
const index = item.text.indexOf(char, indexes.length > 0 ? indexes[indexes.length - 1] + 1 : 0);
if (index < 0) {
indexes.push(-1);
if (indexes.length == 0 && char == item.text[0]?.toUpperCase()) {
// handle first letter of camelcase
indexes.push(0);
} else {
indexes.push(index);
const index = item.text.indexOf(char, indexes.length > 0 ? indexes[indexes.length - 1] + 1 : 0);
if (index < 0) {
indexes.push(-1);
} else {
indexes.push(index);
}
}
}
if (indexes.some(x => x < 0)) {
@@ -129,7 +134,13 @@ export function tokenizeBySearchFilter(text: string, filter: string): { text: st
res = nextres;
}
return res.filter(x => x.text.length > 0);
res = res.filter(x => x.text.length > 0);
if (res.length == 1 && !res[0].isMatch) {
return null;
}
return res;
// const result = [];
// let lastMatch = 0;
+1 -1
View File
@@ -20,7 +20,7 @@
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.5",
"@tsconfig/svelte": "^1.0.0",
"ace-builds": "^1.4.8",
"ace-builds": "^1.36.5",
"chart.js": "^4.4.2",
"chartjs-adapter-moment": "^1.0.0",
"cross-env": "^7.0.3",
@@ -89,6 +89,7 @@
{passProps}
isExpandedBySearch={filter && item.isChildMatched}
{filter}
isMainMatched={item.isMainMatched}
/>
{/each}
</div>
+24 -7
View File
@@ -6,6 +6,8 @@
import AppObjectListItem from './AppObjectListItem.svelte';
import { writable } from 'svelte/store';
import Link from '../elements/Link.svelte';
import { focusedConnectionOrDatabase } from '../stores';
import { tick } from 'svelte';
export let list;
export let module;
@@ -40,33 +42,33 @@
const matchResult = matcher ? matcher(data) : true;
let isMatched = true;
let isMainMatched = true;
let isChildMatched = true;
if (matchResult == false) {
isMatched = false;
isChildMatched = false;
isMainMatched = false;
} else if (matchResult == 'child') {
isMatched = true;
isChildMatched = true;
isMainMatched = false;
} else if (matchResult == 'main') {
isMatched = true;
isChildMatched = false;
} else if (matchResult == 'none') {
isMatched = false;
isChildMatched = false;
isMainMatched = false;
} else if (matchResult == 'both') {
isMatched = true;
isChildMatched = !module.disableShowChildrenWithParentMatch;
}
const group = groupFunc ? groupFunc(data) : undefined;
return { group, data, isMatched, isChildMatched };
return { group, data, isMatched, isChildMatched, isMainMatched };
})
);
$: filtered = dataLabeled.filter(x => x.isMatched).map(x => x.data);
$: childrenMatched = dataLabeled.filter(x => x.isChildMatched).map(x => x.data);
$: mainMatched = dataLabeled.filter(x => x.isMainMatched).map(x => x.data);
// let filtered = [];
@@ -93,10 +95,24 @@
return res;
}
function setExpandLimited() {
expandLimited = true;
}
$: groups = groupFunc ? extendGroups(_.groupBy(dataLabeled, 'group'), emptyGroupNames) : null;
$: listLimited = isExpandedBySearch && !expandLimited ? filtered.slice(0, Math.min(filter.trim().length, 3)) : list;
$: listLimited = isExpandedBySearch && !expandLimited ? filtered.slice(0, filter.trim().length < 3 ? 1 : 3) : list;
$: isListLimited = isExpandedBySearch && listLimited.length < filtered.length;
$: listMissingItems = isListLimited ? filtered.slice(listLimited.length) : [];
$: if (
$focusedConnectionOrDatabase &&
listMissingItems.some(
x => $focusedConnectionOrDatabase.conid == x?.connection?._id && $focusedConnectionOrDatabase.database == x?.name
)
) {
tick().then(setExpandLimited);
}
</script>
{#if groupFunc}
@@ -136,6 +152,7 @@
{disableContextMenu}
{filter}
isExpandedBySearch={filter && childrenMatched.includes(data)}
isMainMatched={filter && mainMatched.includes(data)}
{passProps}
{getIsExpanded}
{setIsExpanded}
@@ -24,6 +24,7 @@
export let passProps;
export let getIsExpanded = null;
export let setIsExpanded = null;
export let isMainMatched = false;
let isExpandedCore = false;
@@ -70,7 +71,8 @@
{filter}
{passProps}
{isExpandedBySearch}
isExpandedOnlyBySearch={isExpandedBySearch && !isExpanded}
{isExpanded}
{isMainMatched}
/>
</div>
{/if}
@@ -2,7 +2,7 @@
export const extractKey = ({ columnName }) => columnName;
export const createMatcher =
(filter, cfg = DEFAULT_SEARCH_SETTINGS) =>
(filter, cfg = DEFAULT_OBJECT_SEARCH_SETTINGS) =>
data => {
const filterArgs = [];
if (cfg.columnName) filterArgs.push(data.columnName);
@@ -21,7 +21,7 @@
import { renameDatabaseObjectDialog, alterDatabaseDialog } from '../utility/alterDatabaseTools';
import AppObjectCore from './AppObjectCore.svelte';
import { DEFAULT_SEARCH_SETTINGS } from '../stores';
import { DEFAULT_OBJECT_SEARCH_SETTINGS } from '../stores';
import { filterName } from 'dbgate-tools';
export let data;
@@ -1,14 +1,24 @@
<script context="module">
export const extractKey = data => data._id;
export const createMatcher = filter => props => {
const { _id, displayName, server } = props;
const databases = getLocalStorage(`database_list_${_id}`) || [];
return filterNameCompoud(
filter,
[displayName, server],
databases.map(x => x.name)
);
};
export const createMatcher =
(filter, cfg = DEFAULT_CONNECTION_SEARCH_SETTINGS) =>
props => {
const { _id, displayName, server, user, engine } = props;
const databases = getLocalStorage(`database_list_${_id}`) || [];
const match = (engine || '').match(/^([^@]*)@/);
const engineDisplay = match ? match[1] : engine;
return filterNameCompoud(
filter,
[
cfg.displayName ? displayName : null,
cfg.server ? server : null,
cfg.user ? user : null,
cfg.engine ? engineDisplay : null,
],
cfg.database ? databases.map(x => x.name) : []
);
};
export function openConnection(connection, disableExpand = false) {
if (connection.singleDatabase) {
if (getOpenedSingleDatabaseConnections().includes(connection._id)) {
@@ -40,6 +50,10 @@
expandedConnections.update(x => _.uniq([...x, connection._id]));
}
if (connection.defaultDatabase) {
switchCurrentDatabase({ connection, name: connection.defaultDatabase });
}
// if (!config.runAsPortal && getCurrentSettings()['defaultAction.connectionClick'] != 'connect') {
// expandedConnections.update(x => _.uniq([...x, connection._id]));
// }
@@ -92,6 +106,7 @@
import AppObjectCore from './AppObjectCore.svelte';
import {
currentDatabase,
DEFAULT_CONNECTION_SEARCH_SETTINGS,
expandedConnections,
extensions,
focusedConnectionOrDatabase,
@@ -3,7 +3,7 @@
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
export const createMatcher =
(filter, cfg = DEFAULT_SEARCH_SETTINGS) =>
(filter, cfg = DEFAULT_OBJECT_SEARCH_SETTINGS) =>
({ schemaName, pureName, objectComment, tableEngine, columns, objectTypeField, createSql }) => {
const mainArgs = [];
const childArgs = [];
@@ -18,6 +18,8 @@
if (cfg.columnComment) childArgs.push(column.columnComment);
if (cfg.columnDataType) childArgs.push(column.dataType);
}
} else if (objectTypeField == 'collections') {
if (cfg.collectionName) mainArgs.push(pureName);
} else {
if (cfg.sqlObjectName) mainArgs.push(pureName);
if (cfg.sqlObjectText) childArgs.push(createSql);
@@ -885,7 +887,35 @@
return createDatabaseObjectMenu(data);
}
export function handleObjectClick(data, { forceNewTab = false, tabPreviewMode = false, focusTab = false }) {
export function handleObjectClick(data, clickAction) {
// on:click={() => handleObjectClick(data, { tabPreviewMode: true })}
// on:middleclick={() => handleObjectClick(data, { forceNewTab: true })}
// on:dblclick={() => handleObjectClick(data, { tabPreviewMode: false, focusTab: true })}
const openDetailOnArrows = getOpenDetailOnArrowsSettings();
let forceNewTab = false;
let tabPreviewMode = false;
let focusTab = false;
switch (clickAction) {
case 'leftClick':
tabPreviewMode = true;
break;
case 'middleClick':
forceNewTab = true;
break;
case 'dblClick':
focusTab = true;
break;
case 'keyEnter':
focusTab = true;
break;
case 'keyArrow':
if (!openDetailOnArrows) return;
tabPreviewMode = true;
break;
}
return handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
}
</script>
@@ -895,7 +925,7 @@
import AppObjectCore from './AppObjectCore.svelte';
import {
currentDatabase,
DEFAULT_SEARCH_SETTINGS,
DEFAULT_OBJECT_SEARCH_SETTINGS,
extensions,
getActiveTab,
getCurrentSettings,
@@ -937,16 +967,12 @@
import { openImportExportTab } from '../utility/importExportTools';
import { defaultDatabaseObjectAppObjectActions, matchDatabaseObjectAppObject } from './appObjectTools';
import { getSupportedScriptTemplates } from '../utility/applyScriptTemplate';
import { getBoolSettingsValue } from '../settings/settingsTools';
import { getBoolSettingsValue, getOpenDetailOnArrowsSettings } from '../settings/settingsTools';
import { isProApp } from '../utility/proTools';
export let data;
export let passProps;
function handleClick({ forceNewTab = false, tabPreviewMode = false, focusTab = false } = {}) {
handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
}
function createMenu() {
return createDatabaseObjectMenu(data, passProps?.connection);
}
@@ -981,9 +1007,9 @@
onUnpin={isPinned ? () => pinnedTables.update(list => list.filter(x => !testEqual(x, data))) : null}
extInfo={getExtInfo(data)}
isChoosed={matchDatabaseObjectAppObject($selectedDatabaseObjectAppObject, data)}
on:click={() => handleClick({ tabPreviewMode: true })}
on:middleclick={() => handleClick({ forceNewTab: true })}
on:dblclick={() => handleClick({ tabPreviewMode: false, focusTab: true })}
on:click={() => handleObjectClick(data, 'leftClick')}
on:middleclick={() => handleObjectClick(data, 'middleClick')}
on:dblclick={() => handleObjectClick(data, 'dblClick')}
on:expand
on:dragstart
on:dragenter
@@ -2,7 +2,7 @@
export const extractKey = ({ columnName }) => columnName;
export const createMatcher =
(filter, cfg = DEFAULT_SEARCH_SETTINGS) =>
(filter, cfg = DEFAULT_OBJECT_SEARCH_SETTINGS) =>
data => {
const filterArgs = [];
if (cfg.sqlObjectText) filterArgs.push(data.lineData);
@@ -15,7 +15,7 @@
<script lang="ts">
import AppObjectCore from './AppObjectCore.svelte';
import { filterName } from 'dbgate-tools';
import { DEFAULT_SEARCH_SETTINGS } from '../stores';
import { DEFAULT_OBJECT_SEARCH_SETTINGS } from '../stores';
export let data;
</script>
@@ -11,8 +11,11 @@
export let data;
export let passProps;
export let isExpandedOnlyBySearch;
export let isExpandedBySearch;
export let isExpanded;
export let isMainMatched;
$: isExpandedOnlyBySearch = isExpandedBySearch && !isExpanded;
$: databases = useDatabaseList({ conid: isExpandedOnlyBySearch ? null : data._id });
$: dbList = isExpandedOnlyBySearch ? getLocalStorage(`database_list_${data._id}`) || [] : $databases || [];
@@ -24,6 +27,6 @@
list={_.sortBy(dbList, x => x.sortOrder ?? x.name).map(db => ({ ...db, connection: data }))}
module={databaseAppObject}
{passProps}
{filter}
filter={isMainMatched ? '' : filter}
{isExpandedBySearch}
/>
+18 -6
View File
@@ -7,20 +7,32 @@
export let icon = 'icon chevron-down';
export let menu;
export let asyncMenu = undefined;
export let narrow = false;
export let square = true;
export let disabled = false;
let domButton;
function handleClick() {
let domButton;
let isLoading = false;
async function handleClick() {
if (disabled) return;
let items = menu;
if (asyncMenu) {
isLoading = true;
items = await asyncMenu();
isLoading = false;
}
const rect = domButton.getBoundingClientRect();
const left = rect.left;
const top = rect.bottom;
currentDropDownMenu.set({ left, top, items: menu });
currentDropDownMenu.set({ left, top, items });
}
</script>
<InlineButton square {narrow} on:click={handleClick} bind:this={domButton} {disabled}>
<FontIcon {icon} />
<InlineButton {square} {narrow} on:click={handleClick} bind:this={domButton} {disabled}>
<FontIcon icon={isLoading ? 'icon loading' : icon} />
</InlineButton>
@@ -54,6 +54,8 @@
// don't parse JSON for explicit data types
$: jsonParsedValue = !editorTypes?.explicitDataType && isJsonLikeLongString(value) ? safeJsonParse(value) : null;
$: showHint = allowHintField && rowData && _.some(col.hintColumnNames, hintColumnName => rowData[hintColumnName]);
</script>
<td
@@ -68,11 +70,12 @@
class:isDeleted
class:isAutofillSelected
class:isFocusedColumn
class:alignRight={_.isNumber(value) && !showHint}
{style}
>
<CellValue {rowData} {value} {jsonParsedValue} {editorTypes} />
{#if allowHintField && rowData && _.some(col.hintColumnNames, hintColumnName => rowData[hintColumnName])}
{#if showHint}
<span class="hint"
>{col.hintColumnNames.map(hintColumnName => rowData[hintColumnName]).join(col.hintColumnDelimiter || ' ')}</span
>
@@ -199,4 +202,9 @@
overflow: visible;
cursor: crosshair;
}
.alignRight {
color: var(--theme-icon-green);
text-align: var(--data-grid-numbers-align);
}
</style>
+39 -6
View File
@@ -416,7 +416,6 @@
import GenerateSqlFromDataModal from '../modals/GenerateSqlFromDataModal.svelte';
import { showModal } from '../modals/modalTools';
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
import { findCommand } from '../commands/runCommand';
import { openJsonDocument } from '../tabs/JsonTab.svelte';
import EditJsonModal from '../modals/EditJsonModal.svelte';
import { apiCall } from '../utility/api';
@@ -430,8 +429,7 @@
import { openJsonLinesData } from '../utility/openJsonLinesData';
import contextMenuActivator from '../utility/contextMenuActivator';
import InputTextModal from '../modals/InputTextModal.svelte';
import hasPermission from '../utility/hasPermission';
export let onLoadNextData = undefined;
export let grider = undefined;
export let display: GridDisplay = undefined;
@@ -483,6 +481,7 @@
let domFocusField;
let domHorizontalScroll;
let domVerticalScroll;
let domContainer;
let currentCell = topLeftCell;
let selectedCells = [topLeftCell];
@@ -492,7 +491,7 @@
let autofillSelectedCells = emptyCellArray;
const domFilterControlsRef = createRef({});
let isGridFocused=false;
let isGridFocused = false;
const tabid = getContext('tabid');
@@ -596,6 +595,7 @@
selectedCells = [cell];
await tick();
scrollIntoView(cell);
domFocusField?.focus();
}
export async function cloneRows() {
@@ -1748,6 +1748,11 @@
// { text: 'Copy as JSON', onClick: () => copyToClipboardCore('json') },
],
},
{
text: 'Paste',
onClick: () => domFocusField.paste(),
keyText: 'CtrlOrCommand+V',
},
{ placeTag: 'switch' },
{ divider: true },
{ placeTag: 'save' },
@@ -1838,11 +1843,11 @@
{/if}
</div>
{:else if isDynamicStructure && isLoadedAll && grider?.rowCount == 0}
<div>
<div class="ml-2">
<ErrorInfo
alignTop
message={grider.editable
? 'No rows loaded, check filter or add new documents. You could copy documents from ohter collections/tables with Copy advanved/Copy as JSON command.'
? 'No rows loaded, check filter or add new documents. You could copy documents from other collections/tables with Copy advanved/Copy as JSON command.'
: 'No rows loaded'}
/>
{#if display.filterCount > 0}
@@ -1867,9 +1872,15 @@
class:data-grid-focused={isGridFocused}
bind:clientWidth={containerWidth}
bind:clientHeight={containerHeight}
bind:this={domContainer}
use:contextMenu={buildMenu}
use:contextMenuActivator={activator}
on:wheel={handleGridWheel}
on:click={e => {
if (e.target == domContainer) {
domFocusField?.focus();
}
}}
>
<input
type="text"
@@ -2014,6 +2025,24 @@
{/each}
</tbody>
</table>
{#if !isDynamicStructure && isLoadedAll && grider?.rowCount == 0}
<div class="no-rows-info ml-2">
<div class="mb-3">
<ErrorInfo alignTop message="No rows loaded" icon="img info" />
</div>
{#if display.filterCount > 0}
<FormStyledButton value="Reset filter" on:click={() => display.clearFilters()} />
{/if}
{#if grider.editable}
<FormStyledButton value="Add row" on:click={insertNewRow} />
{/if}
{#if onOpenQuery}
<FormStyledButton value="Open Query" on:click={onOpenQuery} />
{/if}
</div>
{/if}
<HorizontalScrollBar
minimum={0}
maximum={maxScrollColumn}
@@ -2092,4 +2121,8 @@
right: 40px;
bottom: 20px;
}
.no-rows-info {
margin-top: 60px;
}
</style>
@@ -9,7 +9,7 @@
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
import SearchInput from '../elements/SearchInput.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import ColumnManagerRow from './ColumnManagerRow.svelte';
import TokenizedFilteredText from '../widgets/TokenizedFilteredText.svelte';
export let managerSize;
export let display: GridDisplay;
@@ -21,7 +21,6 @@
$: foreignKeys = display?.baseTable?.foreignKeys || [];
$: dependencies = display?.baseTable?.dependencies || [];
</script>
<SearchBoxWrapper>
@@ -46,7 +45,7 @@
>
<FontIcon icon="img link" />
<div class="ml-1 nowrap">
{fk.refTableName}
<TokenizedFilteredText text={fk.refTableName} {filter} />
({fk.columns.map(x => x.columnName).join(', ')})
</div>
</div>
@@ -70,7 +69,7 @@
>
<FontIcon icon="img reference" />
<div class="ml-1 nowrap">
{fk.pureName}
<TokenizedFilteredText text={fk.pureName} {filter} />
({fk.columns.map(x => x.columnName).join(', ')})
</div>
</div>
@@ -89,5 +88,4 @@
.link:hover {
text-decoration: underline;
}
</style>
@@ -9,6 +9,7 @@
export let disabled = false;
export let defaultValue;
export let menu;
export let asyncMenu;
const { values, setFieldValue } = getFormContext();
@@ -26,5 +27,5 @@
value={$values[name] ?? defaultValue}
on:input={e => setFieldValue(name, e.target['value'])}
/>
<DropDownButton {menu} {disabled} />
<DropDownButton {menu} {asyncMenu} {disabled} />
</div>
+1
View File
@@ -287,6 +287,7 @@
'img import': 'mdi mdi-database-import color-icon-green',
'img export': 'mdi mdi-database-export color-icon-green',
'img transform': 'mdi mdi-rotate-orbit color-icon-blue',
'img tip': 'mdi mdi-lightbulb-on color-icon-yellow',
};
</script>
+4 -4
View File
@@ -82,7 +82,7 @@
submenuOffset = hoverOffset;
return;
}
if (item.switchStore) {
if (item.switchStore && item.switchValue) {
item.switchStore.update(x => ({
...x,
[item.switchValue]: !x[item.switchValue],
@@ -142,12 +142,12 @@
>
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled} class:bold={item.isBold}>
<span>
{#if item.switchStoreGetter}
{#if item.switchValue && item.switchStoreGetter}
{#key switchIndex}
{#if item.switchStoreGetter()[item.switchValue]}
<FontIcon icon="icon checkbox-marked" padRight />
<FontIcon icon="icon check" padRight />
{:else}
<FontIcon icon="icon checkbox-blank" padRight />
<FontIcon icon="icon invisible-box" padRight />
{/if}
{/key}
{/if}
@@ -16,8 +16,12 @@
import FormColorField from '../forms/FormColorField.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import FormDropDownTextField from '../forms/FormDropDownTextField.svelte';
import { getConnectionLabel } from 'dbgate-tools';
const { values } = getFormContext();
export let getDatabaseList;
export let currentConnection;
const { values, setFieldValue } = getFormContext();
const electron = getElectron();
$: authType = $values.authType;
@@ -69,6 +73,14 @@
'me-central-1',
'sa-east-1',
];
async function createDatabasesMenu() {
const databases = await getDatabaseList();
return databases.map(db => ({
text: db.name,
onClick: () => setFieldValue('defaultDatabase', db.name),
}));
}
</script>
<FormSelectField
@@ -377,11 +389,13 @@
{/if}
{#if driver?.showConnectionField('defaultDatabase', $values, showConnectionFieldArgs)}
<FormTextField
<FormDropDownTextField
label="Default database"
name="defaultDatabase"
disabled={isConnected}
data-testid="ConnectionDriverFields_defaultDatabase"
asyncMenu={createDatabasesMenu}
placeholder="(not selected - optional)"
/>
{/if}
@@ -412,7 +426,8 @@
templateProps={{ noMargin: true }}
disabled={isConnected}
data-testid="ConnectionDriverFields_displayName"
/>
placeholder={getConnectionLabel(currentConnection)}
/>
</div>
<div class="col-6 mr-1">
<FormColorField
+26 -1
View File
@@ -100,7 +100,7 @@ ORDER BY
{ label: 'Connection', slot: 2 },
{ label: 'Themes', slot: 3 },
{ label: 'Default Actions', slot: 4 },
{ label: 'Confirmations', slot: 5 },
{ label: 'Behaviour', slot: 5 },
{ label: 'Other', slot: 6 },
]}
>
@@ -138,6 +138,8 @@ ORDER BY
defaultValue="10"
/>
<FormCheckboxField name="dataGrid.alignNumbersRight" label="Align numbers to right" defaultValue={false} />
<div class="heading">SQL editor</div>
<div class="flex">
@@ -331,6 +333,24 @@ ORDER BY
/>
</svelte:fragment>
<svelte:fragment slot="5">
<div class="heading">Behaviour</div>
<FormCheckboxField name="behaviour.useTabPreviewMode" label="Use tab preview mode" defaultValue={true} />
<div class="tip">
<FontIcon icon="img tip" /> When you single-click or select a file in the "Tables, Views, Functions" view, it
is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing
tables and don't want every visited table to have its own tab. When you start editing the table or use double-click
to open the table from the "Tables" view, a new tab is dedicated to that table.
</div>
<FormCheckboxField
name="behaviour.openDetailOnArrows"
label="Open detail on keyboard navigation"
defaultValue={true}
disabled={values['behaviour.useTabPreviewMode'] === false}
/>
<div class="heading">Confirmations</div>
<FormCheckboxField name="skipConfirm.tableDataSave" label="Skip confirmation when saving table data (SQL)" />
@@ -405,6 +425,11 @@ ORDER BY
margin-top: var(--dim-large-form-margin);
}
.tip {
margin-left: var(--dim-large-form-margin);
margin-top: var(--dim-large-form-margin);
}
.themes {
overflow-x: scroll;
display: flex;
@@ -36,3 +36,10 @@ export function getConnectionClickActionSetting(): 'connect' | 'openDetails' | '
export function getDatabaseClickActionSetting(): 'switch' | 'none' {
return getStringSettingsValue('defaultAction.databaseClick', 'switch');
}
export function getOpenDetailOnArrowsSettings(): boolean {
return (
getBoolSettingsValue('behaviour.useTabPreviewMode', true) &&
getBoolSettingsValue('behaviour.openDetailOnArrows', true)
);
}
+29 -4
View File
@@ -161,7 +161,7 @@ export const lastUsedDefaultActions = writableWithStorage({}, 'lastUsedDefaultAc
export const selectedDatabaseObjectAppObject = writable(null);
export const focusedConnectionOrDatabase = writable<{ conid: string; database?: string; connection: any }>(null);
export const DEFAULT_SEARCH_SETTINGS = {
export const DEFAULT_OBJECT_SEARCH_SETTINGS = {
collectionName: true,
schemaName: false,
tableName: true,
@@ -175,11 +175,24 @@ export const DEFAULT_SEARCH_SETTINGS = {
tableEngine: false,
};
export const DEFAULT_CONNECTION_SEARCH_SETTINGS = {
displayName: true,
server: true,
user: false,
engine: false,
database: true,
};
export const databaseObjectAppObjectSearchSettings = writableWithStorage(
DEFAULT_SEARCH_SETTINGS,
DEFAULT_OBJECT_SEARCH_SETTINGS,
'databaseObjectAppObjectSearchSettings'
);
export const connectionAppObjectSearchSettings = writableWithStorage(
DEFAULT_CONNECTION_SEARCH_SETTINGS,
'connectionAppObjectSearchSettings'
);
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
$extensions.themes.find(x => x.themeClassName == $currentTheme)
);
@@ -205,6 +218,9 @@ export const visibleTitleBar = derived(useSettings(), $settings => {
// console.log('nativeMenuOnStartup', nativeMenuOnStartup);
return !$settings['app.fullscreen'] && !nativeMenuOnStartup;
});
export const alignDataGridNumbersToRight = derived(useSettings(), $settings => {
return !!$settings?.['dataGrid.alignNumbersRight'];
});
export const visibleHamburgerMenuWidget = derived(useSettings(), $settings => {
const electron = getElectron();
@@ -218,6 +234,7 @@ subscribeCssVariable(visibleSelectedWidget, x => (x ? 1 : 0), '--dim-visible-lef
subscribeCssVariable(leftPanelWidth, x => `${x}px`, '--dim-left-panel-width');
subscribeCssVariable(visibleTitleBar, x => (x ? 1 : 0), '--dim-visible-titlebar');
subscribeCssVariable(lockedDatabaseMode, x => (x ? 0 : 1), '--dim-visible-tabs-databases');
subscribeCssVariable(alignDataGridNumbersToRight, x => (x ? 'right' : 'left'), '--data-grid-numbers-align');
let activeTabIdValue = null;
activeTabId.subscribe(value => {
@@ -379,10 +396,18 @@ lastUsedDefaultActions.subscribe(value => {
});
export const getLastUsedDefaultActions = () => lastUsedDefaultActionsValue;
let databaseObjectAppObjectSearchSettingsValue: typeof DEFAULT_SEARCH_SETTINGS = {
...DEFAULT_SEARCH_SETTINGS,
let databaseObjectAppObjectSearchSettingsValue: typeof DEFAULT_OBJECT_SEARCH_SETTINGS = {
...DEFAULT_OBJECT_SEARCH_SETTINGS,
};
databaseObjectAppObjectSearchSettings.subscribe(value => {
databaseObjectAppObjectSearchSettingsValue = value;
});
export const getDatabaseObjectAppObjectSearchSettings = () => databaseObjectAppObjectSearchSettingsValue;
let connectionAppObjectSearchSettingsValue: typeof DEFAULT_CONNECTION_SEARCH_SETTINGS = {
...DEFAULT_CONNECTION_SEARCH_SETTINGS,
};
connectionAppObjectSearchSettings.subscribe(value => {
connectionAppObjectSearchSettingsValue = value;
});
export const getConnectionAppObjectSearchSettings = () => connectionAppObjectSearchSettingsValue;
+34 -14
View File
@@ -63,15 +63,16 @@
const testIdRef = createRef(0);
async function handleTest(e) {
async function handleTest(e, requestDbList = false) {
isTesting = true;
testIdRef.update(x => x + 1);
const testid = testIdRef.get();
const resp = await apiCall('connections/test', e.detail);
const resp = await apiCall('connections/test', { connection: e.detail, requestDbList });
if (testIdRef.get() != testid) return;
isTesting = false;
sqlConnectResult = resp;
return resp;
}
function handleCancelTest() {
@@ -80,6 +81,10 @@
}
function getCurrentConnection() {
return getCurrentConnectionCore($values, driver);
}
function getCurrentConnectionCore(values, driver) {
const allProps = [
'databaseFile',
'useDatabaseUrl',
@@ -94,15 +99,15 @@
'socketPath',
'serviceName',
];
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, $values, { config: $config }));
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, values, { config: $config }));
const omitProps = _.difference(allProps, visibleProps);
if (!$values.defaultDatabase) omitProps.push('singleDatabase');
if (!values.defaultDatabase) omitProps.push('singleDatabase');
let connection: Dictionary<string | boolean> = _.omit($values, omitProps);
let connection: Dictionary<string | boolean> = _.omit(values, omitProps);
if (driver?.beforeConnectionSave) connection = driver?.beforeConnectionSave(connection);
if (driver?.showConnectionTab('sshTunnel', $values)) {
if (!$values.useSshTunnel) {
if (driver?.showConnectionTab('sshTunnel', values)) {
if (!values.useSshTunnel) {
connection = _.omitBy(connection, (v, k) => k.startsWith('ssh'));
}
} else {
@@ -110,8 +115,8 @@
connection = _.omitBy(connection, (v, k) => k.startsWith('ssh'));
}
if (driver?.showConnectionTab('ssl', $values)) {
if (!$values.useSsl) {
if (driver?.showConnectionTab('ssl', values)) {
if (!values.useSsl) {
connection = _.omitBy(connection, (v, k) => k.startsWith('ssl'));
}
} else {
@@ -122,6 +127,8 @@
return connection;
}
$: currentConnection = getCurrentConnectionCore($values, driver);
async function handleSave() {
let connection = getCurrentConnection();
connection = {
@@ -190,6 +197,14 @@
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
// $: console.log('CONN VALUES', $values);
async function getDatabaseList() {
const resp = await handleTest({ detail: getCurrentConnection() }, true);
if (resp && resp.msgtype == 'connected') {
return resp.databases;
}
return [];
}
</script>
<FormProviderCore template={FormFieldTemplateLarge} {values}>
@@ -202,6 +217,7 @@
{
label: 'General',
component: ConnectionDriverFields,
props: { getDatabaseList, currentConnection },
},
driver?.showConnectionTab('sshTunnel', $values) && {
label: 'SSH Tunnel',
@@ -223,20 +239,24 @@
<div class="buttons">
{#if onlyTestButton}
{#if isTesting}
<FormButton value="Cancel test" on:click={handleCancelTest} data-testid="ConnectionTab_buttonCancelTest" />
<FormButton
value="Cancel test"
on:click={handleCancelTest}
data-testid="ConnectionTab_buttonCancelTest"
/>
{:else}
<FormButton value="Test connection" on:click={handleTest} data-testid="ConnectionTab_buttonDisconnect" />
{/if}
{:else if isConnected}
<FormButton value="Disconnect" on:click={handleDisconnect} data-testid='ConnectionTab_buttonDisconnect' />
<FormButton value="Disconnect" on:click={handleDisconnect} data-testid="ConnectionTab_buttonDisconnect" />
{:else}
<FormButton value="Connect" on:click={handleConnect} data-testid='ConnectionTab_buttonConnect' />
<FormButton value="Connect" on:click={handleConnect} data-testid="ConnectionTab_buttonConnect" />
{#if isTesting}
<FormButton value="Cancel test" on:click={handleCancelTest} />
{:else}
<FormButton value="Test" on:click={handleTest} data-testid='ConnectionTab_buttonTest' />
<FormButton value="Test" on:click={handleTest} data-testid="ConnectionTab_buttonTest" />
{/if}
<FormButton value="Save" on:click={handleSave} data-testid='ConnectionTab_buttonSave' />
<FormButton value="Save" on:click={handleSave} data-testid="ConnectionTab_buttonSave" />
{/if}
</div>
<div class="test-result">
+8
View File
@@ -7,6 +7,7 @@ import localforage from 'localforage';
import stableStringify from 'json-stable-stringify';
import { saveAllPendingEditorData } from '../query/useEditorData';
import { getConnectionInfo } from './metadataLoaders';
import { getBoolSettingsValue } from '../settings/settingsTools';
function findFreeNumber(numbers: number[]) {
if (numbers.length == 0) return 1;
@@ -22,6 +23,13 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
let existing = null;
if (!getBoolSettingsValue('behaviour.useTabPreviewMode', true) && newTab.tabPreviewMode) {
newTab = {
...newTab,
tabPreviewMode: false,
};
}
const { savedFile, savedFolder, savedFilePath, conid, database } = newTab.props || {};
if (conid && database) {
@@ -16,6 +16,7 @@
let isListFocused = false;
let domDiv = null;
let lastInputMethod = null;
export let hideContent = false;
function handleKeyDown(ev) {
@@ -25,6 +26,11 @@
const selected = getSelectedObject();
const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
if (index < 0) {
focusFirst();
return;
}
if (index == 0 && diff < 0) {
onFocusFilterBox?.();
return;
@@ -41,7 +47,7 @@
if (listInstance[newIndex]) {
selectedObjectStore.set(listInstance[newIndex]);
handleObjectClick?.(listInstance[newIndex], { tabPreviewMode: true });
handleObjectClick?.(listInstance[newIndex], 'keyArrow');
}
if (newIndex == 0) {
@@ -58,7 +64,7 @@
ev.preventDefault();
}
if (ev.keyCode == keycodes.enter) {
handleObjectClick?.(getSelectedObject(), { tabPreviewMode: false, focusTab: true });
handleObjectClick?.(getSelectedObject(), 'keyEnter');
ev.preventDefault();
}
if (ev.keyCode == keycodes.pageDown) {
@@ -72,14 +78,14 @@
if (ev.keyCode == keycodes.home) {
if (listInstance[0]) {
selectedObjectStore.set(listInstance[0]);
handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
handleObjectClick?.(listInstance[0], 'keyArrow');
onScrollTop?.();
}
}
if (ev.keyCode == keycodes.end) {
if (listInstance[listInstance.length - 1]) {
selectedObjectStore.set(listInstance[listInstance.length - 1]);
handleObjectClick?.(listInstance[listInstance.length - 1], { tabPreviewMode: true });
handleObjectClick?.(listInstance[listInstance.length - 1], 'keyArrow');
}
}
if (ev.keyCode == keycodes.numPadAdd) {
@@ -110,17 +116,18 @@
domDiv?.focus();
if (listInstance[0]) {
selectedObjectStore.set(listInstance[0]);
handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
handleObjectClick?.(listInstance[0], 'keyArrow');
onScrollTop?.();
}
}
async function handleFocus() {
async function handleFocus(e) {
isListFocused = true;
// await tick();
await sleep(100);
// console.log('ON FOCUS AFTER SLEEP');
if (lastInputMethod == 'mouse') {
return;
}
const listInstance = _.isFunction(list) ? list() : list;
const selected = getSelectedObject();
const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
@@ -130,7 +137,7 @@
const index2 = _.findIndex(listInstance, x => selectedObjectMatcher(x, focused));
if (index2 >= 0) {
selectedObjectStore.set(focused);
handleObjectClick?.(focused, { tabPreviewMode: true });
handleObjectClick?.(focused, 'keyArrow');
return;
}
}
@@ -139,6 +146,15 @@
}
</script>
<svelte:window
on:keydown={() => {
lastInputMethod = 'keyboard';
}}
on:mousedown={() => {
lastInputMethod = 'mouse';
}}
/>
<div
tabindex="0"
on:keydown={handleKeyDown}
+44 -13
View File
@@ -20,6 +20,8 @@
getFocusedConnectionOrDatabase,
currentDatabase,
getCurrentConfig,
connectionAppObjectSearchSettings,
getConnectionAppObjectSearchSettings,
} from '../stores';
import runCommand from '../commands/runCommand';
import { filterName, getConnectionLabel } from 'dbgate-tools';
@@ -38,7 +40,13 @@
import { switchCurrentDatabase } from '../utility/common';
import openNewTab from '../utility/openNewTab';
import { openConnection } from '../appobj/ConnectionAppObject.svelte';
import { getConnectionClickActionSetting } from '../settings/settingsTools';
import {
getBoolSettingsValue,
getConnectionClickActionSetting,
getDatabaseClickActionSetting,
getOpenDetailOnArrowsSettings,
} from '../settings/settingsTools';
import DropDownButton from '../buttons/DropDownButton.svelte';
const connections = useConnectionList();
const serverStatus = useServerStatus();
@@ -182,6 +190,21 @@
{ text: 'Delete', onClick: handleDelete },
];
}
function createSearchMenu() {
const res = [];
res.push({ label: 'Search by:', isBold: true, disabled: true });
res.push({ label: 'Display name', switchValue: 'displayName' });
res.push({ label: 'Server', switchValue: 'server' });
res.push({ label: 'User', switchValue: 'user' });
res.push({ label: 'Database engine', switchValue: 'engine' });
res.push({ label: 'Database name', switchValue: 'database' });
return res.map(item => ({
...item,
switchStore: connectionAppObjectSearchSettings,
switchStoreGetter: getConnectionAppObjectSearchSettings,
}));
}
</script>
<SearchBoxWrapper>
@@ -194,6 +217,7 @@
}}
/>
<CloseSearchButton bind:filter />
<DropDownButton icon="icon filter" menu={createSearchMenu} square={!!filter} narrow={false} />
{#if $commandsCustomized['new.connection']?.enabled}
<InlineButton
on:click={() => runCommand('new.connection')}
@@ -239,30 +263,35 @@
onFocusFilterBox={text => {
domFilter?.focus(text);
}}
handleObjectClick={(data, options) => {
handleObjectClick={(data, clickAction) => {
const connectionClickAction = getConnectionClickActionSetting();
const databaseClickAction = getDatabaseClickActionSetting();
const openDetailOnArrows = getOpenDetailOnArrowsSettings();
if (data.database) {
if (options.focusTab) {
if ($openedSingleDatabaseConnections.includes(data.conid)) {
switchCurrentDatabase({ connection: data.connection, name: data.database });
} else {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
// console.log('FOCUSING DB', passProps);
// passProps?.onFocusSqlObjectList?.();
if (databaseClickAction == 'switch' && clickAction == 'leftClick') {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
if (clickAction == 'keyEnter' || clickAction == 'dblClick') {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
} else {
if (options.focusTab) {
if (clickAction == 'keyEnter' || clickAction == 'dblClick') {
openConnection(data.connection);
} else {
const config = getCurrentConfig();
if (config.runAsPortal == false && !config.storageDatabase && connectionClickAction == 'openDetails') {
if (
config.runAsPortal == false &&
!config.storageDatabase &&
connectionClickAction == 'openDetails' &&
(clickAction == 'leftClick' || (clickAction == 'keyArrow' && openDetailOnArrows))
) {
openNewTab({
title: getConnectionLabel(data.connection),
icon: 'img connection',
tabComponent: 'ConnectionTab',
tabPreviewMode: options.tabPreviewMode,
tabPreviewMode: true,
props: {
conid: data.conid,
},
@@ -289,6 +318,7 @@
...passProps,
connectionColorFactory: $connectionColorFactory,
showPinnedInsteadOfUnpin: true,
searchSettings: $connectionAppObjectSearchSettings,
}}
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
setIsExpanded={(data, value) => {
@@ -316,6 +346,7 @@
passProps={{
connectionColorFactory: $connectionColorFactory,
showPinnedInsteadOfUnpin: true,
searchSettings: $connectionAppObjectSearchSettings,
}}
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
setIsExpanded={(data, value) => {
@@ -13,6 +13,7 @@
import { filterName } from 'dbgate-tools';
import { currentDatabase, focusedConnectionOrDatabase, getFocusedConnectionOrDatabase } from '../stores';
import { switchCurrentDatabase } from '../utility/common';
import { getConnectionClickActionSetting, getDatabaseClickActionSetting } from '../settings/settingsTools';
export let connection;
@@ -81,9 +82,16 @@
onFocusFilterBox={text => {
domFilter?.focus(text);
}}
handleObjectClick={(data, options) => {
handleObjectClick={(data, clickAction) => {
const connectionClickAction = getConnectionClickActionSetting();
const databaseClickAction = getDatabaseClickActionSetting();
if (data.database) {
if (options.focusTab) {
if (databaseClickAction == 'switch' && clickAction == 'leftClick') {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
if (clickAction == 'keyEnter' || clickAction == 'dblClick') {
switchCurrentDatabase({ connection: data.connection, name: data.database });
}
}
@@ -129,8 +129,9 @@
function createSearchMenu() {
const res = [];
res.push({ label: 'Search by:', isBold: true, disabled: true });
if (driver?.databaseEngineTypes?.includes('document')) {
res.push({ label: 'Collection names' });
res.push({ label: 'Collection names', switchValue: 'collectionName' });
}
if (driver?.databaseEngineTypes?.includes('sql')) {
res.push({ label: 'Schema name', switchValue: 'schemaName' });
@@ -216,9 +217,7 @@
}}
/>
<CloseSearchButton bind:filter />
{#if filter}
<DropDownButton icon="icon filter" menu={createSearchMenu} />
{/if}
<DropDownButton icon="icon filter" menu={createSearchMenu} square={!!filter} narrow={false} />
{#if !filter}
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
{/if}
@@ -250,7 +249,7 @@
selectedObjectStore={selectedDatabaseObjectAppObject}
getSelectedObject={getSelectedDatabaseObjectAppObject}
selectedObjectMatcher={matchDatabaseObjectAppObject}
handleObjectClick={(data, options) => databaseObjectAppObject.handleObjectClick(data, options)}
handleObjectClick={(data, clickAction) => databaseObjectAppObject.handleObjectClick(data, clickAction)}
onScrollTop={() => {
domContainer?.scrollTop();
}}
@@ -3,7 +3,7 @@ const stream = require('stream');
const driverBase = require('../frontend/driver');
const Analyser = require('./Analyser');
const { createBulkInsertStreamBase, makeUniqueColumnNames } = global.DBGATE_PACKAGES['dbgate-tools'];
const { makeUniqueColumnNames } = global.DBGATE_PACKAGES['dbgate-tools'];
const createOracleBulkInsertStream = require('./createOracleBulkInsertStream');
let platformInfo;
@@ -12,16 +12,11 @@ let oracledbValue;
function getOracledb() {
if (!oracledbValue) {
oracledbValue = require('oracledb');
oracledbValue.fetchAsString = [oracledbValue.CLOB, oracledbValue.NCLOB];
}
return oracledbValue;
}
/*
pg.types.setTypeParser(1082, 'text', val => val); // date
pg.types.setTypeParser(1114, 'text', val => val); // timestamp without timezone
pg.types.setTypeParser(1184, 'text', val => val); // timestamp
*/
function extractOracleColumns(result) {
if (!result /*|| !result.fields */) return [];
const res = result.map(fld => ({
+1 -1
View File
@@ -30,12 +30,12 @@
"prepublishOnly": "yarn build"
},
"devDependencies": {
"wkx": "^0.5.0",
"dbgate-plugin-tools": "^1.0.7",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"wkx": "^0.5.0",
"pg-copy-streams": "^6.0.6",
"dbgate-query-splitter": "^4.11.2",
"dbgate-tools": "^6.0.0-alpha.1",
@@ -134,16 +134,8 @@ const drivers = driverBases.map(driverBase => ({
database,
};
const datatypes = await this.query(
dbhan,
`SELECT oid AS datatypeid, typname AS datatypename FROM pg_type WHERE typname in ('geography')`
);
const typeIdToName = datatypes.rows.reduce((acc, cur) => {
acc[cur.datatypeid] = cur.datatypename;
return acc;
}, {});
const datatypes = await this.query(dbhan, `SELECT oid, typname FROM pg_type WHERE typname in ('geography')`);
const typeIdToName = _.fromPairs(datatypes.rows.map(cur => [cur.oid, cur.typname]));
dbhan['typeIdToName'] = typeIdToName;
if (isReadOnly) {
+4 -4
View File
@@ -2685,10 +2685,10 @@ accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
ace-builds@^1.4.8:
version "1.33.1"
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.33.1.tgz#a37904fb2d37756733346c688dcb235662877d6e"
integrity sha512-pj5mcXV1n3s86UI4SWUt8X0ltN8cTaYcvF76cSmvy5i2ZDtXX9KkjVcYTGkCV7ox6VUrzqHByeqH0xRsMjXi4g==
ace-builds@^1.36.5:
version "1.36.5"
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.36.5.tgz#ae9cc7a32eccc2f484926131c00545cd6b78a6a6"
integrity sha512-mZ5KVanRT6nLRDLqtG/1YQQLX/gZVC/v526cm1Ru/MTSlrbweSmqv2ZT0d2GaHpJq035MwCMIrj+LgDAUnDXrg==
acorn-globals@^4.1.0:
version "4.3.4"