Merge branch 'feature/tab-preview-mode'

This commit is contained in:
Jan Prochazka
2024-11-22 10:43:24 +01:00
35 changed files with 954 additions and 384 deletions

View File

@@ -14,6 +14,7 @@
export let module = null;
export let isBold = false;
export let isChoosed = false;
export let isBusy = false;
export let statusIcon = undefined;
export let statusIconBefore = undefined;
@@ -66,11 +67,21 @@
}
// $: console.log(title, indentLevel);
let domDiv;
$: if (isBold && domDiv) {
domDiv.scrollIntoView({ block: 'nearest', inline: 'nearest' });
}
$: if (isChoosed && domDiv) {
domDiv.scrollIntoView({ block: 'nearest', inline: 'nearest' });
}
</script>
<div
class="main"
class:isBold
class:isChoosed
draggable={true}
on:click={handleClick}
on:mouseup={handleMouseUp}
@@ -83,6 +94,7 @@
on:dragenter
on:dragend
on:drop
bind:this={domDiv}
>
{#if checkedObjectsStore}
<CheckboxField
@@ -180,6 +192,12 @@
.isBold {
font-weight: bold;
}
.isChoosed {
background-color: var(--theme-bg-3);
}
:global(.app-object-list-focused) .isChoosed {
background-color: var(--theme-bg-selected);
}
.status {
margin-left: 5px;
}

View File

@@ -28,6 +28,7 @@
export let emptyGroupNames = [];
export let collapsedGroupNames = writable([]);
export let onChangeFilteredList;
$: filtered = !groupFunc
? list.filter(data => {

View File

@@ -509,9 +509,9 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
extInfo={data.extInfo}
icon="img database"
colorMark={passProps?.connectionColorFactory &&
passProps?.connectionColorFactory({ conid: _.get(data.connection, '_id'), database: data.name }, null, null, false)}
isBold={_.get($currentDatabase, 'connection._id') == _.get(data.connection, '_id') &&
extractDbNameFromComposite(_.get($currentDatabase, 'name')) == data.name}
passProps?.connectionColorFactory({ conid: data?.connection?._id, database: data.name }, null, null, false)}
isBold={$currentDatabase?.connection?._id == data?.connection?._id &&
extractDbNameFromComposite($currentDatabase?.name) == data.name}
on:click={() => switchCurrentDatabase(data)}
on:dragstart
on:dragenter

View File

@@ -29,12 +29,18 @@
views: 'ViewDataTab',
matviews: 'ViewDataTab',
queries: 'QueryDataTab',
procedures: 'SqlObjectTab',
functions: 'SqlObjectTab',
};
function createMenusCore(
objectTypeField,
driver
): {
function createScriptTemplatesSubmenu(objectTypeField) {
return {
label: 'SQL template',
submenu: getSupportedScriptTemplates(objectTypeField),
};
}
interface DbObjMenuItem {
label?: string;
tab?: string;
forceNewTab?: boolean;
@@ -53,34 +59,22 @@
isExport?: boolean;
isImport?: boolean;
isActiveChart?: boolean;
isShowSql?: boolean;
scriptTemplate?: string;
sqlGeneratorProps?: any;
isDropCollection?: boolean;
isRenameCollection?: boolean;
isDuplicateCollection?: boolean;
}[] {
submenu?: DbObjMenuItem[];
}
function createMenusCore(objectTypeField, driver): DbObjMenuItem[] {
switch (objectTypeField) {
case 'tables':
return [
...defaultDatabaseObjectAppObjectActions['tables'],
{
label: 'Open data',
tab: 'TableDataTab',
forceNewTab: true,
},
{
label: 'Open form',
tab: 'TableDataTab',
forceNewTab: true,
initialData: {
grid: {
isFormView: true,
},
},
},
{
label: 'Open structure',
tab: 'TableStructureTab',
icon: 'img table-structure',
divider: true,
},
{
label: 'Design query',
@@ -93,6 +87,33 @@
forceNewTab: true,
icon: 'img perspective',
},
createScriptTemplatesSubmenu('tables'),
{
label: 'SQL generator',
submenu: [
{
label: 'CREATE TABLE',
sqlGeneratorProps: {
createTables: true,
createIndexes: true,
createForeignKeys: true,
},
},
{
label: 'DROP TABLE',
sqlGeneratorProps: {
dropTables: true,
dropReferences: true,
},
},
{
label: 'INSERT',
sqlGeneratorProps: {
insert: true,
},
},
],
},
{
divider: true,
},
@@ -142,50 +163,12 @@
label: 'Open active chart',
isActiveChart: true,
},
{
divider: true,
},
{
label: 'SQL: CREATE TABLE',
scriptTemplate: 'CREATE TABLE',
},
{
label: 'SQL: SELECT',
scriptTemplate: 'SELECT',
},
{
label: 'SQL Generator: CREATE TABLE',
sqlGeneratorProps: {
createTables: true,
createIndexes: true,
createForeignKeys: true,
},
},
{
label: 'SQL Generator: DROP TABLE',
sqlGeneratorProps: {
dropTables: true,
dropReferences: true,
},
},
{
label: 'SQL Generator: INSERT',
sqlGeneratorProps: {
insert: true,
},
},
];
case 'views':
return [
...defaultDatabaseObjectAppObjectActions['views'],
{
label: 'Open data',
tab: 'ViewDataTab',
forceNewTab: true,
},
{
label: 'Open structure',
tab: 'TableStructureTab',
icon: 'img view-structure',
divider: true,
},
{
label: 'Design query',
@@ -197,6 +180,27 @@
forceNewTab: true,
icon: 'img perspective',
},
createScriptTemplatesSubmenu('views'),
{
label: 'SQL generator',
submenu: [
{
label: 'CREATE VIEW',
sqlGeneratorProps: {
createViews: true,
},
},
{
label: 'DROP VIEW',
sqlGeneratorProps: {
dropViews: true,
},
},
],
},
{
divider: true,
},
hasPermission('dbops/model/edit') && {
label: 'Drop view',
isDrop: true,
@@ -219,48 +223,12 @@
label: 'Open active chart',
isActiveChart: true,
},
{
divider: true,
},
{
label: 'SQL: CREATE VIEW',
scriptTemplate: 'CREATE OBJECT',
},
{
label: 'SQL: ALTER VIEW',
scriptTemplate: 'ALTER OBJECT',
},
{
label: 'SQL: CREATE TABLE',
scriptTemplate: 'CREATE TABLE',
},
{
label: 'SQL: SELECT',
scriptTemplate: 'SELECT',
},
{
label: 'SQL Generator: CREATE VIEW',
sqlGeneratorProps: {
createViews: true,
},
},
{
label: 'SQL Generator: DROP VIEW',
sqlGeneratorProps: {
dropViews: true,
},
},
];
case 'matviews':
return [
...defaultDatabaseObjectAppObjectActions['matviews'],
{
label: 'Open data',
tab: 'ViewDataTab',
forceNewTab: true,
},
{
label: 'Open structure',
tab: 'TableStructureTab',
divider: true,
},
hasPermission('dbops/model/edit') && {
label: 'Drop view',
@@ -272,10 +240,31 @@
isRename: true,
requiresWriteAccess: true,
},
{
divider: true,
},
{
label: 'Query designer',
isQueryDesigner: true,
},
createScriptTemplatesSubmenu('matviews'),
{
label: 'SQL generator',
submenu: [
{
label: 'CREATE MATERIALIZED VIEW',
sqlGeneratorProps: {
createMatviews: true,
},
},
{
label: 'DROP MATERIALIZED VIEW',
sqlGeneratorProps: {
dropMatviews: true,
},
},
],
},
{
divider: true,
},
@@ -288,37 +277,6 @@
label: 'Open active chart',
isActiveChart: true,
},
{
divider: true,
},
{
label: 'SQL: CREATE MATERIALIZED VIEW',
scriptTemplate: 'CREATE OBJECT',
},
{
label: 'SQL: ALTER MATERIALIZED VIEW',
scriptTemplate: 'ALTER OBJECT',
},
{
label: 'SQL: CREATE TABLE',
scriptTemplate: 'CREATE TABLE',
},
{
label: 'SQL: SELECT',
scriptTemplate: 'SELECT',
},
{
label: 'SQL Generator: CREATE MATERIALIZED VIEW',
sqlGeneratorProps: {
createMatviews: true,
},
},
{
label: 'SQL Generator: DROP MATERIALIZED VIEW',
sqlGeneratorProps: {
dropMatviews: true,
},
},
];
case 'queries':
return [
@@ -330,6 +288,10 @@
];
case 'procedures':
return [
...defaultDatabaseObjectAppObjectActions['procedures'],
{
divider: true,
},
hasPermission('dbops/model/edit') && {
label: 'Drop procedure',
isDrop: true,
@@ -340,33 +302,31 @@
isRename: true,
requiresWriteAccess: true,
},
createScriptTemplatesSubmenu('procedures'),
{
label: 'SQL: CREATE PROCEDURE',
scriptTemplate: 'CREATE OBJECT',
},
{
label: 'SQL: ALTER PROCEDURE',
scriptTemplate: 'ALTER OBJECT',
},
{
label: 'SQL: EXECUTE',
scriptTemplate: 'EXECUTE PROCEDURE',
},
{
label: 'SQL Generator: CREATE PROCEDURE',
sqlGeneratorProps: {
createProcedures: true,
},
},
{
label: 'SQL Generator: DROP PROCEDURE',
sqlGeneratorProps: {
dropProcedures: true,
},
label: 'SQL generator',
submenu: [
{
label: 'CREATE PROCEDURE',
sqlGeneratorProps: {
createProcedures: true,
},
},
{
label: 'DROP PROCEDURE',
sqlGeneratorProps: {
dropProcedures: true,
},
},
],
},
];
case 'functions':
return [
...defaultDatabaseObjectAppObjectActions['functions'],
{
divider: true,
},
hasPermission('dbops/model/edit') && {
label: 'Drop function',
isDrop: true,
@@ -377,43 +337,30 @@
isRename: true,
requiresWriteAccess: true,
},
createScriptTemplatesSubmenu('functions'),
{
label: 'SQL: CREATE FUNCTION',
scriptTemplate: 'CREATE OBJECT',
},
{
label: 'SQL: ALTER FUNCTION',
scriptTemplate: 'ALTER OBJECT',
},
{
label: 'SQL Generator: CREATE FUNCTION',
sqlGeneratorProps: {
createFunctions: true,
},
},
{
label: 'SQL Generator: DROP FUNCTION',
sqlGeneratorProps: {
dropFunctions: true,
},
label: 'SQL generator',
submenu: [
{
label: 'CREATE FUNCTION',
sqlGeneratorProps: {
createFunctions: true,
},
},
{
label: 'DROP FUNCTION',
sqlGeneratorProps: {
dropFunctions: true,
},
},
],
},
];
case 'collections':
return [
...defaultDatabaseObjectAppObjectActions['collections'],
{
label: 'Open data',
tab: 'CollectionDataTab',
forceNewTab: true,
},
{
label: 'Open JSON',
tab: 'CollectionDataTab',
forceNewTab: true,
initialData: {
grid: {
isJsonView: true,
},
},
divider: true,
},
{
label: 'Design perspective query',
@@ -659,15 +606,30 @@
// fixedTargetPureName: data.pureName,
// },
// });
// } else if (menu.isShowSql) {
// openNewTab({
// title: data.pureName,
// icon: 'img sql-file',
// tabComponent: 'SqlObjectTab',
// tabPreviewMode: true,
// props: {
// conid: data.conid,
// database: data.database,
// schemaName: data.schemaName,
// pureName: data.pureName,
// objectTypeField: data.objectTypeField,
// },
// });
} else {
openDatabaseObjectDetail(
menu.tab,
menu.scriptTemplate,
data,
{ ...data, defaultActionId: menu.defaultActionId },
menu.forceNewTab,
menu.initialData,
menu.icon,
data
data,
!!menu.defaultActionId
);
}
}
@@ -697,11 +659,12 @@
export async function openDatabaseObjectDetail(
tabComponent,
scriptTemplate,
{ schemaName, pureName, conid, database, objectTypeField },
{ schemaName, pureName, conid, database, objectTypeField, defaultActionId },
forceNewTab?,
initialData?,
icon?,
appObjectData?
appObjectData?,
tabPreviewMode?
) {
const connection = await getConnectionInfo({ conid });
const tooltip = `${getConnectionLabel(connection)}\n${database}\n${fullDisplayName({
@@ -711,12 +674,16 @@
openNewTab(
{
title: scriptTemplate ? 'Query #' : getObjectTitle(connection, schemaName, pureName),
// title: getObjectTitle(connection, schemaName, pureName),
title: tabComponent ? getObjectTitle(connection, schemaName, pureName) : 'Query #',
tooltip,
icon: icon || (scriptTemplate ? 'img sql-file' : databaseObjectIcons[objectTypeField]),
tabComponent: scriptTemplate ? 'QueryTab' : tabComponent,
icon:
icon ||
(scriptTemplate || tabComponent == 'SqlObjectTab' ? 'img sql-file' : databaseObjectIcons[objectTypeField]),
tabComponent: tabComponent ?? 'QueryTab',
appObject: 'DatabaseObjectAppObject',
appObjectData,
tabPreviewMode,
props: {
schemaName,
pureName,
@@ -724,6 +691,7 @@
database,
objectTypeField,
initialArgs: scriptTemplate ? { scriptTemplate } : null,
defaultActionId,
},
},
initialData,
@@ -731,31 +699,62 @@
);
}
export function handleDatabaseObjectClick(data, forceNewTab = false) {
export function handleDatabaseObjectClick(
data,
{ forceNewTab = false, tabPreviewMode = false, focusTab = false } = {}
) {
const { schemaName, pureName, conid, database, objectTypeField } = data;
const driver = findEngineDriver(data, getExtensions());
const configuredAction = getCurrentSettings()[`defaultAction.dbObjectClick.${objectTypeField}`];
const overrideMenu = createMenus(objectTypeField, driver).find(x => x.label && x.label == configuredAction);
if (overrideMenu) {
databaseObjectMenuClickHandler(data, overrideMenu);
const activeTab = getActiveTab();
const activeTabProps = activeTab?.props || {};
const activeDefaultActionId = activeTab?.props?.defaultActionId;
if (matchDatabaseObjectAppObject(data, activeTabProps)) {
if (!tabPreviewMode) {
openedTabs.update(tabs => {
return tabs.map(tab => ({
...tab,
tabPreviewMode: tab.tabid == activeTab.tabid ? false : tab.tabPreviewMode,
focused: focusTab && tab.tabid == activeTab.tabid ? true : tab.focused,
}));
});
}
return;
}
const availableDefaultActions = defaultDatabaseObjectAppObjectActions[objectTypeField];
const configuredActionId = getCurrentSettings()[`defaultAction.dbObjectClick.${objectTypeField}`];
const prefferedAction =
availableDefaultActions.find(x => x.defaultActionId == activeDefaultActionId) ??
availableDefaultActions.find(x => x.defaultActionId == configuredActionId) ??
availableDefaultActions[0];
// console.log('activeTab', activeTab);
// const overrideMenu = createMenus(objectTypeField, driver).find(x => x.label && x.label == configuredAction);
// if (overrideMenu) {
// databaseObjectMenuClickHandler(data, overrideMenu);
// return;
// }
openDatabaseObjectDetail(
defaultTabs[objectTypeField],
defaultTabs[objectTypeField] ? null : 'CREATE OBJECT',
prefferedAction.tab,
activeTabProps?.scriptTemplate,
{
schemaName,
pureName,
conid,
database,
objectTypeField,
defaultActionId: prefferedAction.defaultActionId,
},
forceNewTab,
null,
null,
data
data,
tabPreviewMode
);
}
@@ -769,64 +768,74 @@
);
}
function menuItemMapper(menu, data, connection) {
if (menu.divider) return menu;
if (menu.isExport) {
return createQuickExportMenu(
fmt => async () => {
const coninfo = await getConnectionInfo(data);
exportQuickExportFile(
data.pureName,
{
functionName: menu.functionName,
props: {
connection: extractShellConnection(coninfo, data.database),
..._.pick(data, ['pureName', 'schemaName']),
},
},
fmt
);
},
{
onClick: () => {
openImportExportTab({
sourceStorageType: 'database',
sourceConnectionId: data.conid,
sourceDatabaseName: extractDbNameFromComposite(data.database),
sourceSchemaName: data.schemaName,
sourceList: [data.pureName],
});
// showModal(ImportExportModal, {
// initialValues: {
// sourceStorageType: 'database',
// sourceConnectionId: data.conid,
// sourceDatabaseName: data.database,
// sourceSchemaName: data.schemaName,
// sourceList: [data.pureName],
// },
// });
},
}
);
}
if (connection?.isReadOnly && menu.requiresWriteAccess) {
return null;
}
if (menu.submenu) {
return {
...menu,
submenu: menu.submenu.map(x => menuItemMapper(x, data, connection)),
};
}
return {
text: menu.label,
onClick: () => {
databaseObjectMenuClickHandler(data, menu);
},
};
}
export function createDatabaseObjectMenu(data, connection = null) {
const driver = findEngineDriver(data, getExtensions());
const { objectTypeField } = data;
return createMenus(objectTypeField, driver)
.filter(x => x)
.map(menu => {
if (menu.divider) return menu;
if (menu.isExport) {
return createQuickExportMenu(
fmt => async () => {
const coninfo = await getConnectionInfo(data);
exportQuickExportFile(
data.pureName,
{
functionName: menu.functionName,
props: {
connection: extractShellConnection(coninfo, data.database),
..._.pick(data, ['pureName', 'schemaName']),
},
},
fmt
);
},
{
onClick: () => {
openImportExportTab({
sourceStorageType: 'database',
sourceConnectionId: data.conid,
sourceDatabaseName: extractDbNameFromComposite(data.database),
sourceSchemaName: data.schemaName,
sourceList: [data.pureName],
});
// showModal(ImportExportModal, {
// initialValues: {
// sourceStorageType: 'database',
// sourceConnectionId: data.conid,
// sourceDatabaseName: data.database,
// sourceSchemaName: data.schemaName,
// sourceList: [data.pureName],
// },
// });
},
}
);
}
if (connection?.isReadOnly && menu.requiresWriteAccess) {
return null;
}
return {
text: menu.label,
onClick: () => {
databaseObjectMenuClickHandler(data, menu);
},
};
});
.map(menu => menuItemMapper(menu, data, connection));
}
function formatRowCount(value) {
@@ -838,6 +847,10 @@
export function createAppObjectMenu(data) {
return createDatabaseObjectMenu(data);
}
export function handleObjectClick(data, { forceNewTab = false, tabPreviewMode = false, focusTab = false }) {
return handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
}
</script>
<script lang="ts">
@@ -846,10 +859,13 @@
import {
currentDatabase,
extensions,
getActiveTab,
getCurrentSettings,
getExtensions,
openedConnections,
openedTabs,
pinnedTables,
selectedDatabaseObjectAppObject,
} from '../stores';
import openNewTab from '../utility/openNewTab';
import {
@@ -877,12 +893,15 @@
import { getDefaultFileFormat } from '../plugins/fileformats';
import hasPermission from '../utility/hasPermission';
import { openImportExportTab } from '../utility/importExportTools';
import { defaultDatabaseObjectAppObjectActions, matchDatabaseObjectAppObject } from './appObjectTools';
import { getSupportedScriptTemplates } from '../utility/applyScriptTemplate';
export let data;
export let passProps;
function handleClick(forceNewTab = false) {
handleDatabaseObjectClick(data, forceNewTab);
function handleClick({ forceNewTab = false, tabPreviewMode = false, focusTab = false } = {}) {
$selectedDatabaseObjectAppObject = _.pick(data, ['conid', 'database', 'objectTypeField', 'pureName', 'schemaName']);
handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
}
function createMenu() {
@@ -915,8 +934,10 @@
onPin={isPinned ? null : () => pinnedTables.update(list => [...list, data])}
onUnpin={isPinned ? () => pinnedTables.update(list => list.filter(x => !testEqual(x, data))) : null}
extInfo={getExtInfo(data)}
on:click={() => handleClick()}
on:middleclick={() => handleClick(true)}
isChoosed={matchDatabaseObjectAppObject($selectedDatabaseObjectAppObject, data)}
on:click={() => handleClick({ tabPreviewMode: true })}
on:middleclick={() => handleClick({ forceNewTab: true })}
on:dblclick={() => handleClick({ tabPreviewMode: false, focusTab: true })}
on:expand
on:dragstart
on:dragenter

View File

@@ -0,0 +1,80 @@
export function matchDatabaseObjectAppObject(obj1, obj2) {
return (
obj1?.objectTypeField == obj2?.objectTypeField &&
obj1?.conid == obj2?.conid &&
obj1?.database == obj2?.database &&
obj1?.pureName == obj2?.pureName &&
obj1?.schemaName == obj2?.schemaName
);
}
function getTableLikeActions(dataTab) {
return [
{
label: 'Open data',
tab: dataTab,
defaultActionId: 'openTable',
},
{
label: 'Open form',
tab: dataTab,
initialData: {
grid: {
isFormView: true,
},
},
defaultActionId: 'openForm',
},
{
label: 'Open structure',
tab: 'TableStructureTab',
icon: 'img table-structure',
defaultActionId: 'openStructure',
},
{
label: 'Show SQL',
tab: 'SqlObjectTab',
defaultActionId: 'showSql',
icon: 'img sql-file',
},
];
}
export const defaultDatabaseObjectAppObjectActions = {
tables: getTableLikeActions('TableDataTab'),
views: getTableLikeActions('ViewDataTab'),
matviews: getTableLikeActions('ViewDataTab'),
procedures: [
{
label: 'Show SQL',
tab: 'SqlObjectTab',
defaultActionId: 'showSql',
icon: 'img sql-file',
},
],
functions: [
{
label: 'Show SQL',
tab: 'SqlObjectTab',
defaultActionId: 'showSql',
icon: 'img sql-file',
},
],
collections: [
{
label: 'Open data',
tab: 'CollectionDataTab',
defaultActionId: 'openTable',
},
{
label: 'Open JSON',
tab: 'CollectionDataTab',
defaultActionId: 'openJson',
initialData: {
grid: {
isJsonView: true,
},
},
},
],
};

View File

@@ -142,6 +142,8 @@
},
});
}
let isColumnManagerFocused = false;
</script>
{#if allowChangeChangeSetStructure}
@@ -207,9 +209,13 @@
bind:this={domFocusField}
on:keydown={handleKeyDown}
on:focus={() => {
isColumnManagerFocused = true;
// activator.activate();
// invalidateCommands();
}}
on:blur={() => {
isColumnManagerFocused = false;
}}
on:copy={copyToClipboard}
/>
@@ -224,6 +230,7 @@
{database}
{tableInfo}
{setTableInfo}
{isColumnManagerFocused}
columnInfo={tableInfo?.columns?.[columnIndex]}
{columnIndex}
{allowChangeChangeSetStructure}

View File

@@ -22,6 +22,8 @@
export let columnInfo = null;
export let columnIndex = -1;
export let isColumnManagerFocused = false;
export let allowChangeChangeSetStructure = false;
$: addDataCommand = allowChangeChangeSetStructure;
@@ -49,6 +51,7 @@
else display.focusColumns([column.uniqueName]);
}}
class:isSelected
class:isFocused={isColumnManagerFocused}
on:click
on:mousedown
on:mousemove
@@ -123,6 +126,10 @@
}
.row.isSelected {
background: var(--theme-bg-3);
}
.row.isSelected.isFocused {
background: var(--theme-bg-selected);
}

View File

@@ -173,6 +173,9 @@
background: var(--theme-bg-volcano);
}
td.isSelected {
background: var(--theme-bg-3);
}
:global(.data-grid-focused) td.isSelected {
background: var(--theme-bg-selected);
}
td.isDeleted {

View File

@@ -472,7 +472,7 @@
export let dataEditorTypesBehaviourOverride = null;
const wheelRowCount = 5;
const tabVisible: any = getContext('tabVisible');
const tabFocused: any = getContext('tabFocused');
let containerHeight = 0;
let containerWidth = 0;
@@ -492,6 +492,8 @@
let autofillSelectedCells = emptyCellArray;
const domFilterControlsRef = createRef({});
let isGridFocused=false;
const tabid = getContext('tabid');
let unsubscribeDbRefresh;
@@ -1041,7 +1043,6 @@
return !hideGridLeftColumn;
}
$: autofillMarkerCell =
selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map(x => x[0])).length == 1
? [_.max(selectedCells.map(x => x[0])), _.max(selectedCells.map(x => x[1]))]
@@ -1134,7 +1135,7 @@
}
}
$: if ($tabVisible && domFocusField && focusOnVisible) {
$: if ($tabFocused && domFocusField && focusOnVisible) {
domFocusField.focus();
}
@@ -1863,6 +1864,7 @@
{:else}
<div
class="container"
class:data-grid-focused={isGridFocused}
bind:clientWidth={containerWidth}
bind:clientHeight={containerHeight}
use:contextMenu={buildMenu}
@@ -1877,10 +1879,14 @@
on:focus={() => {
activator.activate();
invalidateCommands();
isGridFocused = true;
}}
on:blur
on:paste={handlePaste}
on:copy={copyToClipboard}
on:blur={handleBlur}
on:blur={() => {
isGridFocused = false;
}}
/>
<table
class="table"

View File

@@ -7,6 +7,7 @@
$: searchValue = value || '';
export let isDebounced = false;
export let onFocusFilteredList = null;
let domInput;
@@ -14,9 +15,17 @@
if (e.keyCode == keycodes.escape) {
value = '';
}
if (e.keyCode == keycodes.downArrow || e.keyCode == keycodes.pageDown || e.keyCode == keycodes.enter) {
onFocusFilteredList?.();
e.preventDefault();
}
}
const debouncedSet = _.debounce(x => (value = x), 500);
export function focus() {
domInput.focus();
}
</script>
<input

View File

@@ -217,12 +217,12 @@
$: rowHeight = $dataGridRowHeight;
let currentCell = [0, 0];
const tabVisible: any = getContext('tabVisible');
const tabFocused: any = getContext('tabFocused');
const domCells = {};
let domFocusField;
$: if ($tabVisible && domFocusField && focusOnVisible) {
$: if ($tabFocused && domFocusField && focusOnVisible) {
domFocusField.focus();
}

View File

@@ -49,8 +49,8 @@
--theme-border: #555;
--theme-bg-hover: #112a45;
--theme-bg-selected: #15395b; /* blue-3 */
--theme-bg-hover: #555;
--theme-bg-selected: #1d4d7e; /* blue-4 */
--theme-bg-selected-point: #1765ad; /* blue-5 */
--theme-bg-statusbar-inv: #0050b3;

View File

@@ -42,7 +42,7 @@
--theme-border: #ccc;
--theme-bg-hover: #bae7ff;
--theme-bg-hover: #ccc;
--theme-bg-selected: #91d5ff; /* blue-3 */
--theme-bg-selected-point: #40a9ff; /* blue-5 */

View File

@@ -0,0 +1,18 @@
<script alng="ts">
import { defaultDatabaseObjectAppObjectActions } from '../appobj/appObjectTools';
import FormSelectField from '../forms/FormSelectField.svelte';
export let label;
export let objectTypeField;
</script>
<FormSelectField
{label}
name={`defaultAction.dbObjectClick.${objectTypeField}`}
isNative
defaultValue={defaultDatabaseObjectAppObjectActions[objectTypeField][0]?.defaultActionId}
options={defaultDatabaseObjectAppObjectActions[objectTypeField].map(x => ({
value: x.defaultActionId,
label: x.label,
}))}
/>

View File

@@ -38,6 +38,7 @@
import { useSettings } from '../utility/metadataLoaders';
import { derived } from 'svelte/store';
import { safeFormatDate } from 'dbgate-tools';
import FormDefaultActionField from './FormDefaultActionField.svelte';
const electron = getElectron();
let restartWarning = false;
@@ -288,56 +289,11 @@ ORDER BY
]}
/>
<FormSelectField
label="Table click"
name="defaultAction.dbObjectClick.tables"
isNative
defaultValue=""
options={[
{ value: '', label: 'Open data, or open existing' },
{ value: 'Open data', label: 'Open data (always new tab)' },
{ value: 'Open form', label: 'Open form (always new tab)' },
{ value: 'Open structure', label: 'Open structure' },
{ value: 'SQL: CREATE TABLE', label: 'SQL: CREATE' },
{ value: 'SQL: SELECT', label: 'SQL: SELECT' },
]}
/>
<FormSelectField
label="View click"
name="defaultAction.dbObjectClick.views"
isNative
defaultValue=""
options={[
{ value: '', label: 'Open data, or open existing' },
{ value: 'Open data', label: 'Open data (always new tab)' },
{ value: 'SQL: CREATE VIEW', label: 'SQL: CREATE' },
]}
/>
<FormSelectField
label="Materialized view click"
name="defaultAction.dbObjectClick.matviews"
isNative
defaultValue=""
options={[
{ value: '', label: 'Open data, or open existing' },
{ value: 'Open data', label: 'Open data (always new tab)' },
{ value: 'SQL: CREATE MATERIALIZED VIEW', label: 'SQL: CREATE' },
]}
/>
<FormSelectField
label="Procedure click"
name="defaultAction.dbObjectClick.procedures"
isNative
defaultValue=""
options={[
{ value: '', label: 'SQL: CREATE' },
{ value: 'SQL: EXECUTE', label: 'SQL: EXECUTE' },
// { value: 'SQL: CREATE PROCEDURE', label: 'SQL: CREATE' },
]}
/>
<FormDefaultActionField label="Table click" objectTypeField="tables" />
<FormDefaultActionField label="View click" objectTypeField="views" />
<FormDefaultActionField label="Materialized view click" objectTypeField="matviews" />
<FormDefaultActionField label="Procedure click" objectTypeField="procedures" />
<FormDefaultActionField label="Function click" objectTypeField="functions" />
</svelte:fragment>
<svelte:fragment slot="5">
<div class="heading">Confirmations</div>

View File

@@ -22,6 +22,8 @@ export interface TabDefinition {
tabOrder?: number;
multiTabIndex?: number;
unsaved?: boolean;
tabPreviewMode?: boolean;
focused?: boolean;
}
export function writableWithStorage<T>(defaultValue: T, storageName) {
@@ -91,7 +93,7 @@ export const openedConnections = writable([]);
export const temporaryOpenedConnections = writable([]);
export const openedSingleDatabaseConnections = writable([]);
export const expandedConnections = writable([]);
export const currentDatabase = writable(null);
export const currentDatabase = writableWithForage(null, 'currentDatabase');
export const openedTabs = writableWithForage<TabDefinition[]>([], getOpenedTabsStorageName(), x => [...(x || [])]);
export const copyRowsFormat = writableWithStorage('textWithoutHeaders', 'copyRowsFormat');
export const extensions = writable<ExtensionsDirectory>(null);
@@ -155,6 +157,8 @@ export const activeDbKeysStore = writableWithStorage({}, 'activeDbKeysStore');
export const appliedCurrentSchema = writable<string>(null);
export const loadingSchemaLists = writable({}); // dict [`${conid}::${database}`]: true
export const selectedDatabaseObjectAppObject = writable(null);
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
$extensions.themes.find(x => x.themeClassName == $currentTheme)
);
@@ -324,6 +328,12 @@ appliedCurrentSchema.subscribe(value => {
});
export const getAppliedCurrentSchema = () => appliedCurrentSchemaValue;
let selectedDatabaseObjectAppObjectValue = null;
selectedDatabaseObjectAppObject.subscribe(value => {
selectedDatabaseObjectAppObjectValue = value;
});
export const getSelectedDatabaseObjectAppObject = () => selectedDatabaseObjectAppObjectValue;
let openedModalsValue = [];
openedModals.subscribe(value => {
openedModalsValue = value;

View File

@@ -4,17 +4,22 @@
export let tabid;
export let tabVisible;
export let tabFocused;
export let tabComponent;
const tabVisibleStore = writable(tabVisible);
setContext('tabid', tabid);
setContext('tabVisible', tabVisibleStore);
const tabVisibleStore = writable(tabVisible);
setContext('tabVisible', tabVisibleStore);
$: tabVisibleStore.set(tabVisible);
const tabFocusedStore = writable(tabFocused);
setContext('tabFocused', tabFocusedStore);
$: tabFocusedStore.set(tabFocused);
</script>
<div class:tabVisible>
<svelte:component this={tabComponent} {...$$restProps} {tabid} {tabVisible} />
<svelte:component this={tabComponent} {...$$restProps} {tabid} {tabVisible} {tabFocused} />
</div>
<style>

View File

@@ -52,5 +52,6 @@
{tabid}
unsaved={openedTabsByTabId[tabid]?.unsaved}
tabVisible={tabid == shownTab?.tabid}
tabFocused={tabid == shownTab?.tabid && shownTab?.focused}
/>
{/each}

View File

@@ -241,7 +241,7 @@
const hasAnyOtherTab = getOpenedTabs().filter(x => !x.closedTime).length >= 1;
const hasAnyModalOpen = getOpenedModals().length > 0;
return hasAnyOtherTab && !hasConfirmModalOpen;
return hasAnyOtherTab && !hasAnyModalOpen;
},
onClick: closeCurrentTab,
});
@@ -323,6 +323,7 @@
import CloseTabModal from '../modals/CloseTabModal.svelte';
import SwitchDatabaseModal from '../modals/SwitchDatabaseModal.svelte';
import { getConnectionLabel } from 'dbgate-tools';
import { handleAfterTabClick } from '../utility/changeCurrentDbByTab';
export let multiTabIndex;
export let shownTab;
@@ -362,6 +363,7 @@
return;
}
setSelectedTab(tabid);
handleAfterTabClick();
};
const handleMouseDown = (e, tabid) => {
@@ -377,6 +379,17 @@
}
};
const handleDoubleClick = (e, tabid) => {
e.preventDefault();
openedTabs.update(tabs =>
tabs.map(x => ({
...x,
tabPreviewMode: x.tabid == tabid ? false : x.tabPreviewMode,
}))
);
};
const getContextMenu = tab => () => {
const { tabid, props, tabComponent, appObject, appObjectData } = tab;
@@ -596,9 +609,11 @@
class:selected={$draggingTab || $draggingDbGroup
? tab.tabid == $draggingTabTarget?.tabid
: tab.tabid == shownTab?.tabid}
class:preview={!!tab.tabPreviewMode}
on:click={e => handleTabClick(e, tab.tabid)}
on:mousedown={e => handleMouseDown(e, tab.tabid)}
on:mouseup={e => handleMouseUp(e, tab.tabid)}
on:dblclick={e => handleDoubleClick(e, tab.tabid)}
use:contextMenu={getContextMenu(tab)}
draggable={true}
on:dragstart={async e => {
@@ -742,6 +757,9 @@
.file-tab-item.selected {
background-color: var(--theme-bg-0);
}
.file-tab-item.preview {
font-style: italic;
}
.file-name {
margin-left: 5px;
white-space: nowrap;

View File

@@ -47,13 +47,13 @@
export let tabid;
export let savedFile;
const tabVisible: any = getContext('tabVisible');
const tabFocused: any = getContext('tabFocused');
export const activator = createActivator('FavoriteEditorTab', false);
let domEditor;
$: if ($tabVisible && domEditor) {
$: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus();
}

View File

@@ -27,14 +27,14 @@
export let tabid;
const tabVisible: any = getContext('tabVisible');
const tabFocused: any = getContext('tabFocused');
export const activator = createActivator('JsonEditorTab', false);
let domEditor;
let domToolStrip;
$: if ($tabVisible && domEditor) {
$: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus();
}

View File

@@ -81,14 +81,14 @@
let jslid = null;
const tabVisible: any = getContext('tabVisible');
const tabFocused: any = getContext('tabFocused');
export const activator = createActivator('JsonLinesEditorTab', false);
let domEditor;
let domToolStrip;
$: if ($tabVisible && domEditor) {
$: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus();
}

View File

@@ -41,13 +41,13 @@
export let tabid;
const tabVisible: any = getContext('tabVisible');
const tabFocused: any = getContext('tabFocused');
export const activator = createActivator('MarkdownEditorTab', false);
let domEditor;
$: if ($tabVisible && domEditor) {
$: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus();
}

View File

@@ -124,7 +124,7 @@
},
];
const tabVisible: any = getContext('tabVisible');
const tabFocused: any = getContext('tabFocused');
const timerLabel = useTimerLabel();
let busy = false;
@@ -182,7 +182,7 @@
invalidateCommands();
}
$: if ($tabVisible && domEditor) {
$: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus();
}
@@ -454,7 +454,7 @@
/>
{:else}
<AceEditor
mode={driver?.editorMode || 'text'}
mode={driver?.editorMode || 'sql'}
value={$editorState.value || ''}
splitterOptions={driver?.getQuerySplitterOptions('editor')}
options={{

View File

@@ -52,7 +52,7 @@
export let tabid;
const tabVisible: any = getContext('tabVisible');
const tabFocused: any = getContext('tabFocused');
const timerLabel = useTimerLabel();
let runnerId;
@@ -82,7 +82,7 @@
invalidateCommands();
}
$: if ($tabVisible && domEditor) {
$: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus();
}

View File

@@ -0,0 +1,113 @@
<script lang="ts" context="module">
const getCurrentEditor = () => getActiveComponent('SqlObjectTab');
registerCommand({
id: 'sqlObject.find',
category: 'SQL Object',
name: 'Find',
keyText: 'CtrlOrCommand+F',
testEnabled: () => getCurrentEditor() != null,
onClick: () => getCurrentEditor().find(),
});
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName', 'objectTypeField'];
</script>
<script lang="ts">
import { getContext } from 'svelte';
import AceEditor from '../query/AceEditor.svelte';
import invalidateCommands from '../commands/invalidateCommands';
import createActivator, { getActiveComponent } from '../utility/createActivator';
import ToolStripContainer from '../buttons/ToolStripContainer.svelte';
import { useConnectionInfo } from '../utility/metadataLoaders';
import { extensions } from '../stores';
import { findEngineDriver } from 'dbgate-tools';
import registerCommand from '../commands/registerCommand';
import applyScriptTemplate, { getSupportedScriptTemplates } from '../utility/applyScriptTemplate';
import LoadingInfo from '../elements/LoadingInfo.svelte';
import ToolStripCommandButton from '../buttons/ToolStripCommandButton.svelte';
import SelectField from '../forms/SelectField.svelte';
import { changeTab } from '../utility/common';
export let tabid;
export let appObjectData;
export let scriptTemplate;
export let schemaName;
export let pureName;
export let conid;
export let database;
export let objectTypeField;
$: appObjectData = {
schemaName,
pureName,
conid,
database,
objectTypeField,
};
$: defaultScriptTemplate = getSupportedScriptTemplates(appObjectData.objectTypeField)?.[0]?.scriptTemplate;
$: connection = useConnectionInfo({ conid });
$: driver = findEngineDriver($connection, $extensions);
const tabFocused: any = getContext('tabFocused');
export const activator = createActivator('SqlObjectTab', false);
let domEditor;
let domToolStrip;
$: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus();
}
export function find() {
domEditor.getEditor().execCommand('find');
}
function createMenu() {
return [{ command: 'sqlObject.find' }];
}
</script>
<ToolStripContainer>
{#await applyScriptTemplate(scriptTemplate ?? defaultScriptTemplate, $extensions, appObjectData)}
<LoadingInfo message="Loading script..." />
{:then sql}
<AceEditor
value={sql || ''}
readOnly
menu={createMenu()}
on:focus={() => {
activator.activate();
domToolStrip?.activate();
invalidateCommands();
}}
bind:this={domEditor}
mode={driver?.editorMode || 'sql'}
/>
{/await}
<svelte:fragment slot="toolstrip">
<SelectField
isNative
value={scriptTemplate ?? defaultScriptTemplate}
options={getSupportedScriptTemplates(appObjectData.objectTypeField).map(x => ({
label: x.label,
value: x.scriptTemplate,
}))}
on:change={e => {
changeTab(tabid, tab => ({
...tab,
props: {
...tab.props,
scriptTemplate: e.detail,
},
}));
}}
/>
</svelte:fragment>
</ToolStripContainer>

View File

@@ -29,13 +29,13 @@
export let tabid;
const tabVisible: any = getContext('tabVisible');
const tabFocused: any = getContext('tabFocused');
export const activator = createActivator('YamlEditorTab', false);
let domEditor;
$: if ($tabVisible && domEditor) {
$: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus();
}

View File

@@ -28,6 +28,7 @@ import * as ServerSummaryTab from './ServerSummaryTab.svelte';
import * as ProfilerTab from './ProfilerTab.svelte';
import * as DataDuplicatorTab from './DataDuplicatorTab.svelte';
import * as ImportExportTab from './ImportExportTab.svelte';
import * as SqlObjectTab from './SqlObjectTab.svelte';
import protabs from './index-pro';
@@ -62,5 +63,6 @@ export default {
ProfilerTab,
DataDuplicatorTab,
ImportExportTab,
SqlObjectTab,
...protabs,
};

View File

@@ -63,3 +63,79 @@ export default async function applyScriptTemplate(scriptTemplate, extensions, pr
return res;
}
export function getSupportedScriptTemplates(objectTypeField: string): { label: string; scriptTemplate: string }[] {
switch (objectTypeField) {
case 'tables':
return [
{ label: 'CREATE TABLE', scriptTemplate: 'CREATE TABLE' },
{ label: 'SELECT', scriptTemplate: 'SELECT' },
];
case 'views':
return [
{
label: 'CREATE VIEW',
scriptTemplate: 'CREATE OBJECT',
},
{
label: 'ALTER VIEW',
scriptTemplate: 'ALTER OBJECT',
},
{
label: 'CREATE TABLE',
scriptTemplate: 'CREATE TABLE',
},
{
label: 'SELECT',
scriptTemplate: 'SELECT',
},
];
case 'matviews':
return [
{
label: 'CREATE MATERIALIZED VIEW',
scriptTemplate: 'CREATE OBJECT',
},
{
label: 'ALTER MATERIALIZED VIEW',
scriptTemplate: 'ALTER OBJECT',
},
{
label: 'CREATE TABLE',
scriptTemplate: 'CREATE TABLE',
},
{
label: 'SELECT',
scriptTemplate: 'SELECT',
},
];
case 'procedures':
return [
{
label: 'CREATE PROCEDURE',
scriptTemplate: 'CREATE OBJECT',
},
{
label: 'ALTER PROCEDURE',
scriptTemplate: 'ALTER OBJECT',
},
{
label: 'EXECUTE',
scriptTemplate: 'EXECUTE PROCEDURE',
},
];
case 'functions':
return [
{
label: 'CREATE FUNCTION',
scriptTemplate: 'CREATE OBJECT',
},
{
label: ' ALTER FUNCTION',
scriptTemplate: 'ALTER OBJECT',
},
];
}
}

View File

@@ -1,35 +1,81 @@
import _ from 'lodash';
import { currentDatabase, getCurrentDatabase, getLockedDatabaseMode, openedTabs } from '../stores';
import {
currentDatabase,
getActiveTab,
getCurrentDatabase,
getLockedDatabaseMode,
openedTabs,
selectedDatabaseObjectAppObject,
} from '../stores';
import { shouldShowTab } from '../tabpanel/TabsPanel.svelte';
import { callWhenAppLoaded, getAppLoaded } from './appLoadManager';
import { getConnectionInfo } from './metadataLoaders';
import { switchCurrentDatabase } from './common';
let lastCurrentTab = null;
// let lastCurrentTab = null;
openedTabs.subscribe(value => {
const newCurrentTab = (value || []).find(x => x.selected);
if (newCurrentTab == lastCurrentTab) return;
if (getLockedDatabaseMode() && getCurrentDatabase()) return;
// openedTabs.subscribe(value => {
// const newCurrentTab = (value || []).find(x => x.selected);
// if (newCurrentTab == lastCurrentTab) return;
// if (getLockedDatabaseMode() && getCurrentDatabase()) return;
const lastTab = lastCurrentTab;
lastCurrentTab = newCurrentTab;
// if (lastTab?.tabComponent == 'ConnectionTab') return;
// const lastTab = lastCurrentTab;
// lastCurrentTab = newCurrentTab;
// // if (lastTab?.tabComponent == 'ConnectionTab') return;
if (newCurrentTab) {
const { conid, database } = newCurrentTab.props || {};
if (conid && database && (conid != lastTab?.props?.conid || database != lastTab?.props?.database)) {
const doWork = async () => {
const connection = await getConnectionInfo({ conid });
switchCurrentDatabase({
connection,
name: database,
});
};
callWhenAppLoaded(doWork);
}
// if (newCurrentTab) {
// const { conid, database } = newCurrentTab.props || {};
// if (conid && database && (conid != lastTab?.props?.conid || database != lastTab?.props?.database)) {
// const doWork = async () => {
// const connection = await getConnectionInfo({ conid });
// switchCurrentDatabase({
// connection,
// name: database,
// });
// };
// callWhenAppLoaded(doWork);
// }
// }
// });
export async function handleAfterTabClick() {
const currentTab = getActiveTab();
const { conid, database, objectTypeField, pureName, schemaName, defaultActionId } = currentTab?.props || {};
const db = getCurrentDatabase();
if (conid && database && (conid != db?.connection?._id || database != db?.name)) {
const connection = await getConnectionInfo({ conid });
switchCurrentDatabase({
connection,
name: database,
});
// const doWork = async () => {
// const connection = await getConnectionInfo({ conid });
// switchCurrentDatabase({
// connection,
// name: database,
// });
// };
// callWhenAppLoaded(doWork);
}
});
if (conid && database && objectTypeField && pureName && defaultActionId) {
selectedDatabaseObjectAppObject.set({
conid,
database,
objectTypeField,
pureName,
schemaName,
});
}
// focus current tab
openedTabs.update(tabs => {
return tabs.map(tab => ({
...tab,
focused: !!tab.selected && !tab.closedTime,
}));
});
}
currentDatabase.subscribe(currentDb => {
if (!getLockedDatabaseMode()) return;

View File

@@ -30,10 +30,23 @@ export function markTabSaved(tabid) {
openedTabs.update(files => files.map(tab => (tab.tabid == tabid ? { ...tab, unsaved: false } : tab)));
}
export function setSelectedTabFunc(files, tabid) {
export function setSelectedTabFunc(files, tabid, additionalProps = {}) {
return [
...(files || []).filter(x => x.tabid != tabid).map(x => ({ ...x, selected: false })),
...(files || []).filter(x => x.tabid == tabid).map(x => ({ ...x, selected: true })),
...(files || [])
.filter(x => x.tabid != tabid)
.map(x => ({
...x,
selected: false,
focused: false,
})),
...(files || [])
.filter(x => x.tabid == tabid)
.map(x => ({
...x,
selected: true,
focused: false,
...additionalProps,
})),
];
}

View File

@@ -22,6 +22,7 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
let existing = null;
const { savedFile, savedFolder, savedFilePath } = newTab.props || {};
const { tabPreviewMode } = newTab;
if (savedFile || savedFilePath) {
existing = oldTabs.find(
x =>
@@ -49,7 +50,9 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
}
if (existing) {
openedTabs.update(tabs => setSelectedTabFunc(tabs, existing.tabid));
openedTabs.update(tabs =>
setSelectedTabFunc(tabs, existing.tabid, !tabPreviewMode ? { tabPreviewMode: false } : {})
);
return;
}
@@ -92,8 +95,14 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
items.push(newItem);
}
const filesFiltered = tabPreviewMode ? (files || []).filter(x => !x.tabPreviewMode) : files;
return [
...(files || []).map(x => ({ ...x, selected: false, tabOrder: _.findIndex(items, y => y.tabid == x.tabid) })),
...(filesFiltered || []).map(x => ({
...x,
selected: false,
tabOrder: _.findIndex(items, y => y.tabid == x.tabid),
})),
{
...newTab,
tabid,

View File

@@ -0,0 +1,111 @@
<script lang="ts">
import keycodes from '../utility/keycodes';
import _ from 'lodash';
export let list;
export let selectedObjectStore;
export let getSelectedObject;
export let selectedObjectMatcher;
export let module;
export let onScrollTop = null;
export let onFocusFilterBox = null;
let isListFocused = false;
let domDiv = null;
function handleKeyDown(ev) {
function selectByDiff(diff) {
const selected = getSelectedObject();
const index = _.findIndex(list, x => selectedObjectMatcher(x, selected));
if (index == 0 && diff < 0) {
onFocusFilterBox?.();
return;
}
if (index >= 0) {
let newIndex = index + diff;
if (newIndex >= list.length) {
newIndex = list.length - 1;
}
if (newIndex < 0) {
newIndex = 0;
}
if (list[newIndex]) {
selectedObjectStore.set(list[newIndex]);
module.handleObjectClick(list[newIndex], { tabPreviewMode: true });
}
if (newIndex == 0) {
onScrollTop?.();
}
}
}
if (ev.keyCode == keycodes.upArrow) {
selectByDiff(-1);
ev.preventDefault();
}
if (ev.keyCode == keycodes.downArrow) {
selectByDiff(1);
ev.preventDefault();
}
if (ev.keyCode == keycodes.enter) {
module.handleObjectClick(getSelectedObject(), { tabPreviewMode: false, focusTab: true });
ev.preventDefault();
}
if (ev.keyCode == keycodes.pageDown) {
selectByDiff(10);
ev.preventDefault();
}
if (ev.keyCode == keycodes.pageUp) {
selectByDiff(-10);
ev.preventDefault();
}
if (ev.keyCode == keycodes.home) {
if (list[0]) {
selectedObjectStore.set(list[0]);
module.handleObjectClick(list[0], { tabPreviewMode: true });
onScrollTop?.();
}
}
if (ev.keyCode == keycodes.end) {
if (list[list.length - 1]) {
selectedObjectStore.set(list[list.length - 1]);
module.handleObjectClick(list[list.length - 1], { tabPreviewMode: true });
}
}
}
export function focusFirst() {
domDiv?.focus();
if (list[0]) {
selectedObjectStore.set(list[0]);
module.handleObjectClick(list[0], { tabPreviewMode: true });
onScrollTop?.();
}
}
</script>
<div
tabindex="0"
on:keydown={handleKeyDown}
class="wrapper"
class:app-object-list-focused={isListFocused}
on:focus={() => {
isListFocused = true;
}}
on:blur={() => {
isListFocused = false;
}}
bind:this={domDiv}
>
<slot />
</div>
<style>
.wrapper:focus {
outline: none;
}
</style>

View File

@@ -36,18 +36,28 @@
import FontIcon from '../icons/FontIcon.svelte';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
import { findEngineDriver } from 'dbgate-tools';
import { currentDatabase, extensions } from '../stores';
import {
currentDatabase,
extensions,
getSelectedDatabaseObjectAppObject,
selectedDatabaseObjectAppObject,
} from '../stores';
import newQuery from '../query/newQuery';
import runCommand from '../commands/runCommand';
import { apiCall } from '../utility/api';
import { filterAppsForDatabase } from '../utility/appTools';
import SchemaSelector from './SchemaSelector.svelte';
import { appliedCurrentSchema } from '../stores';
import AppObjectListHandler from './AppObjectListHandler.svelte';
import { matchDatabaseObjectAppObject } from '../appobj/appObjectTools';
export let conid;
export let database;
let filter = '';
let domContainer = null;
let domFilter = null;
let domListHandler;
$: objects = useDatabaseInfo({ conid, database });
$: status = useDatabaseStatus({ conid, database });
@@ -151,7 +161,14 @@
</WidgetsInnerContainer>
{:else}
<SearchBoxWrapper>
<SearchInput placeholder="Search in tables, objects, # prefix in columns" bind:value={filter} />
<SearchInput
placeholder="Search in tables, objects, # prefix in columns"
bind:value={filter}
bind:this={domFilter}
onFocusFilteredList={() => {
domListHandler?.focusFirst();
}}
/>
<CloseSearchButton bind:filter />
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square>
@@ -168,27 +185,42 @@
negativeMarginTop
/>
<WidgetsInnerContainer>
<WidgetsInnerContainer bind:this={domContainer}>
{#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects}
<LoadingInfo message={$status?.feedback?.analysingMessage || 'Loading database structure'} />
{:else}
<AppObjectList
list={objectList
.filter(x => ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
.map(x => ({ ...x, conid, database }))}
<AppObjectListHandler
bind:this={domListHandler}
list={flatFilteredList.map(x => ({ ...x, conid, database }))}
selectedObjectStore={selectedDatabaseObjectAppObject}
getSelectedObject={getSelectedDatabaseObjectAppObject}
selectedObjectMatcher={matchDatabaseObjectAppObject}
module={databaseObjectAppObject}
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
subItemsComponent={SubColumnParamList}
isExpandable={data =>
data.objectTypeField == 'tables' || data.objectTypeField == 'views' || data.objectTypeField == 'matviews'}
expandIconFunc={chevronExpandIcon}
{filter}
passProps={{
showPinnedInsteadOfUnpin: true,
connection: $connection,
hideSchemaName: !!$appliedCurrentSchema,
onScrollTop={() => {
domContainer?.scrollTop();
}}
/>
onFocusFilterBox={() => {
domFilter?.focus();
}}
>
<AppObjectList
list={objectList
.filter(x => ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
.map(x => ({ ...x, conid, database }))}
module={databaseObjectAppObject}
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
subItemsComponent={SubColumnParamList}
isExpandable={data =>
data.objectTypeField == 'tables' || data.objectTypeField == 'views' || data.objectTypeField == 'matviews'}
expandIconFunc={chevronExpandIcon}
{filter}
passProps={{
showPinnedInsteadOfUnpin: true,
connection: $connection,
hideSchemaName: !!$appliedCurrentSchema,
}}
/>
</AppObjectListHandler>
{/if}
</WidgetsInnerContainer>
{/if}

View File

@@ -1,4 +1,12 @@
<div on:drop><slot /></div>
<script lang="ts">
let domDiv;
export function scrollTop() {
domDiv.scrollTop = 0;
}
</script>
<div on:drop bind:this={domDiv}><slot /></div>
<style>
div {