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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,7 +42,7 @@
--theme-border: #ccc; --theme-border: #ccc;
--theme-bg-hover: #bae7ff; --theme-bg-hover: #ccc;
--theme-bg-selected: #91d5ff; /* blue-3 */ --theme-bg-selected: #91d5ff; /* blue-3 */
--theme-bg-selected-point: #40a9ff; /* blue-5 */ --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 { useSettings } from '../utility/metadataLoaders';
import { derived } from 'svelte/store'; import { derived } from 'svelte/store';
import { safeFormatDate } from 'dbgate-tools'; import { safeFormatDate } from 'dbgate-tools';
import FormDefaultActionField from './FormDefaultActionField.svelte';
const electron = getElectron(); const electron = getElectron();
let restartWarning = false; let restartWarning = false;
@@ -288,56 +289,11 @@ ORDER BY
]} ]}
/> />
<FormSelectField <FormDefaultActionField label="Table click" objectTypeField="tables" />
label="Table click" <FormDefaultActionField label="View click" objectTypeField="views" />
name="defaultAction.dbObjectClick.tables" <FormDefaultActionField label="Materialized view click" objectTypeField="matviews" />
isNative <FormDefaultActionField label="Procedure click" objectTypeField="procedures" />
defaultValue="" <FormDefaultActionField label="Function click" objectTypeField="functions" />
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' },
]}
/>
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="5"> <svelte:fragment slot="5">
<div class="heading">Confirmations</div> <div class="heading">Confirmations</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -52,7 +52,7 @@
export let tabid; export let tabid;
const tabVisible: any = getContext('tabVisible'); const tabFocused: any = getContext('tabFocused');
const timerLabel = useTimerLabel(); const timerLabel = useTimerLabel();
let runnerId; let runnerId;
@@ -82,7 +82,7 @@
invalidateCommands(); invalidateCommands();
} }
$: if ($tabVisible && domEditor) { $: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus(); 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; export let tabid;
const tabVisible: any = getContext('tabVisible'); const tabFocused: any = getContext('tabFocused');
export const activator = createActivator('YamlEditorTab', false); export const activator = createActivator('YamlEditorTab', false);
let domEditor; let domEditor;
$: if ($tabVisible && domEditor) { $: if ($tabFocused && domEditor) {
domEditor?.getEditor()?.focus(); domEditor?.getEditor()?.focus();
} }

View File

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

View File

@@ -63,3 +63,79 @@ export default async function applyScriptTemplate(scriptTemplate, extensions, pr
return res; 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 _ 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 { shouldShowTab } from '../tabpanel/TabsPanel.svelte';
import { callWhenAppLoaded, getAppLoaded } from './appLoadManager'; import { callWhenAppLoaded, getAppLoaded } from './appLoadManager';
import { getConnectionInfo } from './metadataLoaders'; import { getConnectionInfo } from './metadataLoaders';
import { switchCurrentDatabase } from './common'; import { switchCurrentDatabase } from './common';
let lastCurrentTab = null; // let lastCurrentTab = null;
openedTabs.subscribe(value => { // openedTabs.subscribe(value => {
const newCurrentTab = (value || []).find(x => x.selected); // const newCurrentTab = (value || []).find(x => x.selected);
if (newCurrentTab == lastCurrentTab) return; // if (newCurrentTab == lastCurrentTab) return;
if (getLockedDatabaseMode() && getCurrentDatabase()) return; // if (getLockedDatabaseMode() && getCurrentDatabase()) return;
const lastTab = lastCurrentTab; // const lastTab = lastCurrentTab;
lastCurrentTab = newCurrentTab; // lastCurrentTab = newCurrentTab;
// if (lastTab?.tabComponent == 'ConnectionTab') return; // // if (lastTab?.tabComponent == 'ConnectionTab') return;
if (newCurrentTab) { // if (newCurrentTab) {
const { conid, database } = newCurrentTab.props || {}; // const { conid, database } = newCurrentTab.props || {};
if (conid && database && (conid != lastTab?.props?.conid || database != lastTab?.props?.database)) { // if (conid && database && (conid != lastTab?.props?.conid || database != lastTab?.props?.database)) {
const doWork = async () => { // const doWork = async () => {
const connection = await getConnectionInfo({ conid }); // const connection = await getConnectionInfo({ conid });
switchCurrentDatabase({ // switchCurrentDatabase({
connection, // connection,
name: database, // name: database,
}); // });
}; // };
callWhenAppLoaded(doWork); // 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 => { currentDatabase.subscribe(currentDb => {
if (!getLockedDatabaseMode()) return; 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))); 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 [ return [
...(files || []).filter(x => x.tabid != tabid).map(x => ({ ...x, selected: false })), ...(files || [])
...(files || []).filter(x => x.tabid == tabid).map(x => ({ ...x, selected: true })), .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; let existing = null;
const { savedFile, savedFolder, savedFilePath } = newTab.props || {}; const { savedFile, savedFolder, savedFilePath } = newTab.props || {};
const { tabPreviewMode } = newTab;
if (savedFile || savedFilePath) { if (savedFile || savedFilePath) {
existing = oldTabs.find( existing = oldTabs.find(
x => x =>
@@ -49,7 +50,9 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
} }
if (existing) { if (existing) {
openedTabs.update(tabs => setSelectedTabFunc(tabs, existing.tabid)); openedTabs.update(tabs =>
setSelectedTabFunc(tabs, existing.tabid, !tabPreviewMode ? { tabPreviewMode: false } : {})
);
return; return;
} }
@@ -92,8 +95,14 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
items.push(newItem); items.push(newItem);
} }
const filesFiltered = tabPreviewMode ? (files || []).filter(x => !x.tabPreviewMode) : files;
return [ 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, ...newTab,
tabid, 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 FontIcon from '../icons/FontIcon.svelte';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte'; import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
import { findEngineDriver } from 'dbgate-tools'; import { findEngineDriver } from 'dbgate-tools';
import { currentDatabase, extensions } from '../stores'; import {
currentDatabase,
extensions,
getSelectedDatabaseObjectAppObject,
selectedDatabaseObjectAppObject,
} from '../stores';
import newQuery from '../query/newQuery'; import newQuery from '../query/newQuery';
import runCommand from '../commands/runCommand'; import runCommand from '../commands/runCommand';
import { apiCall } from '../utility/api'; import { apiCall } from '../utility/api';
import { filterAppsForDatabase } from '../utility/appTools'; import { filterAppsForDatabase } from '../utility/appTools';
import SchemaSelector from './SchemaSelector.svelte'; import SchemaSelector from './SchemaSelector.svelte';
import { appliedCurrentSchema } from '../stores'; import { appliedCurrentSchema } from '../stores';
import AppObjectListHandler from './AppObjectListHandler.svelte';
import { matchDatabaseObjectAppObject } from '../appobj/appObjectTools';
export let conid; export let conid;
export let database; export let database;
let filter = ''; let filter = '';
let domContainer = null;
let domFilter = null;
let domListHandler;
$: objects = useDatabaseInfo({ conid, database }); $: objects = useDatabaseInfo({ conid, database });
$: status = useDatabaseStatus({ conid, database }); $: status = useDatabaseStatus({ conid, database });
@@ -151,7 +161,14 @@
</WidgetsInnerContainer> </WidgetsInnerContainer>
{:else} {:else}
<SearchBoxWrapper> <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 /> <CloseSearchButton bind:filter />
<DropDownButton icon="icon plus-thick" menu={createAddMenu} /> <DropDownButton icon="icon plus-thick" menu={createAddMenu} />
<InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square> <InlineButton on:click={handleRefreshDatabase} title="Refresh database connection and object list" square>
@@ -168,27 +185,42 @@
negativeMarginTop negativeMarginTop
/> />
<WidgetsInnerContainer> <WidgetsInnerContainer bind:this={domContainer}>
{#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects} {#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects}
<LoadingInfo message={$status?.feedback?.analysingMessage || 'Loading database structure'} /> <LoadingInfo message={$status?.feedback?.analysingMessage || 'Loading database structure'} />
{:else} {:else}
<AppObjectList <AppObjectListHandler
list={objectList bind:this={domListHandler}
.filter(x => ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true)) list={flatFilteredList.map(x => ({ ...x, conid, database }))}
.map(x => ({ ...x, conid, database }))} selectedObjectStore={selectedDatabaseObjectAppObject}
getSelectedObject={getSelectedDatabaseObjectAppObject}
selectedObjectMatcher={matchDatabaseObjectAppObject}
module={databaseObjectAppObject} module={databaseObjectAppObject}
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)} onScrollTop={() => {
subItemsComponent={SubColumnParamList} domContainer?.scrollTop();
isExpandable={data =>
data.objectTypeField == 'tables' || data.objectTypeField == 'views' || data.objectTypeField == 'matviews'}
expandIconFunc={chevronExpandIcon}
{filter}
passProps={{
showPinnedInsteadOfUnpin: true,
connection: $connection,
hideSchemaName: !!$appliedCurrentSchema,
}} }}
/> 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} {/if}
</WidgetsInnerContainer> </WidgetsInnerContainer>
{/if} {/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> <style>
div { div {