mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-11-01 07:48:03 -06:00
feat: toggle Object Explorer by re-clicking Default workspace icon (#10254)
Clicking the Default workspace icon whilst that workspace is already current now collapses the Object Explorer, and clicking it again brings it back, in the manner of the VS Code side bar. The choice is remembered in a new Browser/ObjectExplorerVisible setting, so it survives a refresh, and File → Reset Layout restores the panel along with the rest of the layout. The panel is hidden with CSS rather than unmounted, so the tree keeps its expansion and selection state whilst it is out of the way, and because rc-dock sizes box children with flex, the main panel takes the freed width with no gap left behind. Folded in during review: The Object Explorer keyboard shortcut no longer does nothing when the sidebar is collapsed. It asks for the panel to be shown first, through a pgadmin:object-explorer:show event on pgAdmin.Browser.Events. The focus() call in bindLeftTree() was already inert, since the panel is a div with no tabindex, so the useful part of that shortcut is the tree selection that follows it. LayoutDocker now fires a RESET event which BrowserComponent listens for, rather than resetLayout() being monkey-patched onto the shared docker instance. That removes a mutation of an object other code holds a reference to. A failed attempt to persist the setting is logged rather than silently discarded, since otherwise the sidebar could disagree with the server after the next refresh with nothing to explain why. The icon's tooltip says Show or Hide Object Explorer while it acts as a toggle, instead of describing a workspace switch that will not happen, and aria-pressed exposes the state to assistive technology. Tests in web/regression/javascript/workspaces/WorkspaceProvider.spec.jsx cover the default, the saved setting being honoured, each toggle persisting its new value, two toggles in one tick resolving in order, and the reveal event.
This commit is contained in:
1 parent
7b592f2e82
commit
549d71a203
10 files changed
+276
-20
No files matched your search
@@ -144,3 +144,7 @@ export const AllPermissionTypes = {
|
||||
STORAGE_ADD_FOLDER: 'storage_add_folder',
|
||||
STORAGE_REMOVE_FOLDER: 'storage_remove_folder'
|
||||
};
|
||||
|
||||
// Fired on pgAdmin.Browser.Events when code outside React, e.g. the keyboard
|
||||
// shortcut that focuses the tree, needs the Object Explorer on screen.
|
||||
export const SHOW_OBJECT_EXPLORER_EVENT = 'pgadmin:object-explorer:show';
|
||||
@@ -14,6 +14,7 @@ import * as commonUtils from '../../../static/js/utils';
|
||||
import gettext from 'sources/gettext';
|
||||
import pgWindow from 'sources/window';
|
||||
import usePreferences from '../../../preferences/static/js/store';
|
||||
import { SHOW_OBJECT_EXPLORER_EVENT } from './constants';
|
||||
|
||||
|
||||
const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
@@ -203,8 +204,14 @@ _.extend(pgBrowser.keyboardNavigation, {
|
||||
bindLeftTree: function() {
|
||||
const tree = this.getTreeDetails();
|
||||
|
||||
document.querySelector('[id="id-object-explorer"]').focus();
|
||||
tree.t.select(tree.i);
|
||||
// The Object Explorer can be collapsed, and focus() on a hidden element
|
||||
// does nothing at all, so ask for it to be shown first. Focus is deferred
|
||||
// to give React a chance to paint the panel before we move into it.
|
||||
pgAdmin.Browser.Events.trigger(SHOW_OBJECT_EXPLORER_EVENT);
|
||||
setTimeout(()=>{
|
||||
document.querySelector('[id="id-object-explorer"]')?.focus();
|
||||
tree.t.select(tree.i);
|
||||
}, 0);
|
||||
},
|
||||
bindSubMenuQueryTool: function() {
|
||||
const tree = this.getTreeDetails();
|
||||
|
||||
@@ -7,21 +7,61 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React, { useContext, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BROWSER_PANELS, WORKSPACES } from '../../../../browser/static/js/constants';
|
||||
import { BROWSER_PANELS, SHOW_OBJECT_EXPLORER_EVENT, WORKSPACES }
|
||||
from '../../../../browser/static/js/constants';
|
||||
import { usePgAdmin } from '../../../../static/js/PgAdminProvider';
|
||||
import usePreferences from '../../../../preferences/static/js/store';
|
||||
import getApiInstance from '../../../../static/js/api_instance';
|
||||
import url_for from 'sources/url_for';
|
||||
import { config } from './config';
|
||||
|
||||
const WorkspaceContext = React.createContext();
|
||||
const OBJECT_EXPLORER_VISIBLE_SETTING = 'Browser/ObjectExplorerVisible';
|
||||
|
||||
// Serialize visibility writes so overlapping store requests cannot apply out of order.
|
||||
let objectExplorerPersistChain = Promise.resolve();
|
||||
|
||||
export const useWorkspace = ()=>useContext(WorkspaceContext);
|
||||
|
||||
function getSavedObjectExplorerVisible(pgAdmin) {
|
||||
const saved = pgAdmin?.Browser?.utils?.layout?.[OBJECT_EXPLORER_VISIBLE_SETTING];
|
||||
if (saved === undefined || saved === null || saved === '') {
|
||||
return true;
|
||||
}
|
||||
return saved === true || saved === 'true';
|
||||
}
|
||||
|
||||
function persistObjectExplorerVisible(pgAdmin, visible) {
|
||||
if (pgAdmin?.Browser?.utils?.layout) {
|
||||
pgAdmin.Browser.utils.layout[OBJECT_EXPLORER_VISIBLE_SETTING] = String(visible);
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('setting', OBJECT_EXPLORER_VISIBLE_SETTING);
|
||||
formData.append('value', String(visible));
|
||||
objectExplorerPersistChain = objectExplorerPersistChain
|
||||
.catch(()=>{/* The previous write failed and has already been logged. */})
|
||||
.then(()=>getApiInstance().post(url_for('settings.store_bulk'), formData))
|
||||
.catch((error)=>{
|
||||
// Not worth interrupting the user for, but silence would leave the
|
||||
// sidebar disagreeing with the server after the next refresh with no
|
||||
// way to tell why.
|
||||
console.warn('Unable to save the Object Explorer visibility setting.',
|
||||
error);
|
||||
});
|
||||
}
|
||||
|
||||
export function WorkspaceProvider({children}) {
|
||||
const pgAdmin = usePgAdmin();
|
||||
const [currentWorkspace, setCurrentWorkspace] = useState(WORKSPACES.DEFAULT);
|
||||
const [isObjectExplorerVisible, setIsObjectExplorerVisible] = useState(
|
||||
() => getSavedObjectExplorerVisible(pgAdmin)
|
||||
);
|
||||
const lastSelectedTreeItem = useRef();
|
||||
// Keep latest visibility for consecutive toggles before React re-renders.
|
||||
const isObjectExplorerVisibleRef = useRef(isObjectExplorerVisible);
|
||||
isObjectExplorerVisibleRef.current = isObjectExplorerVisible;
|
||||
const isClassic = (usePreferences()?.getPreferencesForModule('misc')?.layout ?? 'classic') == 'classic';
|
||||
const openInResWorkspace = usePreferences()?.getPreferencesForModule('misc')?.open_in_res_workspace && !isClassic;
|
||||
|
||||
@@ -114,6 +154,23 @@ export function WorkspaceProvider({children}) {
|
||||
changeWorkspace(WORKSPACES.DEFAULT);
|
||||
};
|
||||
|
||||
const setObjectExplorerVisible = useCallback((visible)=>{
|
||||
isObjectExplorerVisibleRef.current = visible;
|
||||
setIsObjectExplorerVisible(visible);
|
||||
persistObjectExplorerVisible(pgAdmin, visible);
|
||||
}, [pgAdmin]);
|
||||
|
||||
const toggleObjectExplorer = useCallback(()=>{
|
||||
setObjectExplorerVisible(!isObjectExplorerVisibleRef.current);
|
||||
}, [setObjectExplorerVisible]);
|
||||
|
||||
useEffect(()=>{
|
||||
// Code outside React, e.g. the shortcut that focuses the tree, asks for
|
||||
// the panel this way rather than reaching into this state.
|
||||
return pgAdmin.Browser.Events.registerListener(
|
||||
SHOW_OBJECT_EXPLORER_EVENT, ()=>setObjectExplorerVisible(true));
|
||||
}, [pgAdmin, setObjectExplorerVisible]);
|
||||
|
||||
const value = useMemo(()=>({
|
||||
config: config,
|
||||
currentWorkspace: currentWorkspace,
|
||||
@@ -121,8 +178,11 @@ export function WorkspaceProvider({children}) {
|
||||
changeWorkspace,
|
||||
hasOpenTabs,
|
||||
getLayoutObj,
|
||||
onWorkspaceDisabled
|
||||
}), [currentWorkspace, isClassic]);
|
||||
onWorkspaceDisabled,
|
||||
isObjectExplorerVisible,
|
||||
setObjectExplorerVisible,
|
||||
toggleObjectExplorer,
|
||||
}), [currentWorkspace, isClassic, isObjectExplorerVisible, setObjectExplorerVisible, toggleObjectExplorer]);
|
||||
|
||||
return <WorkspaceContext.Provider value={value}>
|
||||
{children}
|
||||
|
||||
@@ -45,9 +45,15 @@ const StyledWorkspaceButton = styled(PgIconButton)(({theme}) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
function WorkspaceButton({menuItem, value, options, ...props}) {
|
||||
const {currentWorkspace, hasOpenTabs, getLayoutObj, onWorkspaceDisabled, changeWorkspace} = useWorkspace();
|
||||
const active = value == currentWorkspace;
|
||||
function WorkspaceButton({menuItem, value, options, title: titleProp, ...props}) {
|
||||
const {
|
||||
currentWorkspace, hasOpenTabs, getLayoutObj, onWorkspaceDisabled,
|
||||
changeWorkspace, isObjectExplorerVisible, toggleObjectExplorer,
|
||||
} = useWorkspace();
|
||||
// Default workspace icon is active only when OE sidebar is visible (VS Code-like).
|
||||
const active = value == currentWorkspace && (
|
||||
value != WORKSPACES.DEFAULT || isObjectExplorerVisible
|
||||
);
|
||||
const [disabled, setDisabled] = useState();
|
||||
|
||||
useEffect(()=>{
|
||||
@@ -73,10 +79,24 @@ function WorkspaceButton({menuItem, value, options, ...props}) {
|
||||
if(disabled && active) {
|
||||
onWorkspaceDisabled();
|
||||
}
|
||||
}, [disabled]);
|
||||
}, [disabled, active]);
|
||||
|
||||
// While the Default workspace is current its icon toggles the Object
|
||||
// Explorer, so say so rather than leaving the label describing a switch that
|
||||
// will not happen, and expose the state to assistive technology.
|
||||
const isOEToggle = value == WORKSPACES.DEFAULT
|
||||
&& currentWorkspace == WORKSPACES.DEFAULT;
|
||||
// titleProp is pulled out of props above deliberately: props are spread
|
||||
// after title below, so anything left in there would override this.
|
||||
const title = isOEToggle
|
||||
? (isObjectExplorerVisible
|
||||
? gettext('Hide Object Explorer')
|
||||
: gettext('Show Object Explorer'))
|
||||
: (titleProp ?? menuItem?.label ?? '');
|
||||
|
||||
return (
|
||||
<StyledWorkspaceButton className={active ? 'active': ''} title={menuItem?.label??''}
|
||||
<StyledWorkspaceButton className={active ? 'active': ''} title={title}
|
||||
aria-pressed={isOEToggle ? isObjectExplorerVisible : undefined}
|
||||
{...props}
|
||||
onClick={()=>{
|
||||
if(menuItem) {
|
||||
@@ -84,6 +104,11 @@ function WorkspaceButton({menuItem, value, options, ...props}) {
|
||||
} else {
|
||||
// Check permission and call.
|
||||
withCheckPermission(options, () => {
|
||||
// Re-clicking the Default workspace icon toggles Object Explorer visibility.
|
||||
if (value == WORKSPACES.DEFAULT && currentWorkspace == WORKSPACES.DEFAULT) {
|
||||
toggleObjectExplorer();
|
||||
return;
|
||||
}
|
||||
changeWorkspace(value);
|
||||
})();
|
||||
}
|
||||
@@ -93,6 +118,7 @@ function WorkspaceButton({menuItem, value, options, ...props}) {
|
||||
);
|
||||
}
|
||||
WorkspaceButton.propTypes = {
|
||||
title: PropTypes.string,
|
||||
menuItem: PropTypes.object,
|
||||
active: PropTypes.bool,
|
||||
changeWorkspace: PropTypes.func,
|
||||
|
||||
@@ -97,7 +97,12 @@ def get_workspace_layout():
|
||||
|
||||
|
||||
def get_layout():
|
||||
layout = {'Browser/Layout': get_setting('Browser/Layout', default='')}
|
||||
layout = {
|
||||
'Browser/Layout': get_setting('Browser/Layout', default=''),
|
||||
# Persist Object Explorer sidebar visibility across refresh/navigation
|
||||
'Browser/ObjectExplorerVisible': get_setting(
|
||||
'Browser/ObjectExplorerVisible', default='true'),
|
||||
}
|
||||
layout = {**layout, **get_workspace_layout()}
|
||||
return layout
|
||||
|
||||
@@ -160,12 +165,20 @@ def reset_layout():
|
||||
.filter(Setting.user_id == current_user.id) \
|
||||
.filter((Setting.setting == request.params['setting'])) \
|
||||
.delete()
|
||||
# Resetting Browser layout restores Object Explorer visibility.
|
||||
if request.params['setting'] == 'Browser/Layout':
|
||||
db.session.query(Setting) \
|
||||
.filter(Setting.user_id == current_user.id) \
|
||||
.filter(Setting.setting ==
|
||||
'Browser/ObjectExplorerVisible') \
|
||||
.delete()
|
||||
else:
|
||||
db.session.query(Setting) \
|
||||
.filter(Setting.user_id == current_user.id)\
|
||||
.filter((Setting.setting == 'Browser/Layout') |
|
||||
(Setting.setting == 'SQLEditor/Layout') |
|
||||
(Setting.setting == 'Debugger/Layout'))\
|
||||
(Setting.setting == 'Debugger/Layout') |
|
||||
(Setting.setting == 'Browser/ObjectExplorerVisible'))\
|
||||
.delete()
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@@ -113,7 +113,22 @@ let defaultLayout = {
|
||||
|
||||
function Layouts({browser}) {
|
||||
const pgAdmin = usePgAdmin();
|
||||
const {config, enabled, currentWorkspace} = useWorkspace();
|
||||
const {
|
||||
config, enabled, currentWorkspace, isObjectExplorerVisible, setObjectExplorerVisible,
|
||||
} = useWorkspace();
|
||||
|
||||
useEffect(()=>{
|
||||
// File → Reset Layout puts the layout back to its defaults, which has to
|
||||
// bring the Object Explorer back with it. The docker instance is set by
|
||||
// the ref callback below, which runs during commit, so it is available by
|
||||
// the time this effect runs.
|
||||
const docker = pgAdmin.Browser.docker.default_workspace;
|
||||
if(!docker) return;
|
||||
|
||||
return docker.eventBus.registerListener(
|
||||
LAYOUT_EVENTS.RESET, ()=>setObjectExplorerVisible(true));
|
||||
}, [setObjectExplorerVisible]);
|
||||
|
||||
return (
|
||||
<ApplicationStateProvider>
|
||||
<div style={{display: 'flex', height: (browser != 'Electron' ? 'calc(100% - 30px)' : '100%')}}>
|
||||
@@ -133,6 +148,7 @@ function Layouts({browser}) {
|
||||
resetToTabPanel={BROWSER_PANELS.MAIN}
|
||||
enableToolEvents
|
||||
isLayoutVisible={!enabled || currentWorkspace == WORKSPACES.DEFAULT}
|
||||
className={(!enabled || isObjectExplorerVisible) ? undefined : 'object-explorer-collapsed'}
|
||||
/>
|
||||
{enabled && config.map((item)=>(
|
||||
<Layout
|
||||
|
||||
@@ -190,6 +190,16 @@ export default function rcdockOverride(theme) {
|
||||
color: theme.custom.icon.contrastText
|
||||
}
|
||||
},
|
||||
// Collapse Object Explorer without unmounting so tree state is preserved.
|
||||
'.object-explorer-collapsed': {
|
||||
'& .dock-style-object-explorer': {
|
||||
display: 'none !important',
|
||||
},
|
||||
// Cover divider on either side of the OE panel (layout order can vary).
|
||||
'& .dock-style-object-explorer + .dock-divider, & .dock-divider:has(+ .dock-style-object-explorer)': {
|
||||
display: 'none !important',
|
||||
},
|
||||
},
|
||||
'.dock-dropdown': {
|
||||
zIndex: 1004,
|
||||
|
||||
|
||||
@@ -445,6 +445,10 @@ export class LayoutDocker {
|
||||
|
||||
focusOn && this.focus(focusOn);
|
||||
this.saveLayout();
|
||||
// Anything that tracks state alongside the layout, e.g. whether the
|
||||
// Object Explorer is collapsed, needs to know the layout went back to
|
||||
// its defaults.
|
||||
this.eventBus.fireEvent(LAYOUT_EVENTS.RESET);
|
||||
}
|
||||
|
||||
static getPanel({icon, title, closable, tooltip, renamable, manualClose, bgcolor, fgcolor, server_id, ...attrs}) {
|
||||
@@ -561,7 +565,7 @@ export function getDefaultGroup() {
|
||||
};
|
||||
}
|
||||
|
||||
export default function Layout({groups, noContextGroups, getLayoutInstance, layoutId, savedLayout, resetToTabPanel, enableToolEvents=false, isLayoutVisible=true, ...props}) {
|
||||
export default function Layout({groups, noContextGroups, getLayoutInstance, layoutId, savedLayout, resetToTabPanel, enableToolEvents=false, isLayoutVisible=true, className, ...props}) {
|
||||
const [[contextPos, contextPanelId, contextExtraMenus], setContextPos] = React.useState([null, null, null]);
|
||||
const defaultGroups = React.useMemo(()=>({
|
||||
'dialogs': getDialogsGroup(),
|
||||
@@ -702,7 +706,8 @@ export default function Layout({groups, noContextGroups, getLayoutInstance, layo
|
||||
return (
|
||||
<ApplicationStateProvider>
|
||||
<LayoutDockerContext.Provider value={layoutDockerObj}>
|
||||
<Box height="100%" width="100%" display={isLayoutVisible ? 'initial' : 'none'} >
|
||||
<Box height="100%" width="100%" display={isLayoutVisible ? 'initial' : 'none'}
|
||||
className={className} >
|
||||
{useMemo(()=>(<DockLayout
|
||||
style={{
|
||||
height: '100%',
|
||||
@@ -750,7 +755,8 @@ Layout.propTypes = {
|
||||
savedLayout: PropTypes.string,
|
||||
resetToTabPanel: PropTypes.string,
|
||||
enableToolEvents: PropTypes.bool,
|
||||
isLayoutVisible: PropTypes.bool
|
||||
isLayoutVisible: PropTypes.bool,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
|
||||
@@ -765,5 +771,6 @@ export const LAYOUT_EVENTS = {
|
||||
CLOSING: 'closing',
|
||||
CONTEXT: 'context',
|
||||
CHANGE: 'change',
|
||||
REFRESH_TITLE: 'refresh-title'
|
||||
REFRESH_TITLE: 'refresh-title',
|
||||
RESET: 'reset'
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2026, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { act, render } from '@testing-library/react';
|
||||
|
||||
import { SHOW_OBJECT_EXPLORER_EVENT } from
|
||||
'../../../pgadmin/browser/static/js/constants';
|
||||
import { WorkspaceProvider, useWorkspace } from
|
||||
'../../../pgadmin/misc/workspaces/static/js/WorkspaceProvider';
|
||||
import { PgAdminProvider } from '../../../pgadmin/static/js/PgAdminProvider';
|
||||
import pgAdmin from '../fake_pgadmin';
|
||||
import getApiInstance from '../../../pgadmin/static/js/api_instance';
|
||||
|
||||
jest.mock('../../../pgadmin/static/js/api_instance');
|
||||
|
||||
// The Object Explorer can be collapsed, and that choice is persisted, so the
|
||||
// visibility state has to survive a reload and the writes must not be able to
|
||||
// land out of order (#9631).
|
||||
describe('WorkspaceProvider Object Explorer visibility', () => {
|
||||
let post;
|
||||
let workspace;
|
||||
|
||||
const Consumer = () => {
|
||||
workspace = useWorkspace();
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderProvider = async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<PgAdminProvider value={pgAdmin}>
|
||||
<WorkspaceProvider>
|
||||
<Consumer />
|
||||
</WorkspaceProvider>
|
||||
</PgAdminProvider>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
workspace = undefined;
|
||||
post = jest.fn(() => Promise.resolve({data: {success: 1}}));
|
||||
getApiInstance.mockReturnValue({post});
|
||||
pgAdmin.Browser.utils = {...(pgAdmin.Browser.utils ?? {}), layout: {}};
|
||||
});
|
||||
|
||||
it('is visible by default', async () => {
|
||||
await renderProvider();
|
||||
expect(workspace.isObjectExplorerVisible).toBe(true);
|
||||
});
|
||||
|
||||
it('starts hidden when the saved setting says so', async () => {
|
||||
pgAdmin.Browser.utils.layout['Browser/ObjectExplorerVisible'] = 'false';
|
||||
await renderProvider();
|
||||
expect(workspace.isObjectExplorerVisible).toBe(false);
|
||||
});
|
||||
|
||||
it('toggles, and persists each new value', async () => {
|
||||
await renderProvider();
|
||||
|
||||
await act(async () => {
|
||||
workspace.toggleObjectExplorer();
|
||||
});
|
||||
expect(workspace.isObjectExplorerVisible).toBe(false);
|
||||
|
||||
await act(async () => {
|
||||
workspace.toggleObjectExplorer();
|
||||
});
|
||||
expect(workspace.isObjectExplorerVisible).toBe(true);
|
||||
|
||||
expect(post).toHaveBeenCalledTimes(2);
|
||||
const values = post.mock.calls.map(([, formData]) =>
|
||||
formData.get('value'));
|
||||
expect(values).toEqual(['false', 'true']);
|
||||
});
|
||||
|
||||
/* Two toggles in one tick must not both act on the same captured state,
|
||||
* which would leave the sidebar where it started while telling the server
|
||||
* it had changed. */
|
||||
it('handles consecutive toggles before a re-render', async () => {
|
||||
await renderProvider();
|
||||
|
||||
await act(async () => {
|
||||
workspace.toggleObjectExplorer();
|
||||
workspace.toggleObjectExplorer();
|
||||
});
|
||||
|
||||
expect(workspace.isObjectExplorerVisible).toBe(true);
|
||||
const values = post.mock.calls.map(([, formData]) =>
|
||||
formData.get('value'));
|
||||
expect(values).toEqual(['false', 'true']);
|
||||
});
|
||||
|
||||
it('shows the panel when asked to by code outside React', async () => {
|
||||
pgAdmin.Browser.utils.layout['Browser/ObjectExplorerVisible'] = 'false';
|
||||
await renderProvider();
|
||||
expect(workspace.isObjectExplorerVisible).toBe(false);
|
||||
|
||||
await act(async () => {
|
||||
pgAdmin.Browser.Events.trigger(SHOW_OBJECT_EXPLORER_EVENT);
|
||||
});
|
||||
|
||||
expect(workspace.isObjectExplorerVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1005,12 +1005,14 @@ def reset_layout_db(user_id=None):
|
||||
if user_id is None:
|
||||
cur.execute(
|
||||
'DELETE FROM SETTING WHERE SETTING in '
|
||||
'("Browser/Layout", "SQLEditor/Layout", "Debugger/Layout")'
|
||||
'("Browser/Layout", "SQLEditor/Layout", "Debugger/Layout",'
|
||||
' "Browser/ObjectExplorerVisible")'
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
'DELETE FROM SETTING WHERE SETTING in '
|
||||
'("Browser/Layout", "SQLEditor/Layout", "Debugger/Layout")'
|
||||
'("Browser/Layout", "SQLEditor/Layout", "Debugger/Layout",'
|
||||
' "Browser/ObjectExplorerVisible")'
|
||||
' AND USER_ID=?', user_id
|
||||
)
|
||||
cur.execute('DELETE FROM process')
|
||||
|
||||
Reference in new issue
Block a user