Merge branch 'feature/connection-keyboard-browse'

This commit is contained in:
SPRINX0\prochazka
2024-11-26 09:11:23 +01:00
13 changed files with 357 additions and 82 deletions

View File

@@ -50,7 +50,7 @@
<svelte:component <svelte:component
this={module.default} this={module.default}
{data} {data}
on:click={handleExpand} on:dblclick={handleExpand}
on:expand={handleExpandButton} on:expand={handleExpandButton}
expandIcon={getExpandIcon(!isExpandedBySearch && expandable, subItemsComponent, isExpanded, expandIconFunc)} expandIcon={getExpandIcon(!isExpandedBySearch && expandable, subItemsComponent, isExpanded, expandIconFunc)}
{checkedObjectsStore} {checkedObjectsStore}

View File

@@ -12,6 +12,16 @@
return filterName(filter, ...databases.map(x => x.name)); return filterName(filter, ...databases.map(x => x.name));
}; };
export function openConnection(connection) { export function openConnection(connection) {
if (connection.singleDatabase) {
if (getOpenedSingleDatabaseConnections().includes(connection._id)) {
return;
}
} else {
if (getOpenedConnections().includes(connection._id)) {
return;
}
}
const config = getCurrentConfig(); const config = getCurrentConfig();
if (connection.singleDatabase) { if (connection.singleDatabase) {
switchCurrentDatabase({ connection, name: connection.defaultDatabase }); switchCurrentDatabase({ connection, name: connection.defaultDatabase });
@@ -83,10 +93,12 @@
currentDatabase, currentDatabase,
expandedConnections, expandedConnections,
extensions, extensions,
focusedConnectionOrDatabase,
getCurrentConfig, getCurrentConfig,
getCurrentDatabase, getCurrentDatabase,
getCurrentSettings, getCurrentSettings,
getOpenedConnections, getOpenedConnections,
getOpenedSingleDatabaseConnections,
getOpenedTabs, getOpenedTabs,
openedConnections, openedConnections,
openedSingleDatabaseConnections, openedSingleDatabaseConnections,
@@ -135,7 +147,7 @@
}); });
}; };
const handleClick = async () => { const handleDoubleClick = async () => {
const config = getCurrentConfig(); const config = getCurrentConfig();
if (config.runAsPortal) { if (config.runAsPortal) {
await tick(); await tick();
@@ -158,6 +170,19 @@
} }
}; };
const handleClick = async e => {
focusedConnectionOrDatabase.set({ conid: data?._id });
openNewTab({
title: getConnectionLabel(data),
icon: 'img connection',
tabComponent: 'ConnectionTab',
tabPreviewMode: true,
props: {
conid: data._id,
},
});
};
const handleSqlRestore = () => { const handleSqlRestore = () => {
showModal(ImportDatabaseDumpModal, { showModal(ImportDatabaseDumpModal, {
connection: data, connection: data,
@@ -329,7 +354,7 @@
colorMark={passProps?.connectionColorFactory && passProps?.connectionColorFactory({ conid: data._id })} colorMark={passProps?.connectionColorFactory && passProps?.connectionColorFactory({ conid: data._id })}
menu={getContextMenu} menu={getContextMenu}
on:click={handleClick} on:click={handleClick}
on:click on:dblclick
on:expand on:expand
on:dblclick={handleConnect} on:dblclick={handleConnect}
on:middleclick={() => { on:middleclick={() => {
@@ -337,4 +362,5 @@
.find(x => x.isNewQuery) .find(x => x.isNewQuery)
.onClick(); .onClick();
}} }}
isChoosed={data._id == $focusedConnectionOrDatabase?.conid && !$focusedConnectionOrDatabase?.database}
/> />

View File

@@ -446,6 +446,7 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
currentArchive, currentArchive,
currentDatabase, currentDatabase,
extensions, extensions,
focusedConnectionOrDatabase,
getCurrentDatabase, getCurrentDatabase,
getExtensions, getExtensions,
getOpenedTabs, getOpenedTabs,
@@ -512,7 +513,14 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
passProps?.connectionColorFactory({ conid: data?.connection?._id, database: data.name }, null, null, false)} passProps?.connectionColorFactory({ conid: data?.connection?._id, database: data.name }, null, null, false)}
isBold={$currentDatabase?.connection?._id == data?.connection?._id && isBold={$currentDatabase?.connection?._id == data?.connection?._id &&
extractDbNameFromComposite($currentDatabase?.name) == data.name} extractDbNameFromComposite($currentDatabase?.name) == data.name}
on:click={() => switchCurrentDatabase(data)} on:dblclick={() => {
switchCurrentDatabase(data);
// passProps?.onFocusSqlObjectList?.();
}}
on:click={() => {
// switchCurrentDatabase(data);
$focusedConnectionOrDatabase = { conid: data.connection?._id, database: data.name, connection: data.connection };
}}
on:dragstart on:dragstart
on:dragenter on:dragenter
on:dragend on:dragend
@@ -532,4 +540,6 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
list.filter(x => x?.name != data?.name || x?.connection?._id != data?.connection?._id) list.filter(x => x?.name != data?.name || x?.connection?._id != data?.connection?._id)
) )
: null} : null}
isChoosed={data.connection?._id == $focusedConnectionOrDatabase?.conid &&
data.name == $focusedConnectionOrDatabase?.database}
/> />

View File

@@ -15,13 +15,11 @@
$: databases = useDatabaseList({ conid: isExpandedOnlyBySearch ? null : data._id }); $: databases = useDatabaseList({ conid: isExpandedOnlyBySearch ? null : data._id });
$: dbList = isExpandedOnlyBySearch ? getLocalStorage(`database_list_${data._id}`) || [] : $databases || []; $: dbList = isExpandedOnlyBySearch ? getLocalStorage(`database_list_${data._id}`) || [] : $databases || [];
$: connectionLabel = getConnectionLabel(data);
</script> </script>
<AppObjectList <AppObjectList
list={_.sortBy( list={_.sortBy(
dbList.filter(x => filterName(filter, x.name, connectionLabel)), dbList.filter(x => filterName(filter, x.name, data.displayName, data.server)),
x => x.sortOrder ?? x.name x => x.sortOrder ?? x.name
).map(db => ({ ...db, connection: data }))} ).map(db => ({ ...db, connection: data }))}
module={databaseAppObject} module={databaseAppObject}

View File

@@ -7,6 +7,7 @@
export let disabled = false; export let disabled = false;
export let value; export let value;
export let title = null; export let title = null;
export let skipWidth = false;
function handleClick() { function handleClick() {
if (!disabled) dispatch('click'); if (!disabled) dispatch('click');
@@ -19,19 +20,22 @@
} }
</script> </script>
<input {type} {value} {title} class:disabled {...$$restProps} on:click={handleClick} bind:this={domButton} /> <input {type} {value} {title} class:disabled {...$$restProps} on:click={handleClick} bind:this={domButton} class:skipWidth />
<style> <style>
input { input {
border: 1px solid var(--theme-bg-button-inv-2); border: 1px solid var(--theme-bg-button-inv-2);
padding: 5px; padding: 5px;
margin: 2px; margin: 2px;
width: 100px;
background-color: var(--theme-bg-button-inv); background-color: var(--theme-bg-button-inv);
color: var(--theme-font-inv-1); color: var(--theme-font-inv-1);
border-radius: 2px; border-radius: 2px;
} }
input:not(.skipWidth) {
width: 100px;
}
input:hover:not(.disabled) { input:hover:not(.disabled) {
background-color: var(--theme-bg-button-inv-2); background-color: var(--theme-bg-button-inv-2);
} }

View File

@@ -23,8 +23,12 @@
const debouncedSet = _.debounce(x => (value = x), 500); const debouncedSet = _.debounce(x => (value = x), 500);
export function focus() { export function focus(text) {
domInput.focus(); domInput.focus();
if (text) {
domInput.value = text;
value = text;
}
} }
</script> </script>

View File

@@ -158,6 +158,7 @@ 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 selectedDatabaseObjectAppObject = writable(null);
export const focusedConnectionOrDatabase = writable<{ conid: string; database?: string; connection: any }>(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)
@@ -339,3 +340,15 @@ openedModals.subscribe(value => {
openedModalsValue = value; openedModalsValue = value;
}); });
export const getOpenedModals = () => openedModalsValue; export const getOpenedModals = () => openedModalsValue;
let focusedConnectionOrDatabaseValue = null;
focusedConnectionOrDatabase.subscribe(value => {
focusedConnectionOrDatabaseValue = value;
});
export const getFocusedConnectionOrDatabase = () => focusedConnectionOrDatabaseValue;
let openedSingleDatabaseConnectionsValue = [];
openedSingleDatabaseConnections.subscribe(value => {
openedSingleDatabaseConnectionsValue = value;
});
export const getOpenedSingleDatabaseConnections = () => openedSingleDatabaseConnectionsValue;

View File

@@ -1,6 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import { import {
currentDatabase, currentDatabase,
focusedConnectionOrDatabase,
getActiveTab, getActiveTab,
getCurrentDatabase, getCurrentDatabase,
getLockedDatabaseMode, getLockedDatabaseMode,
@@ -78,6 +79,16 @@ export async function handleAfterTabClick() {
} }
currentDatabase.subscribe(currentDb => { currentDatabase.subscribe(currentDb => {
if (currentDb) {
focusedConnectionOrDatabase.set({
conid: currentDb.connection?._id,
database: currentDb.name,
connection: currentDb.connection,
});
} else {
focusedConnectionOrDatabase.set(null);
}
if (!getLockedDatabaseMode()) return; if (!getLockedDatabaseMode()) return;
if (!currentDb && !getAppLoaded()) return; if (!currentDb && !getAppLoaded()) return;
openedTabs.update(tabs => { openedTabs.update(tabs => {

View File

@@ -1,23 +1,28 @@
<script lang="ts"> <script lang="ts">
import keycodes from '../utility/keycodes'; import keycodes from '../utility/keycodes';
import _ from 'lodash'; import _ from 'lodash';
import { sleep } from '../utility/common';
export let list; export let list;
export let selectedObjectStore; export let selectedObjectStore;
export let getSelectedObject; export let getSelectedObject;
export let selectedObjectMatcher; export let selectedObjectMatcher;
export let module; export let handleObjectClick;
export let onScrollTop = null; export let onScrollTop = null;
export let onFocusFilterBox = null; export let onFocusFilterBox = null;
export let getDefaultFocusedItem = null;
let isListFocused = false; let isListFocused = false;
let domDiv = null; let domDiv = null;
export let hideContent = false;
function handleKeyDown(ev) { function handleKeyDown(ev) {
const listInstance = _.isFunction(list) ? list() : list;
function selectByDiff(diff) { function selectByDiff(diff) {
const selected = getSelectedObject(); const selected = getSelectedObject();
const index = _.findIndex(list, x => selectedObjectMatcher(x, selected)); const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
if (index == 0 && diff < 0) { if (index == 0 && diff < 0) {
onFocusFilterBox?.(); onFocusFilterBox?.();
@@ -26,16 +31,16 @@
if (index >= 0) { if (index >= 0) {
let newIndex = index + diff; let newIndex = index + diff;
if (newIndex >= list.length) { if (newIndex >= listInstance.length) {
newIndex = list.length - 1; newIndex = listInstance.length - 1;
} }
if (newIndex < 0) { if (newIndex < 0) {
newIndex = 0; newIndex = 0;
} }
if (list[newIndex]) { if (listInstance[newIndex]) {
selectedObjectStore.set(list[newIndex]); selectedObjectStore.set(listInstance[newIndex]);
module.handleObjectClick(list[newIndex], { tabPreviewMode: true }); handleObjectClick?.(listInstance[newIndex], { tabPreviewMode: true });
} }
if (newIndex == 0) { if (newIndex == 0) {
@@ -52,7 +57,7 @@
ev.preventDefault(); ev.preventDefault();
} }
if (ev.keyCode == keycodes.enter) { if (ev.keyCode == keycodes.enter) {
module.handleObjectClick(getSelectedObject(), { tabPreviewMode: false, focusTab: true }); handleObjectClick?.(getSelectedObject(), { tabPreviewMode: false, focusTab: true });
ev.preventDefault(); ev.preventDefault();
} }
if (ev.keyCode == keycodes.pageDown) { if (ev.keyCode == keycodes.pageDown) {
@@ -64,28 +69,66 @@
ev.preventDefault(); ev.preventDefault();
} }
if (ev.keyCode == keycodes.home) { if (ev.keyCode == keycodes.home) {
if (list[0]) { if (listInstance[0]) {
selectedObjectStore.set(list[0]); selectedObjectStore.set(listInstance[0]);
module.handleObjectClick(list[0], { tabPreviewMode: true }); handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
onScrollTop?.(); onScrollTop?.();
} }
} }
if (ev.keyCode == keycodes.end) { if (ev.keyCode == keycodes.end) {
if (list[list.length - 1]) { if (listInstance[listInstance.length - 1]) {
selectedObjectStore.set(list[list.length - 1]); selectedObjectStore.set(listInstance[listInstance.length - 1]);
module.handleObjectClick(list[list.length - 1], { tabPreviewMode: true }); handleObjectClick?.(listInstance[listInstance.length - 1], { tabPreviewMode: true });
} }
} }
if (
!ev.ctrlKey &&
!ev.altKey &&
!ev.metaKey &&
((ev.keyCode >= keycodes.a && ev.keyCode <= keycodes.z) ||
(ev.keyCode >= keycodes.n0 && ev.keyCode <= keycodes.n9) ||
(ev.keyCode >= keycodes.numPad0 && ev.keyCode <= keycodes.numPad9) ||
ev.keyCode == keycodes.dash)
) {
const text = ev.key;
onFocusFilterBox?.(text);
ev.preventDefault();
}
} }
export function focusFirst() { export function focusFirst() {
const listInstance = _.isFunction(list) ? list() : list;
domDiv?.focus(); domDiv?.focus();
if (list[0]) { if (listInstance[0]) {
selectedObjectStore.set(list[0]); selectedObjectStore.set(listInstance[0]);
module.handleObjectClick(list[0], { tabPreviewMode: true }); handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
onScrollTop?.(); onScrollTop?.();
} }
} }
async function handleFocus() {
isListFocused = true;
// await tick();
await sleep(100);
// console.log('ON FOCUS AFTER SLEEP');
const listInstance = _.isFunction(list) ? list() : list;
const selected = getSelectedObject();
const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
if (index < 0) {
const focused = getDefaultFocusedItem?.();
if (focused) {
const index2 = _.findIndex(listInstance, x => selectedObjectMatcher(x, focused));
if (index2 >= 0) {
selectedObjectStore.set(focused);
handleObjectClick?.(focused, { tabPreviewMode: true });
return;
}
}
focusFirst();
}
}
</script> </script>
<div <div
@@ -93,13 +136,12 @@
on:keydown={handleKeyDown} on:keydown={handleKeyDown}
class="wrapper" class="wrapper"
class:app-object-list-focused={isListFocused} class:app-object-list-focused={isListFocused}
on:focus={() => { on:focus={handleFocus}
isListFocused = true;
}}
on:blur={() => { on:blur={() => {
isListFocused = false; isListFocused = false;
}} }}
bind:this={domDiv} bind:this={domDiv}
class:hideContent
> >
<slot /> <slot />
</div> </div>
@@ -108,4 +150,8 @@
.wrapper:focus { .wrapper:focus {
outline: none; outline: none;
} }
.hideContent {
visibility: hidden;
}
</style> </style>

View File

@@ -16,9 +16,12 @@
openedTabs, openedTabs,
emptyConnectionGroupNames, emptyConnectionGroupNames,
collapsedConnectionGroupNames, collapsedConnectionGroupNames,
focusedConnectionOrDatabase,
getFocusedConnectionOrDatabase,
currentDatabase,
} from '../stores'; } from '../stores';
import runCommand from '../commands/runCommand'; import runCommand from '../commands/runCommand';
import { getConnectionLabel } from 'dbgate-tools'; import { filterName, getConnectionLabel } from 'dbgate-tools';
import { useConnectionColorFactory } from '../utility/useConnectionColor'; import { useConnectionColorFactory } from '../utility/useConnectionColor';
import FontIcon from '../icons/FontIcon.svelte'; import FontIcon from '../icons/FontIcon.svelte';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte'; import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
@@ -29,11 +32,21 @@
import { showModal } from '../modals/modalTools'; import { showModal } from '../modals/modalTools';
import InputTextModal from '../modals/InputTextModal.svelte'; import InputTextModal from '../modals/InputTextModal.svelte';
import ConfirmModal from '../modals/ConfirmModal.svelte'; import ConfirmModal from '../modals/ConfirmModal.svelte';
import AppObjectListHandler from './AppObjectListHandler.svelte';
import { getLocalStorage } from '../utility/storageCache';
import { switchCurrentDatabase } from '../utility/common';
import openNewTab from '../utility/openNewTab';
import { openConnection } from '../appobj/ConnectionAppObject.svelte';
const connections = useConnectionList(); const connections = useConnectionList();
const serverStatus = useServerStatus(); const serverStatus = useServerStatus();
export let passProps: any = {};
let filter = ''; let filter = '';
let domListHandler;
let domContainer = null;
let domFilter = null;
$: connectionsWithStatus = $: connectionsWithStatus =
$connections && $serverStatus $connections && $serverStatus
@@ -47,12 +60,52 @@
x => !x.unsaved || $openedConnections.includes(x._id) || $openedSingleDatabaseConnections.includes(x._id) x => !x.unsaved || $openedConnections.includes(x._id) || $openedSingleDatabaseConnections.includes(x._id)
); );
$: connectionsWithParent = connectionsWithStatusFiltered $: connectionsWithParent = _.sortBy(
connectionsWithStatusFiltered
? connectionsWithStatusFiltered?.filter(x => x.parent !== undefined && x.parent !== null && x.parent.length !== 0) ? connectionsWithStatusFiltered?.filter(x => x.parent !== undefined && x.parent !== null && x.parent.length !== 0)
: []; : [],
$: connectionsWithoutParent = connectionsWithStatusFiltered connection => (getConnectionLabel(connection) || '').toUpperCase()
);
$: connectionsWithoutParent = _.sortBy(
connectionsWithStatusFiltered
? connectionsWithStatusFiltered?.filter(x => x.parent === undefined || x.parent === null || x.parent.length === 0) ? connectionsWithStatusFiltered?.filter(x => x.parent === undefined || x.parent === null || x.parent.length === 0)
: []; : [],
connection => (getConnectionLabel(connection) || '').toUpperCase()
);
function getFocusFlatList() {
const expanded = $expandedConnections;
const opened = $openedConnections;
const res = [];
for (const con of [...connectionsWithParent, ...connectionsWithoutParent]) {
const databases = getLocalStorage(`database_list_${con._id}`) || [];
if (!filterName(filter, con.displayName, con.server, ...databases.map(x => x.name))) {
continue;
}
res.push({
connection: con,
conid: con._id,
});
if ((expanded.includes(con._id) && opened.includes(con._id)) || filter) {
for (const db of _.sortBy(databases, x => x.sortOrder ?? x.name)) {
if (!filterName(filter, con.displayName, con.server, db.name)) {
continue;
}
res.push({
conid: con._id,
database: db.name,
connection: con,
});
}
}
}
return res;
}
const handleRefreshConnections = () => { const handleRefreshConnections = () => {
for (const conid of $openedConnections) { for (const conid of $openedConnections) {
@@ -112,7 +165,14 @@
</script> </script>
<SearchBoxWrapper> <SearchBoxWrapper>
<SearchInput placeholder="Search connection or database" bind:value={filter} /> <SearchInput
placeholder="Search connection or database"
bind:value={filter}
bind:this={domFilter}
onFocusFilteredList={() => {
domListHandler?.focusFirst();
}}
/>
<CloseSearchButton bind:filter /> <CloseSearchButton bind:filter />
{#if $commandsCustomized['new.connection']?.enabled} {#if $commandsCustomized['new.connection']?.enabled}
<InlineButton on:click={() => runCommand('new.connection')} title="Add new connection"> <InlineButton on:click={() => runCommand('new.connection')} title="Add new connection">
@@ -127,6 +187,7 @@
</InlineButton> </InlineButton>
</SearchBoxWrapper> </SearchBoxWrapper>
<WidgetsInnerContainer <WidgetsInnerContainer
bind:this={domContainer}
on:drop={e => { on:drop={e => {
var data = e.dataTransfer.getData('app_object_drag_data'); var data = e.dataTransfer.getData('app_object_drag_data');
if (data) { if (data) {
@@ -134,14 +195,58 @@
} }
}} }}
> >
<AppObjectListHandler
bind:this={domListHandler}
list={getFocusFlatList}
selectedObjectStore={focusedConnectionOrDatabase}
getSelectedObject={getFocusedConnectionOrDatabase}
selectedObjectMatcher={(o1, o2) => o1?.conid == o2?.conid && o1?.database == o2?.database}
getDefaultFocusedItem={() =>
$currentDatabase
? {
conid: $currentDatabase?.connection?._id,
database: $currentDatabase?.name,
connection: $currentDatabase?.connection,
}
: null}
onScrollTop={() => {
domContainer?.scrollTop();
}}
onFocusFilterBox={text => {
domFilter?.focus(text);
}}
handleObjectClick={(data, options) => {
if (data.database) {
if (options.focusTab) {
switchCurrentDatabase({ connection: data.connection, name: data.database });
// console.log('FOCUSING DB', passProps);
// passProps?.onFocusSqlObjectList?.();
}
} else {
if (options.focusTab) {
openConnection(data.connection);
} else {
openNewTab({
title: getConnectionLabel(data.connection),
icon: 'img connection',
tabComponent: 'ConnectionTab',
tabPreviewMode: options.tabPreviewMode,
props: {
conid: data.conid,
},
});
}
}
}}
>
<AppObjectList <AppObjectList
list={_.sortBy(connectionsWithParent, connection => (getConnectionLabel(connection) || '').toUpperCase())} list={connectionsWithParent}
module={connectionAppObject} module={connectionAppObject}
subItemsComponent={SubDatabaseList} subItemsComponent={SubDatabaseList}
expandOnClick expandOnClick
isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase} isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase}
{filter} {filter}
passProps={{ connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }} passProps={{ ...passProps, connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true }}
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase} getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
setIsExpanded={(data, value) => { setIsExpanded={(data, value) => {
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id))); expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
@@ -159,7 +264,7 @@
<div class="br" /> <div class="br" />
{/if} {/if}
<AppObjectList <AppObjectList
list={_.sortBy(connectionsWithoutParent, connection => (getConnectionLabel(connection) || '').toUpperCase())} list={connectionsWithoutParent}
module={connectionAppObject} module={connectionAppObject}
subItemsComponent={SubDatabaseList} subItemsComponent={SubDatabaseList}
expandOnClick expandOnClick
@@ -171,6 +276,7 @@
expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id))); expandedConnections.update(old => (value ? [...old, data._id] : old.filter(x => x != data._id)));
}} }}
/> />
</AppObjectListHandler>
{#if $connections && !$connections.find(x => !x.unsaved) && $openedConnections.length == 0 && $commandsCustomized['new.connection']?.enabled && !$openedTabs.find(x => !x.closedTime && x.tabComponent == 'ConnectionTab' && !x.props?.conid)} {#if $connections && !$connections.find(x => !x.unsaved) && $openedConnections.length == 0 && $commandsCustomized['new.connection']?.enabled && !$openedTabs.find(x => !x.closedTime && x.tabComponent == 'ConnectionTab' && !x.props?.conid)}
<LargeButton icon="icon new-connection" on:click={() => runCommand('new.connection')} fillHorizontal <LargeButton icon="icon new-connection" on:click={() => runCommand('new.connection')} fillHorizontal
>Add new connection</LargeButton >Add new connection</LargeButton

View File

@@ -16,6 +16,7 @@
import _ from 'lodash'; import _ from 'lodash';
export let hidden = false; export let hidden = false;
let domSqlObjectList = null;
$: conid = $currentDatabase?.connection?._id; $: conid = $currentDatabase?.connection?._id;
$: connection = useConnectionInfo({ conid }); $: connection = useConnectionInfo({ conid });
@@ -32,7 +33,7 @@
</WidgetColumnBarItem> </WidgetColumnBarItem>
{:else if !$config?.singleDbConnection} {:else if !$config?.singleDbConnection}
<WidgetColumnBarItem title="Connections" name="connections" height="35%" storageName="connectionsWidget"> <WidgetColumnBarItem title="Connections" name="connections" height="35%" storageName="connectionsWidget">
<ConnectionList /> <ConnectionList passProps={{ onFocusSqlObjectList: () => domSqlObjectList.focus() }} />
</WidgetColumnBarItem> </WidgetColumnBarItem>
{/if} {/if}
<WidgetColumnBarItem <WidgetColumnBarItem
@@ -48,7 +49,7 @@
<WidgetColumnBarItem <WidgetColumnBarItem
title={driver?.databaseEngineTypes?.includes('document') title={driver?.databaseEngineTypes?.includes('document')
? driver?.collectionPluralLabel ?? 'Collections/containers' ? (driver?.collectionPluralLabel ?? 'Collections/containers')
: 'Tables, views, functions'} : 'Tables, views, functions'}
name="dbObjects" name="dbObjects"
storageName="dbObjectsWidget" storageName="dbObjectsWidget"
@@ -58,7 +59,7 @@
(driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document')) (driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document'))
)} )}
> >
<SqlObjectList {conid} {database} /> <SqlObjectList {conid} {database} bind:this={domSqlObjectList} />
</WidgetColumnBarItem> </WidgetColumnBarItem>
<WidgetColumnBarItem <WidgetColumnBarItem

View File

@@ -31,7 +31,7 @@
import { chevronExpandIcon } from '../icons/expandIcons'; import { chevronExpandIcon } from '../icons/expandIcons';
import ErrorInfo from '../elements/ErrorInfo.svelte'; import ErrorInfo from '../elements/ErrorInfo.svelte';
import LoadingInfo from '../elements/LoadingInfo.svelte'; import LoadingInfo from '../elements/LoadingInfo.svelte';
import { getObjectTypeFieldLabel } from '../utility/common'; import { getObjectTypeFieldLabel, switchCurrentDatabase } from '../utility/common';
import DropDownButton from '../buttons/DropDownButton.svelte'; import DropDownButton from '../buttons/DropDownButton.svelte';
import FontIcon from '../icons/FontIcon.svelte'; import FontIcon from '../icons/FontIcon.svelte';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte'; import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
@@ -39,6 +39,7 @@
import { import {
currentDatabase, currentDatabase,
extensions, extensions,
focusedConnectionOrDatabase,
getSelectedDatabaseObjectAppObject, getSelectedDatabaseObjectAppObject,
selectedDatabaseObjectAppObject, selectedDatabaseObjectAppObject,
} from '../stores'; } from '../stores';
@@ -50,6 +51,8 @@
import { appliedCurrentSchema } from '../stores'; import { appliedCurrentSchema } from '../stores';
import AppObjectListHandler from './AppObjectListHandler.svelte'; import AppObjectListHandler from './AppObjectListHandler.svelte';
import { matchDatabaseObjectAppObject } from '../appobj/appObjectTools'; import { matchDatabaseObjectAppObject } from '../appobj/appObjectTools';
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import clickOutside from '../utility/clickOutside';
export let conid; export let conid;
export let database; export let database;
@@ -125,6 +128,15 @@
if (matcher && !matcher(filter)) return false; if (matcher && !matcher(filter)) return false;
return true; return true;
}); });
export function focus() {
domListHandler?.focusFirst();
}
$: differentFocusedDb =
$focusedConnectionOrDatabase &&
$focusedConnectionOrDatabase?.database &&
($focusedConnectionOrDatabase.conid != conid || $focusedConnectionOrDatabase?.database != database);
</script> </script>
{#if $status && $status.name == 'error'} {#if $status && $status.name == 'error'}
@@ -185,7 +197,36 @@
negativeMarginTop negativeMarginTop
/> />
<WidgetsInnerContainer bind:this={domContainer}> {#if differentFocusedDb}
<div class="no-focused-info">
<div class="m-1">Current database:</div>
<div class="m-1 ml-3 mb-3">
<b>{database}</b>
</div>
<FormStyledButton
value={`Switch to ${$focusedConnectionOrDatabase?.database}`}
skipWidth
on:click={() =>
switchCurrentDatabase({
connection: $focusedConnectionOrDatabase?.connection,
name: $focusedConnectionOrDatabase?.database,
})}
/>
<FormStyledButton
value={`Show ${database}`}
skipWidth
on:click={() => {
$focusedConnectionOrDatabase = {
conid,
database,
connection: $connection,
};
}}
/>
</div>
{/if}
<WidgetsInnerContainer bind:this={domContainer} hideContent={differentFocusedDb}>
{#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}
@@ -195,12 +236,12 @@
selectedObjectStore={selectedDatabaseObjectAppObject} selectedObjectStore={selectedDatabaseObjectAppObject}
getSelectedObject={getSelectedDatabaseObjectAppObject} getSelectedObject={getSelectedDatabaseObjectAppObject}
selectedObjectMatcher={matchDatabaseObjectAppObject} selectedObjectMatcher={matchDatabaseObjectAppObject}
module={databaseObjectAppObject} handleObjectClick={(data, options) => databaseObjectAppObject.handleObjectClick(data, options)}
onScrollTop={() => { onScrollTop={() => {
domContainer?.scrollTop(); domContainer?.scrollTop();
}} }}
onFocusFilterBox={() => { onFocusFilterBox={text => {
domFilter?.focus(); domFilter?.focus(text);
}} }}
> >
<AppObjectList <AppObjectList
@@ -224,3 +265,12 @@
{/if} {/if}
</WidgetsInnerContainer> </WidgetsInnerContainer>
{/if} {/if}
<style>
.no-focused-info {
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
}
</style>

View File

@@ -1,12 +1,14 @@
<script lang="ts"> <script lang="ts">
let domDiv; let domDiv;
export let hideContent = false;
export function scrollTop() { export function scrollTop() {
domDiv.scrollTop = 0; domDiv.scrollTop = 0;
} }
</script> </script>
<div on:drop bind:this={domDiv}><slot /></div> <div on:drop bind:this={domDiv} class:hideContent><slot /></div>
<style> <style>
div { div {
@@ -15,4 +17,8 @@
overflow-y: auto; overflow-y: auto;
width: var(--dim-left-panel-width); width: var(--dim-left-panel-width);
} }
.hideContent {
visibility: hidden;
}
</style> </style>