Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect cells across states #88

Merged
merged 2 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"d3-selection": "^3.0.0",
"html-react-parser": "^4.0.0",
"monaco-editor": "^0.41.0",
"react-xarrows": "^2.0.2",
"tabletojson": "^4.0.1",
"zustand": "^4.3.8"
},
Expand Down
75 changes: 37 additions & 38 deletions src/Overview/Cells/CodeCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import { CellUsers } from './CellUsers';
import { CompareBadge } from './CompareBadge';
import { User } from '@jupyterlab/services';
import parse from 'html-react-parser';

Check warning on line 9 in src/Overview/Cells/CodeCell.tsx

View workflow job for this annotation

GitHub Actions / build

'parse' is defined but never used
import { createStyles } from '@mantine/styles';
import { has } from 'immer/dist/internal';

Check warning on line 11 in src/Overview/Cells/CodeCell.tsx

View workflow job for this annotation

GitHub Actions / build

'has' is defined but never used
import { createUnifedDiff, hasImage } from '../../Detail/ImgDetailDiff';
import { createSummaryVisualizationFromHTML, hasDataframe } from '../../Detail/DataDiff';
import { isCode } from '@jupyterlab/nbformat';
Expand Down Expand Up @@ -87,7 +87,7 @@

// TODO as effect?
// for code, show input (source code) and output (rendered output) next to each other
const { input, inputChanged } = getInput(cell, previousCell, isActiveCell, fullWidth, cx, classes);

Check warning on line 90 in src/Overview/Cells/CodeCell.tsx

View workflow job for this annotation

GitHub Actions / build

'inputChanged' is assigned a value but never used

useEffect(() => {
const diffHtml = async () => {
Expand Down Expand Up @@ -123,45 +123,44 @@

// create a cell with input and output
return (
<>
<div
data-cell-id={cellId}
onClick={setActiveCell}
onDoubleClick={toggleFullwidth}
className={cx(
'jp-Cell',
{ ['active']: isActiveCell },
{ ['added']: added },
{ ['executed']: executions > 0 },
{ ['changed']: !unchanged && !added }
)}
>
<TypeIcon type={type} executions={executions} />
{multiUser && fullWidth ? <CellUsers cellUsers={cellExecutions.get(cellId)?.user ?? []} /> : <></>}
<ExecutionBadge executions={executions} />
{
// Add CompareBadge if old, oldStateNo, and oldTimestamp are defined
previousCell && previousStateNo && previousStateTimestamp && (
<CompareBadge
old={previousCell}
oldStateNo={previousStateNo}
oldTimestamp={previousStateTimestamp}
current={cell}
currentStateNo={stateNo}
currentTimestamp={timestamp}
/>
)
}
<div
id={`${stateNo}-${cellId}`}
data-cell-id={cellId}
onClick={setActiveCell}
onDoubleClick={toggleFullwidth}
className={cx(
'jp-Cell',
{ ['active']: isActiveCell },
{ ['added']: added },
{ ['executed']: executions > 0 },
{ ['changed']: !unchanged && !added }
)}
>
<TypeIcon type={type} executions={executions} />
{multiUser && fullWidth ? <CellUsers cellUsers={cellExecutions.get(cellId)?.user ?? []} /> : <></>}
<ExecutionBadge executions={executions} />
{
// Add CompareBadge if old, oldStateNo, and oldTimestamp are defined
previousCell && previousStateNo && previousStateTimestamp && (
<CompareBadge
old={previousCell}
oldStateNo={previousStateNo}
oldTimestamp={previousStateTimestamp}
current={cell}
currentStateNo={stateNo}
currentTimestamp={timestamp}
/>
)
}

{input}
{split}
{fullWidth && (outputChanged || isActiveCell) ? (
detailDiffOutput
) : (
<div className={cx('unchanged', 'transparent', 'output')}></div>
)}
</div>
</>
{input}
{split}
{fullWidth && (outputChanged || isActiveCell) ? (
detailDiffOutput
) : (
<div className={cx('unchanged', 'transparent', 'output')}></div>
)}
</div>
);
}

Expand Down
9 changes: 7 additions & 2 deletions src/Overview/Cells/DeletedCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import React from 'react';
export interface IDeletedCellProps {
cellId: string;
isActiveCell: boolean;
stateNo: number;
}

export function DeletedCell({ cellId, isActiveCell }: IDeletedCellProps): JSX.Element {
export function DeletedCell({ cellId, isActiveCell, stateNo }: IDeletedCellProps): JSX.Element {
// console.log('render deleted cell');
return (
<div data-cell-id={cellId} className={`jp-Cell deleted ${isActiveCell === true ? 'active' : ''}`}>
<div
id={`${stateNo}-${cellId}`}
data-cell-id={cellId}
className={`jp-Cell deleted ${isActiveCell === true ? 'active' : ''}`}
>
<div style={{ height: '12.8px' }}></div>
</div>
);
Expand Down
76 changes: 37 additions & 39 deletions src/Overview/Cells/MarkDownCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,44 +83,8 @@ export function MarkdownCell({

if (fullWidth) {
return (
<>
<div
data-cell-id={cellId}
onClick={setActiveCell}
onDoubleClick={toggleFullwidth}
className={cx(
'jp-Cell',
{ ['active']: isActiveCell === true },
{ ['added']: added },
{ ['executed']: executions > 0 },
{ ['changed']: changed }
)}
>
<TypeIcon type={'markdown'} executions={executions} />
{multiUser ? <CellUsers cellUsers={cellExecutions.get(cellId)?.user ?? []} /> : <></>}
<ExecutionBadge executions={executions} />
{
// Add CompareBadge if old, oldStateNo, and oldTimestamp are defined
previousCell && previousStateNo && previousStateTimestamp && (
<CompareBadge
old={previousCell}
oldStateNo={previousStateNo}
oldTimestamp={previousStateTimestamp}
current={cell}
currentStateNo={stateNo}
currentTimestamp={timestamp}
/>
)
}
{detailDiffContent}
</div>
</>
);
}
//else: compact
return (
<>
<div
id={`${stateNo}-${cellId}`}
data-cell-id={cellId}
onClick={setActiveCell}
onDoubleClick={toggleFullwidth}
Expand All @@ -133,9 +97,43 @@ export function MarkdownCell({
)}
>
<TypeIcon type={'markdown'} executions={executions} />
{multiUser ? <CellUsers cellUsers={cellExecutions.get(cellId)?.user ?? []} /> : <></>}
<ExecutionBadge executions={executions} />
<div className={cx(classes.tinyHeight)}></div>
{
// Add CompareBadge if old, oldStateNo, and oldTimestamp are defined
previousCell && previousStateNo && previousStateTimestamp && (
<CompareBadge
old={previousCell}
oldStateNo={previousStateNo}
oldTimestamp={previousStateTimestamp}
current={cell}
currentStateNo={stateNo}
currentTimestamp={timestamp}
/>
)
}
{detailDiffContent}
</div>
</>
);
}
//else: compact
return (
<div
id={`${stateNo}-${cellId}`}
data-cell-id={cellId}
onClick={setActiveCell}
onDoubleClick={toggleFullwidth}
className={cx(
'jp-Cell',
{ ['active']: isActiveCell === true },
{ ['added']: added },
{ ['executed']: executions > 0 },
{ ['changed']: changed }
)}
>
<TypeIcon type={'markdown'} executions={executions} />
<ExecutionBadge executions={executions} />
<div className={cx(classes.tinyHeight)}></div>
</div>
);
}
83 changes: 34 additions & 49 deletions src/Overview/State.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ import { getScrollParent, mergeArrays } from '../util';
import { CodeCell } from './Cells/CodeCell';
import { DeletedCell } from './Cells/DeletedCell';
import { MarkdownCell } from './Cells/MarkDownCell';
import { useXarrow } from 'react-xarrows';

const useStyles = createStyles((theme, _params) => ({
header: {
borderBottom: 'var(--jp-border-width) solid var(--jp-toolbar-border-color)'
borderBottom: 'var(--jp-border-width) solid var(--jp-toolbar-border-color)',
zIndex: 1,
backgroundColor: 'white'
},
stateWrapper: {
label: 'wrapper',
Expand Down Expand Up @@ -181,7 +184,9 @@ const useStyles = createStyles((theme, _params) => ({
label: 'version-split',
borderTop: '1px solid var(--jp-toolbar-border-color)',
marginTop: '1em',
textAlign: 'center'
textAlign: 'center',
zIndex: 1, // higher than the xarrow lines
backgroundColor: 'white'
},
dashedBorder: {
// borderLeft: 'var(--jp-border-width) dotted var(--jp-toolbar-border-color)',
Expand All @@ -203,7 +208,6 @@ interface IStateProps {
timestamp: Date;
numStates: number;
nbTracker: INotebookTracker;
handleScroll: (stateNo: number) => void;
multiUser: boolean;
}

Expand All @@ -218,10 +222,10 @@ export function State({
timestamp,
numStates,
nbTracker,
handleScroll,
multiUser
}: IStateProps): JSX.Element {
const { classes, cx } = useStyles();
const updateXarrow = useXarrow();

const [fullWidth, setFullWidth] = useState(stateDoI === 1); // on first render, initialize with stateDoI
useEffect(() => {
Expand Down Expand Up @@ -292,50 +296,35 @@ export function State({
const activeCellTop = useLoopsStore(state => state.activeCellTop);
const stateScrollerRef = useRef<HTMLDivElement>(null);

const scrollToElement = () => {
// provCellTop = distance of the provenance's corresponding cell to the top of the extension
// console.log(`state ${stateNo} scroll to active cell ID with top position`, activeCellId, activeCellTop);
const provCellTop = stateScrollerRef.current?.querySelector<HTMLDivElement>(
`[data-cell-id="${activeCellId}"]`
)?.offsetTop;

const versionSplit = 35;
// activeCellTop and provCellTop are calculated relative to different elements, align them by adding the height of the top panel
const jpTopPanelHeight = document.querySelector<HTMLDivElement>('#jp-top-panel')?.offsetHeight || 0;
// the notebook cells have some padding at the top that needs to be considered in order to align the cells properly
const jpCellPadding =
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--jp-cell-padding')) || 0;

if (activeCellTop && provCellTop) {
// console.log('scroll to element', activeCellTop, provCellTop, jpTopPanelHeight, jpCellPadding);
const scrollPos = provCellTop - activeCellTop + jpTopPanelHeight - jpCellPadding + versionSplit;
// console.log('scrollpos', scrollPos);
stateScrollerRef.current?.scrollTo({ top: scrollPos, behavior: 'instant' });
}
};

useEffect(
() => {
const scrollToElement = () => {
// provCellTop = distance of the provenance's corresponding cell to the top of the extension
// console.log(`state ${stateNo} scroll to active cell ID with top position`, activeCellId, activeCellTop);
const provCellTop = stateScrollerRef.current?.querySelector<HTMLDivElement>(
`[data-cell-id="${activeCellId}"]`
)?.offsetTop;

const versionSplit = 35;
// activeCellTop and provCellTop are calculated relative to different elements, align them by adding the height of the top panel
const jpTopPanelHeight = document.querySelector<HTMLDivElement>('#jp-top-panel')?.offsetHeight || 0;
// the notebook cells have some padding at the top that needs to be considered in order to align the cells properly
const jpCellPadding =
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--jp-cell-padding')) || 0;

if (activeCellTop && provCellTop) {
// console.log('scroll to element', activeCellTop, provCellTop, jpTopPanelHeight, jpCellPadding);
const scrollPos = provCellTop - activeCellTop + jpTopPanelHeight - jpCellPadding + versionSplit;
// console.log('scrollpos', scrollPos);
stateScrollerRef.current?.scrollTo({ top: scrollPos, behavior: 'instant' });
}
};
scrollToElement();
} //, [activeCellTop] // commented out: dpeend on activeCellTop --> run if the value changes
//currently: no dependency --> run on every render (at the end of the render cycle)
},
[activeCellId, activeCellTop] //depend on activeCellTop --> run if the value changes
////currently: no dependency --> run on every render (at the end of the render cycle)
);

// useEffect(() => {
// const element = stateScrollerRef.current;
// const handleScrollWrapper = () => handleScroll(stateNo);

// if (element !== null) {
// element.addEventListener('scroll', handleScrollWrapper);
// }

// return () => {
// if (element !== null) {
// element.removeEventListener('scroll', handleScrollWrapper);
// }
// };
// }, []); // Empty dependency array means this effect runs once on mount and cleanup on unmount

if (!state) {
return <div>State {stateNo} not found</div>;
}
Expand All @@ -354,7 +343,7 @@ export function State({

if (cell === undefined && previousCell !== undefined) {
// cell was deleted in current state
return <DeletedCell key={cellId} cellId={cellId} isActiveCell={isActiveCell} />;
return <DeletedCell key={cellId} cellId={cellId} isActiveCell={isActiveCell} stateNo={stateNo} />;
} else if (cell === undefined && previousCell === undefined) {
// cell is in none of the states
// weird, but nothing to do
Expand Down Expand Up @@ -478,7 +467,7 @@ export function State({
</ActionIcon>
</Center>
</header>
<div ref={stateScrollerRef} className={classes.stateScroller}>
<div ref={stateScrollerRef} className={classes.stateScroller} onScroll={updateXarrow}>
<div className={cx(classes.state, 'state')}>
<div style={{ height: '100vh' }} className={classes.dashedBorder}></div>
{cells}
Expand Down Expand Up @@ -510,7 +499,3 @@ export function State({
</div>
);
}

interface IScrollableElement extends Element {
onscrollend: ((this: IScrollableElement, ev: Event) => any) | null;
}
Loading
Loading