Skip to content

Commit 73bfc31

Browse files
committed
wip
1 parent 16bf358 commit 73bfc31

File tree

9 files changed

+127
-142
lines changed

9 files changed

+127
-142
lines changed

packages/compass-components/src/components/document-list/element.tsx

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { palette } from '@leafygreen-ui/palette';
2929
import { Icon } from '../leafygreen';
3030
import { useDarkMode } from '../../hooks/use-theme';
3131
import VisibleFieldsToggle from './visible-field-toggle';
32-
import { useContextMenuItems } from '../context-menu';
32+
import { useFieldContextMenu } from '../../hooks/use-element-context-menu';
3333

3434
function getEditorByType(type: HadronElementType['type']) {
3535
switch (type) {
@@ -411,16 +411,6 @@ export const calculateShowMoreToggleOffset = ({
411411
return spacerWidth + editableOffset + expandIconSize;
412412
};
413413

414-
// Helper function to check if a string is a URL
415-
const isValidUrl = (str: string): boolean => {
416-
try {
417-
const url = new URL(str);
418-
return url.protocol === 'http:' || url.protocol === 'https:';
419-
} catch {
420-
return false;
421-
}
422-
};
423-
424414
export const HadronElement: React.FunctionComponent<{
425415
value: HadronElementType;
426416
editable: boolean;
@@ -460,27 +450,10 @@ export const HadronElement: React.FunctionComponent<{
460450
} = useHadronElement(element);
461451

462452
// Add context menu hook for the field
463-
const fieldContextMenuRef = useContextMenuItems([
464-
{
465-
label: 'Copy field & value',
466-
onAction: () => {
467-
const fieldStr = `${key.value}: ${objectToIdiomaticEJSON(
468-
value.originalValue
469-
)}`;
470-
void navigator.clipboard.writeText(fieldStr);
471-
},
472-
},
473-
...(type.value === 'String' && isValidUrl(value.value)
474-
? [
475-
{
476-
label: 'Open URL in browser',
477-
onAction: () => {
478-
window.open(value.value, '_blank', 'noopener');
479-
},
480-
},
481-
]
482-
: []),
483-
]);
453+
const fieldContextMenuRef = useFieldContextMenu({
454+
element,
455+
fieldName: key.value,
456+
});
484457

485458
const toggleExpanded = () => {
486459
if (expanded) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { useContextMenuItems } from '../components/context-menu';
2+
import { Element } from 'hadron-document';
3+
import { objectToIdiomaticEJSON } from 'hadron-document';
4+
5+
// Helper function to check if a string is a URL
6+
export const isValidUrl = (str: string): boolean => {
7+
try {
8+
const url = new URL(str);
9+
return url.protocol === 'http:' || url.protocol === 'https:';
10+
} catch {
11+
return false;
12+
}
13+
};
14+
15+
export interface useFieldContextMenuProps {
16+
element: Element | undefined | null;
17+
fieldName: string;
18+
}
19+
20+
export function useFieldContextMenu({
21+
element,
22+
fieldName,
23+
}: useFieldContextMenuProps) {
24+
return useContextMenuItems([
25+
...(element
26+
? [
27+
{
28+
label: 'Copy field & value',
29+
onAction: () => {
30+
const fieldStr = `${fieldName}: ${objectToIdiomaticEJSON(
31+
element.currentValue
32+
)}`;
33+
void navigator.clipboard.writeText(fieldStr);
34+
},
35+
},
36+
{
37+
label: 'Copy value',
38+
onAction: () => {
39+
const valueStr = objectToIdiomaticEJSON(element.currentValue);
40+
void navigator.clipboard.writeText(valueStr);
41+
},
42+
},
43+
...(element.currentType === 'String' &&
44+
typeof element.currentValue === 'string' &&
45+
isValidUrl(element.currentValue)
46+
? [
47+
{
48+
label: 'Open URL in browser',
49+
onAction: () => {
50+
window.open(
51+
element.currentValue as string,
52+
'_blank',
53+
'noopener'
54+
);
55+
},
56+
},
57+
]
58+
: []),
59+
]
60+
: []),
61+
]);
62+
}

packages/compass-components/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,4 @@ export {
215215
export { SelectList } from './components/select-list';
216216
export { ParagraphSkeleton } from '@leafygreen-ui/skeleton-loader';
217217
export { InsightsChip } from './components/insights-chip';
218+
export { useFieldContextMenu } from './hooks/use-element-context-menu';

packages/compass-context-menu/src/context-menu-provider.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import React, {
66
createContext,
77
useContext,
88
} from 'react';
9-
import type { ContextMenuContext, ContextMenuState } from './types';
9+
import type { ContextMenuContextType, ContextMenuState } from './types';
1010
import type { EnhancedMouseEvent } from './context-menu-content';
1111
import { getContextMenuContent } from './context-menu-content';
1212

13-
export const Context = createContext<ContextMenuContext | null>(null);
13+
export const ContextMenuContext = createContext<ContextMenuContextType | null>(
14+
null
15+
);
1416

1517
export function ContextMenuProvider({
1618
children,
@@ -22,7 +24,7 @@ export function ContextMenuProvider({
2224
}>;
2325
}) {
2426
// Check if there's already a parent context menu provider
25-
const parentContext = useContext(Context);
27+
const parentContext = useContext(ContextMenuContext);
2628

2729
const [menu, setMenu] = useState<ContextMenuState>({
2830
isOpen: false,
@@ -92,9 +94,9 @@ export function ContextMenuProvider({
9294
const Wrapper = wrapper ?? React.Fragment;
9395

9496
return (
95-
<Context.Provider value={value}>
97+
<ContextMenuContext.Provider value={value}>
9698
{children}
9799
<Wrapper menu={{ ...menu, close }} />
98-
</Context.Provider>
100+
</ContextMenuContext.Provider>
99101
);
100102
}

packages/compass-context-menu/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
export { useContextMenu } from './use-context-menu';
2-
export { ContextMenuProvider } from './context-menu-provider';
2+
export {
3+
ContextMenuProvider,
4+
ContextMenuContext,
5+
} from './context-menu-provider';
36
export type {
47
ContextMenuItem,
58
ContextMenuItemGroup,

packages/compass-context-menu/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export type ContextMenuWrapperProps = {
1616
menu: ContextMenuState & { close: () => void };
1717
};
1818

19-
export type ContextMenuContext = {
19+
export type ContextMenuContextType = {
2020
close(): void;
2121
};
2222

packages/compass-context-menu/src/use-context-menu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { RefCallback } from 'react';
22
import { useContext, useMemo, useRef } from 'react';
3-
import { Context } from './context-menu-provider';
3+
import { ContextMenuContext } from './context-menu-provider';
44
import { appendContextMenuContent } from './context-menu-content';
55
import type { ContextMenuItem } from './types';
66

@@ -19,7 +19,7 @@ export type ContextMenuMethods<T extends ContextMenuItem> = {
1919
export function useContextMenu<
2020
T extends ContextMenuItem = ContextMenuItem
2121
>(): ContextMenuMethods<T> {
22-
const context = useContext(Context);
22+
const context = useContext(ContextMenuContext);
2323
const previous = useRef<null | [HTMLElement, (event: MouseEvent) => void]>(
2424
null
2525
);

packages/compass-crud/src/components/table-view/cell-renderer.tsx

Lines changed: 18 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@ import {
1010
css,
1111
Icon,
1212
IconButton,
13-
LeafyGreenProvider,
1413
spacing,
1514
withDarkMode,
16-
useContextMenuItems,
15+
useFieldContextMenu,
16+
LeafyGreenProvider,
1717
} from '@mongodb-js/compass-components';
1818
import { type Document, Element } from 'hadron-document';
19-
import { objectToIdiomaticEJSON } from 'hadron-document';
2019
import type { ICellRendererParams } from 'ag-grid-community';
2120
import type { GridActions, TableHeaderType } from '../../stores/grid-store';
2221
import type { CrudActions } from '../../stores/crud-store';
@@ -94,6 +93,7 @@ const decrypdedIconStyles = css({
9493

9594
interface CellContentProps {
9695
element: Element | undefined | null;
96+
fieldName: string;
9797
cellState:
9898
| typeof UNEDITABLE
9999
| typeof EMPTY
@@ -111,13 +111,20 @@ const CellContent: React.FC<CellContentProps> = ({
111111
cellState,
112112
onUndo,
113113
onExpand,
114+
fieldName,
114115
}) => {
115116
const [, forceUpdate] = useReducer((x: number) => x + 1, 0);
116117
const isEmpty = element === undefined || element === null;
117118
const handleElementEvent = useCallback(() => {
118119
forceUpdate();
119120
}, []);
120121

122+
// Context menu functionality
123+
const cellContextMenuRef = useFieldContextMenu({
124+
element,
125+
fieldName,
126+
});
127+
121128
// Subscribe to element events
122129
useEffect(() => {
123130
if (!isEmpty && element) {
@@ -148,7 +155,7 @@ const CellContent: React.FC<CellContentProps> = ({
148155
}
149156
}, [element]);
150157

151-
const renderContent = useCallback(() => {
158+
const renderContent = useMemo(() => {
152159
if (cellState === EMPTY || !element) {
153160
return 'No field';
154161
}
@@ -192,7 +199,7 @@ const CellContent: React.FC<CellContentProps> = ({
192199

193200
return (
194201
<div className={className}>
195-
<div className={cellContainerStyle}>
202+
<div ref={cellContextMenuRef} className={cellContainerStyle}>
196203
{element.decrypted && (
197204
<span
198205
data-testid="hadron-document-element-decrypted-icon"
@@ -206,7 +213,7 @@ const CellContent: React.FC<CellContentProps> = ({
206213
</div>
207214
</div>
208215
);
209-
}, [element, elementLength, cellState]);
216+
}, [element, elementLength, cellState, cellContextMenuRef]);
210217

211218
const canUndo =
212219
cellState === ADDED ||
@@ -222,7 +229,7 @@ const CellContent: React.FC<CellContentProps> = ({
222229
<>
223230
{canUndo && <CellUndoButton alignLeft={canExpand} onClick={onUndo} />}
224231
{canExpand && <CellExpandButton onClick={onExpand} />}
225-
{renderContent()}
232+
{renderContent}
226233
</>
227234
);
228235
};
@@ -256,75 +263,8 @@ const CellRenderer: React.FC<CellRendererProps> = ({
256263
}) => {
257264
const element = value as Element | undefined | null;
258265

259-
const isEmpty = element === undefined || element === null;
260266
const [isDeleted, setIsDeleted] = useState(false);
261267

262-
// Helper function to check if a string is a URL
263-
const isValidUrl = useCallback((str: string): boolean => {
264-
try {
265-
const url = new URL(str);
266-
return url.protocol === 'http:' || url.protocol === 'https:';
267-
} catch {
268-
return false;
269-
}
270-
}, []);
271-
272-
// Add context menu functionality
273-
const contextMenuRef = useContextMenuItems([
274-
...(element && !isEmpty
275-
? [
276-
{
277-
label: 'Copy field & value',
278-
onAction: () => {
279-
const fieldName = column.getColId();
280-
const fieldStr = `${fieldName}: ${objectToIdiomaticEJSON(
281-
element.currentValue
282-
)}`;
283-
void navigator.clipboard.writeText(fieldStr);
284-
},
285-
},
286-
]
287-
: []),
288-
...(element &&
289-
element.currentType === 'String' &&
290-
isValidUrl(element.currentValue)
291-
? [
292-
{
293-
label: 'Open URL in browser',
294-
onAction: () => {
295-
window.open(element.currentValue, '_blank', 'noopener');
296-
},
297-
},
298-
]
299-
: []),
300-
...(element &&
301-
(element.currentType === 'Object' || element.currentType === 'Array')
302-
? [
303-
{
304-
label: 'Expand field',
305-
onAction: () => {
306-
handleDrillDown({
307-
stopPropagation: () => {},
308-
} as React.MouseEvent);
309-
},
310-
},
311-
]
312-
: []),
313-
...(cellState === ADDED ||
314-
cellState === EDITED ||
315-
cellState === INVALID ||
316-
cellState === DELETED
317-
? [
318-
{
319-
label: 'Undo changes',
320-
onAction: () => {
321-
handleUndo({ stopPropagation: () => {} } as React.MouseEvent);
322-
},
323-
},
324-
]
325-
: []),
326-
]);
327-
328268
const isEditable = useMemo(() => {
329269
/* Can't get the editable() function from here, so have to reevaluate */
330270
let editable = true;
@@ -357,7 +297,7 @@ const CellRenderer: React.FC<CellRendererProps> = ({
357297

358298
if (!isEditable) {
359299
cellState = UNEDITABLE;
360-
} else if (isEmpty || isDeleted) {
300+
} else if (!element || isDeleted) {
361301
cellState = EMPTY;
362302
} else if (!element.isCurrentTypeValid()) {
363303
cellState = INVALID;
@@ -393,10 +333,10 @@ const CellRenderer: React.FC<CellRendererProps> = ({
393333
[
394334
element,
395335
node.data.hadronDocument,
336+
cellState,
396337
elementRemoved,
397338
elementAdded,
398339
elementTypeChanged,
399-
cellState,
400340
]
401341
);
402342

@@ -424,7 +364,7 @@ const CellRenderer: React.FC<CellRendererProps> = ({
424364
// `ag-grid` renders this component outside of the context chain
425365
// so we re-supply the dark mode theme here.
426366
<LeafyGreenProvider darkMode={darkMode}>
427-
<div ref={contextMenuRef}>
367+
<div>
428368
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus*/}
429369
<div
430370
className={
@@ -435,6 +375,7 @@ const CellRenderer: React.FC<CellRendererProps> = ({
435375
>
436376
<CellContent
437377
element={element}
378+
fieldName={column.getColId()}
438379
cellState={cellState}
439380
onUndo={handleUndo}
440381
onExpand={handleDrillDown}

0 commit comments

Comments
 (0)