@@ -13,8 +13,10 @@ import {
1313 LeafyGreenProvider ,
1414 spacing ,
1515 withDarkMode ,
16+ useContextMenuItems ,
1617} from '@mongodb-js/compass-components' ;
1718import { type Document , Element } from 'hadron-document' ;
19+ import { objectToIdiomaticEJSON } from 'hadron-document' ;
1820import type { ICellRendererParams } from 'ag-grid-community' ;
1921import type { GridActions , TableHeaderType } from '../../stores/grid-store' ;
2022import type { CrudActions } from '../../stores/crud-store' ;
@@ -257,6 +259,72 @@ const CellRenderer: React.FC<CellRendererProps> = ({
257259 const isEmpty = element === undefined || element === null ;
258260 const [ isDeleted , setIsDeleted ] = useState ( false ) ;
259261
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+
260328 const isEditable = useMemo ( ( ) => {
261329 /* Can't get the editable() function from here, so have to reevaluate */
262330 let editable = true ;
@@ -328,6 +396,7 @@ const CellRenderer: React.FC<CellRendererProps> = ({
328396 elementRemoved ,
329397 elementAdded ,
330398 elementTypeChanged ,
399+ cellState ,
331400 ]
332401 ) ;
333402
@@ -355,7 +424,7 @@ const CellRenderer: React.FC<CellRendererProps> = ({
355424 // `ag-grid` renders this component outside of the context chain
356425 // so we re-supply the dark mode theme here.
357426 < LeafyGreenProvider darkMode = { darkMode } >
358- < div >
427+ < div ref = { contextMenuRef } >
359428 { /* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus*/ }
360429 < div
361430 className = {
0 commit comments