Skip to content

Commit be2f774

Browse files
authored
chore(workspaces): move all workspace tab providers into a single component COMPASS-9413 (#6970)
chore(workspaces): move all workspace tab providers into a single component
1 parent 1f76f3c commit be2f774

File tree

3 files changed

+165
-149
lines changed

3 files changed

+165
-149
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import React, { useCallback, useEffect, useRef } from 'react';
2+
import {
3+
getLocalAppRegistryForTab,
4+
type WorkspaceTab,
5+
} from '../stores/workspaces';
6+
import { NamespaceProvider } from '@mongodb-js/compass-app-stores/provider';
7+
import { ConnectionInfoProvider } from '@mongodb-js/compass-connections/provider';
8+
import { rafraf } from '@mongodb-js/compass-components';
9+
import { useOnTabReplace } from './workspace-close-handler';
10+
import {
11+
useTabState,
12+
WorkspaceTabStateProvider,
13+
} from './workspace-tab-state-provider';
14+
import { AppRegistryProvider } from 'hadron-app-registry';
15+
16+
function getInitialPropsForWorkspace(tab: WorkspaceTab) {
17+
switch (tab.type) {
18+
case 'Welcome':
19+
case 'My Queries':
20+
case 'Data Modeling':
21+
case 'Performance':
22+
case 'Databases':
23+
return null;
24+
case 'Shell':
25+
return {
26+
initialEvaluate: tab.initialEvaluate,
27+
initialInput: tab.initialInput,
28+
};
29+
case 'Collections':
30+
return { namespace: tab.namespace };
31+
case 'Collection': {
32+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
33+
const { id, type, connectionId, ...collectionMetadata } = tab;
34+
return { tabId: id, ...collectionMetadata };
35+
}
36+
}
37+
}
38+
39+
const TabCloseHandler: React.FunctionComponent = ({ children }) => {
40+
const mountedRef = useRef(false);
41+
const [hasInteractedOnce, setHasInteractedOnce] = useTabState(
42+
'hasInteractedOnce',
43+
false
44+
);
45+
46+
useEffect(() => {
47+
mountedRef.current = true;
48+
return () => {
49+
mountedRef.current = false;
50+
};
51+
});
52+
53+
const markAsInteracted = useCallback(() => {
54+
// Make sure we don't count clicking on buttons that actually cause the
55+
// workspace to change, like using breadcrumbs or clicking on an item in the
56+
// Databases / Collections list. There are certain corner-cases this doesn't
57+
// handle, but it's good enough to prevent most cases where users can lose
58+
// content by accident
59+
rafraf(() => {
60+
if (mountedRef.current) {
61+
setHasInteractedOnce(true);
62+
}
63+
});
64+
}, [setHasInteractedOnce]);
65+
66+
useOnTabReplace(() => {
67+
return !hasInteractedOnce;
68+
});
69+
70+
return (
71+
// We're not using these for actual user interactions, just to capture the
72+
// interacted state
73+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
74+
<div
75+
style={{ display: 'contents' }}
76+
onKeyDown={markAsInteracted}
77+
onClickCapture={markAsInteracted}
78+
>
79+
{children}
80+
</div>
81+
);
82+
};
83+
84+
const WorkspaceTabContextProvider: React.FunctionComponent<{
85+
tab: WorkspaceTab;
86+
sectionType: 'tab-content' | 'tab-title';
87+
onNamespaceNotFound?: (
88+
tab: Extract<WorkspaceTab, { namespace: string }>,
89+
fallbackNamespace: string | null
90+
) => void;
91+
children: React.JSX.Element;
92+
}> = ({ tab, onNamespaceNotFound, sectionType: type, children }) => {
93+
const initialProps = getInitialPropsForWorkspace(tab);
94+
95+
if (initialProps) {
96+
children = React.cloneElement(children, initialProps);
97+
}
98+
99+
if ('namespace' in tab) {
100+
children = (
101+
<NamespaceProvider
102+
namespace={tab.namespace}
103+
onNamespaceFallbackSelect={(ns) => {
104+
onNamespaceNotFound?.(tab, ns);
105+
}}
106+
>
107+
{children}
108+
</NamespaceProvider>
109+
);
110+
}
111+
112+
if ('connectionId' in tab) {
113+
children = (
114+
<ConnectionInfoProvider connectionInfoId={tab.connectionId}>
115+
{children}
116+
</ConnectionInfoProvider>
117+
);
118+
}
119+
120+
if (type === 'tab-content') {
121+
children = <TabCloseHandler>{children}</TabCloseHandler>;
122+
}
123+
124+
return (
125+
<WorkspaceTabStateProvider id={tab.id}>
126+
<AppRegistryProvider
127+
key={tab.id}
128+
scopeName="Workspace Tab"
129+
localAppRegistry={getLocalAppRegistryForTab(tab.id)}
130+
deactivateOnUnmount={false}
131+
>
132+
{children}
133+
</AppRegistryProvider>
134+
</WorkspaceTabStateProvider>
135+
);
136+
};
137+
138+
export { WorkspaceTabContextProvider };

packages/compass-workspaces/src/components/workspaces-provider.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ export const useWorkspacePlugins = () => {
3030
hasWorkspacePlugin: <T extends AnyWorkspace['type']>(name: T) => {
3131
return workspaces.some((ws) => ws.name === name);
3232
},
33-
getWorkspacePluginByName: <T extends AnyWorkspace['type']>(name: T) => {
33+
getWorkspacePluginByName: <T extends AnyWorkspace['type']>(name?: T) => {
34+
if (!name) {
35+
return null;
36+
}
3437
const plugin = workspaces.find((ws) => ws.name === name);
3538
if (!plugin) {
3639
throw new Error(

packages/compass-workspaces/src/components/workspaces.tsx

Lines changed: 23 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
2-
import { AppRegistryProvider } from 'hadron-app-registry';
1+
import React, { useCallback, useMemo } from 'react';
32
import {
43
ErrorBoundary,
54
MongoDBLogoMark,
65
WorkspaceTabs,
76
css,
87
palette,
9-
rafraf,
108
spacing,
119
useDarkMode,
1210
} from '@mongodb-js/compass-components';
@@ -20,7 +18,6 @@ import type {
2018
import {
2119
closeTab,
2220
getActiveTab,
23-
getLocalAppRegistryForTab,
2421
moveTab,
2522
openFallbackWorkspace,
2623
openTabFromCurrent,
@@ -32,17 +29,9 @@ import { useWorkspacePlugins } from './workspaces-provider';
3229
import toNS from 'mongodb-ns';
3330
import { useLogger } from '@mongodb-js/compass-logging/provider';
3431
import { connect } from '../stores/context';
35-
import {
36-
WorkspaceTabStateProvider,
37-
useTabState,
38-
} from './workspace-tab-state-provider';
39-
import { useOnTabReplace } from './workspace-close-handler';
40-
import { NamespaceProvider } from '@mongodb-js/compass-app-stores/provider';
41-
import {
42-
ConnectionInfoProvider,
43-
useTabConnectionTheme,
44-
} from '@mongodb-js/compass-connections/provider';
32+
import { useTabConnectionTheme } from '@mongodb-js/compass-connections/provider';
4533
import { useConnectionsListRef } from '@mongodb-js/compass-connections/provider';
34+
import { WorkspaceTabContextProvider } from './workspace-tab-context-provider';
4635

4736
type Tooltip = [string, string][];
4837

@@ -64,51 +53,6 @@ const EmptyWorkspaceContent = () => {
6453
);
6554
};
6655

67-
const ActiveTabCloseHandler: React.FunctionComponent = ({ children }) => {
68-
const mountedRef = useRef(false);
69-
const [hasInteractedOnce, setHasInteractedOnce] = useTabState(
70-
'hasInteractedOnce',
71-
false
72-
);
73-
74-
useEffect(() => {
75-
mountedRef.current = true;
76-
return () => {
77-
mountedRef.current = false;
78-
};
79-
});
80-
81-
const markAsInteracted = useCallback(() => {
82-
// Make sure we don't count clicking on buttons that actually cause the
83-
// workspace to change, like using breadcrumbs or clicking on an item in the
84-
// Databases / Collections list. There are certain corner-cases this doesn't
85-
// handle, but it's good enough to prevent most cases where users can lose
86-
// content by accident
87-
rafraf(() => {
88-
if (mountedRef.current) {
89-
setHasInteractedOnce(true);
90-
}
91-
});
92-
}, [setHasInteractedOnce]);
93-
94-
useOnTabReplace(() => {
95-
return !hasInteractedOnce;
96-
});
97-
98-
return (
99-
// We're not using these for actual user interactions, just to capture the
100-
// interacted state
101-
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
102-
<div
103-
style={{ display: 'contents' }}
104-
onKeyDown={markAsInteracted}
105-
onClickCapture={markAsInteracted}
106-
>
107-
{children}
108-
</div>
109-
);
110-
};
111-
11256
const workspacesContainerStyles = css({
11357
display: 'grid',
11458
width: '100%',
@@ -309,71 +253,7 @@ const CompassWorkspaces: React.FunctionComponent<CompassWorkspacesProps> = ({
309253
}, [tabs, collectionInfo, databaseInfo, getThemeOf, getConnectionById]);
310254

311255
const activeTabIndex = tabs.findIndex((tab) => tab === activeTab);
312-
313-
const activeWorkspaceElement = useMemo(() => {
314-
switch (activeTab?.type) {
315-
case 'Welcome':
316-
case 'My Queries':
317-
case 'Data Modeling': {
318-
const Component = getWorkspacePluginByName(activeTab.type);
319-
return <Component></Component>;
320-
}
321-
case 'Shell': {
322-
const Component = getWorkspacePluginByName(activeTab.type);
323-
return (
324-
<ConnectionInfoProvider connectionInfoId={activeTab.connectionId}>
325-
<Component
326-
initialEvaluate={activeTab.initialEvaluate}
327-
initialInput={activeTab.initialInput}
328-
></Component>
329-
</ConnectionInfoProvider>
330-
);
331-
}
332-
case 'Performance':
333-
case 'Databases': {
334-
const Component = getWorkspacePluginByName(activeTab.type);
335-
return (
336-
<ConnectionInfoProvider connectionInfoId={activeTab.connectionId}>
337-
<Component></Component>
338-
</ConnectionInfoProvider>
339-
);
340-
}
341-
case 'Collections': {
342-
const Component = getWorkspacePluginByName(activeTab.type);
343-
return (
344-
<ConnectionInfoProvider connectionInfoId={activeTab.connectionId}>
345-
<NamespaceProvider
346-
namespace={activeTab.namespace}
347-
onNamespaceFallbackSelect={(ns) => {
348-
onNamespaceNotFound(activeTab, ns);
349-
}}
350-
>
351-
<Component namespace={activeTab.namespace}></Component>
352-
</NamespaceProvider>
353-
</ConnectionInfoProvider>
354-
);
355-
}
356-
case 'Collection': {
357-
const Component = getWorkspacePluginByName(activeTab.type);
358-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
359-
const { id, type, connectionId, ...collectionMetadata } = activeTab;
360-
return (
361-
<ConnectionInfoProvider connectionInfoId={activeTab.connectionId}>
362-
<NamespaceProvider
363-
namespace={activeTab.namespace}
364-
onNamespaceFallbackSelect={(ns) => {
365-
onNamespaceNotFound(activeTab, ns);
366-
}}
367-
>
368-
<Component tabId={id} {...collectionMetadata}></Component>
369-
</NamespaceProvider>
370-
</ConnectionInfoProvider>
371-
);
372-
}
373-
default:
374-
return null;
375-
}
376-
}, [activeTab, getWorkspacePluginByName, onNamespaceNotFound]);
256+
const WorkspaceComponent = getWorkspacePluginByName(activeTab?.type);
377257

378258
const onCreateNewTab = useCallback(() => {
379259
onCreateTab(openOnEmptyWorkspace);
@@ -397,31 +277,26 @@ const CompassWorkspaces: React.FunctionComponent<CompassWorkspacesProps> = ({
397277
></WorkspaceTabs>
398278

399279
<div className={workspacesContentStyles}>
400-
{activeTab && activeWorkspaceElement ? (
401-
<WorkspaceTabStateProvider id={activeTab.id}>
402-
<AppRegistryProvider
403-
key={activeTab.id}
404-
scopeName="Workspace Tab"
405-
localAppRegistry={getLocalAppRegistryForTab(activeTab.id)}
406-
deactivateOnUnmount={false}
280+
{activeTab && WorkspaceComponent ? (
281+
<ErrorBoundary
282+
displayName={activeTab.type}
283+
onError={(error, errorInfo) => {
284+
log.error(
285+
mongoLogId(1_001_000_277),
286+
'Workspace',
287+
'Rendering workspace tab failed',
288+
{ name: activeTab.type, error: error.message, errorInfo }
289+
);
290+
}}
291+
>
292+
<WorkspaceTabContextProvider
293+
tab={activeTab}
294+
sectionType="tab-content"
295+
onNamespaceNotFound={onNamespaceNotFound}
407296
>
408-
<ActiveTabCloseHandler>
409-
<ErrorBoundary
410-
displayName={activeTab.type}
411-
onError={(error, errorInfo) => {
412-
log.error(
413-
mongoLogId(1_001_000_277),
414-
'Workspace',
415-
'Rendering workspace tab failed',
416-
{ name: activeTab.type, error: error.message, errorInfo }
417-
);
418-
}}
419-
>
420-
{activeWorkspaceElement}
421-
</ErrorBoundary>
422-
</ActiveTabCloseHandler>
423-
</AppRegistryProvider>
424-
</WorkspaceTabStateProvider>
297+
<WorkspaceComponent></WorkspaceComponent>
298+
</WorkspaceTabContextProvider>
299+
</ErrorBoundary>
425300
) : (
426301
<EmptyWorkspaceContent></EmptyWorkspaceContent>
427302
)}

0 commit comments

Comments
 (0)