Add inline data explorer to R cells and Quarto documents#12693
Add inline data explorer to R cells and Quarto documents#12693
Conversation
|
E2E Tests 🚀 |
nstrayer
left a comment
There was a problem hiding this comment.
Just a few questions about duplication. Things are working and I really appreciate the scroll fixes!
I'm totally good to defer the de-duplication to a different PR. )Also perhaps it's all premature optimization at this point.)
| const newVerticalOffset = pinToRange( | ||
| context.instance.verticalScrollOffset + deltaY, | ||
| 0, | ||
| context.instance.maximumVerticalScrollOffset | ||
| ); | ||
|
|
||
| // If the scroll position wouldn't actually change, let the event | ||
| // bubble up so parent containers (e.g. Quarto documents) can scroll. | ||
| const horizontalChanged = newHorizontalOffset !== context.instance.horizontalScrollOffset; | ||
| const verticalChanged = newVerticalOffset !== context.instance.verticalScrollOffset; | ||
| if (!horizontalChanged && !verticalChanged) { | ||
| return; | ||
| } | ||
|
|
||
| // Consume the event to prevent scroll chaining to parent containers. | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
|
|
||
| // Record the last wheel event. | ||
| setLastWheelEvent(e.timeStamp); | ||
|
|
||
| // Set the scroll offsets. | ||
| context.instance.setScrollOffsets(newHorizontalOffset, newVerticalOffset); |
There was a problem hiding this comment.
Oh man. Thanks for fixing this.
There was a problem hiding this comment.
Yeah, the scroll trap was too annoying until this was addressed!
| .inline-data-explorer-container { | ||
| display: flex; | ||
| flex-direction: column; | ||
| /* If you change the border width, update BORDER in quartoInlineDataExplorerLayout.ts */ |
There was a problem hiding this comment.
Probably not in this PR but I wonder if we should do something like
export const INLINE_DE_LAYOUT = {
toolbarHeight: 24,
toolbarBorder: 1,
containerBorder: 1,
// ...
} as const;
// ...
const layoutVars = {
'--inline-de-toolbar-height': `${INLINE_DE_LAYOUT.toolbarHeight}px`,
'--inline-de-toolbar-border': `${INLINE_DE_LAYOUT.toolbarBorder}px`,
'--inline-de-container-border': `${INLINE_DE_LAYOUT.containerBorder}px`,
} as React.CSSProperties;In the future to make this harder to break. (Although maybe LLMs handle this totally fine with comments like this.)
There was a problem hiding this comment.
It's nice to have constants in only one place but then you have to move the CSS out of the CSS file! 😭What we really need is some sort of CSS preprocessor we can feed variables to (or --var values)...
| // Exception: when data explorer is present, always keep text/plain. The data | ||
| // explorer is a live component that won't work after the document is closed | ||
| // and reopened; text/plain serves as the cache-safe fallback for that case. |
There was a problem hiding this comment.
This is interesting. In notebooks we just keep everything and then try and find the comm and fallback after we dont find it. I like this approach a lot though.
There was a problem hiding this comment.
I did the fallback thing originally, but it was hard to render nicely -- you'd see all the explorers TRYING to open and then failing.
| (!mime.startsWith('application/vnd.positron.') || | ||
| mime === DATA_EXPLORER_MIME_TYPE)) { |
There was a problem hiding this comment.
I really feel like we should eventually have a nice abstraction for dealing with mime type detection etc.
| const { commId, shape, title, variablePath, documentUri, onFallback, onHeightChange } = props; | ||
| const services = PositronReactServices.services; | ||
| const [state, setState] = useState<QuartoInlineDataExplorerState>({ status: 'loading' }); | ||
| const containerRef = useRef<HTMLDivElement>(null); |
There was a problem hiding this comment.
This is never used, right?
There was a problem hiding this comment.
It is not! Deleted.
| const [maxHeight, setMaxHeight] = useState<number>( | ||
| services.configurationService.getValue<number>( | ||
| POSITRON_QUARTO_INLINE_DATA_EXPLORER_MAX_HEIGHT_KEY | ||
| ) ?? 300 |
There was a problem hiding this comment.
pull 300 to constant as it's used elsewhere?
| function InlineDataExplorerHeader({ title, shape, onOpenInExplorer }: { | ||
| title: string; | ||
| shape: { rows: number; columns: number }; | ||
| onOpenInExplorer?: () => void; | ||
| }) { | ||
| return ( | ||
| <div className='inline-data-explorer-header'> | ||
| <div className='inline-data-explorer-info'> | ||
| <span className='inline-data-explorer-title'>{title}</span> | ||
| <span className='inline-data-explorer-shape'> | ||
| {shape.rows.toLocaleString()} {localize('rows', 'rows')} x {shape.columns.toLocaleString()} {localize('columns', 'columns')} | ||
| </span> | ||
| </div> | ||
| {onOpenInExplorer && ( | ||
| <button | ||
| className='inline-data-explorer-open-button' | ||
| title={localize('openInDataExplorer', 'Open in Data Explorer')} | ||
| onClick={onOpenInExplorer} | ||
| > | ||
| <span className='codicon codicon-go-to-file' /> | ||
| {localize('openInDataExplorer', 'Open in Data Explorer')} | ||
| </button> | ||
| )} | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Should we just export InlineDataExplorerHeader from the notebook version? The function is identical.
| export function QuartoInlineDataExplorer(props: QuartoInlineDataExplorerProps) { | ||
| const { commId, shape, title, variablePath, documentUri, onFallback, onHeightChange } = props; |
There was a problem hiding this comment.
Curious about your opinion about extracting the shared logic to a common InlineDataExplorer component with an interface something like:
export interface InlineDataExplorerProps {
commId: string;
shape: { rows: number; columns: number };
title: string;
variablePath?: string[];
documentUri: URI;
onFallback?: () => void;
onHeightChange?: (height: number) => void;
className?: string; // For Quarto's font override class
}Then quarto and notebooks have simple components around it with some hooks to fill in the differences.
There was a problem hiding this comment.
A good idea to follow up on! I didn't want to run the risk of refactoring the existing notebook code as I'm not as familiar with it. Maybe worth doing next time we're in there.
| /** | ||
| * Configuration key for the maximum height of the inline data explorer. | ||
| */ | ||
| export const POSITRON_QUARTO_INLINE_DATA_EXPLORER_MAX_HEIGHT_KEY = 'positron.quarto.inlineDataExplorer.maxHeight'; |
There was a problem hiding this comment.
would it be reasonable to share this between quarto and notebooks?
There was a problem hiding this comment.
After some research I think we should just use the existing notebook setting for this! I've removed the Quarto specific one.
This change adds support for inline data explorers to R cells in Jupyter notebooks and for both R and Python in Quarto documents with inline output. It extends the work @nstrayer did to support Pandas data frames in Python notebooks.
Requires posit-dev/qa-example-content#117
Requires posit-dev/ark#1124
Addresses #12148
Release Notes
New Features
Bug Fixes
QA Notes
E2E tests for both Python and R are included.
This feature can be disabled using this (existing) setting:
Test tags:
@:quarto