Skip to content

Commit cfd2f10

Browse files
MarcosVnMarcos Alves
andauthored
feat: add support for multiple Cell state management (#255) (#257)
* feat: partially adding support to multiple Cell management * feat: adding more functionalities to multiple Cell state management #255 * fix: fixing keydown events for individual cells #255 --------- Co-authored-by: Marcos Alves <marcos@aevotech.com.br>
1 parent 40257f1 commit cfd2f10

File tree

5 files changed

+111
-39
lines changed

5 files changed

+111
-39
lines changed

packages/react/src/components/cell/Cell.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ import CellAdapter from './CellAdapter';
1212
import Lumino from '../lumino/Lumino';
1313
import { useJupyter } from './../../jupyter/JupyterContext';
1414
import useCellStore from './CellState';
15+
import { newUuid } from '../../utils';
1516

1617
export type ICellProps = {
18+
/**
19+
* An id that can be provided to identify unique cell
20+
*/
21+
id: string;
1722
/**
1823
* Cell type
1924
*/
2025
type: 'code' | 'markdown' | 'raw';
21-
2226
/**
2327
* Cell source
2428
*/
@@ -36,26 +40,27 @@ export type ICellProps = {
3640
export const Cell = (props: ICellProps) => {
3741
const { type='code', source = '', autoStart, showToolbar=true } = props;
3842
const { serverSettings, defaultKernel } = useJupyter();
39-
const cellStore = useCellStore();
43+
const [id] = useState(props.id || newUuid());
4044
const [adapter, setAdapter] = useState<CellAdapter>();
45+
const cellStore = useCellStore();
4146

4247
const handleCellInitEvents = (adapter: CellAdapter) => {
4348
adapter.cell.model.contentChanged.connect(
4449
(cellModel, changedArgs) => {
45-
cellStore.setSource(cellModel.sharedModel.getSource());
50+
cellStore.setSource(id, cellModel.sharedModel.getSource());
4651
}
4752
);
4853

4954
if (adapter.cell instanceof CodeCell) {
5055
adapter.cell.outputArea.outputLengthChanged?.connect(
5156
(outputArea, outputsCount) => {
52-
cellStore.setOutputsCount(outputsCount);
57+
cellStore.setOutputsCount(id, outputsCount);
5358
}
5459
);
5560
}
5661

5762
adapter.sessionContext.initialize().then(() => {
58-
if (!autoStart) {
63+
if (!autoStart || !adapter.cell.model) {
5964
return
6065
}
6166

@@ -77,7 +82,7 @@ export const Cell = (props: ICellProps) => {
7782
}
7883

7984
useEffect(() => {
80-
if (defaultKernel && serverSettings) {
85+
if (id && defaultKernel && serverSettings) {
8186
defaultKernel.ready.then(() => {
8287
const adapter = new CellAdapter({
8388
type,
@@ -86,8 +91,8 @@ export const Cell = (props: ICellProps) => {
8691
kernel: defaultKernel,
8792
boxOptions: {showToolbar}
8893
});
89-
cellStore.setAdapter(adapter);
90-
cellStore.setSource(source);
94+
cellStore.setAdapter(id, adapter);
95+
cellStore.setSource(id, source);
9196
handleCellInitEvents(adapter);
9297
setAdapter(adapter);
9398

packages/react/src/components/cell/CellAdapter.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,11 @@ export class CellAdapter {
189189
document.addEventListener(
190190
'keydown',
191191
event => {
192-
commands.processKeydownEvent(event);
192+
// Trigger and process event only in current focused cell
193+
const activeElement = document.activeElement;
194+
if (activeElement && activeElement.closest('.dla-Jupyter-Cell') === this._cell.node) {
195+
commands.processKeydownEvent(event);
196+
}
193197
},
194198
useCapture
195199
);

packages/react/src/components/cell/CellState.ts

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,85 @@ import { useStore } from 'zustand';
99
import CellAdapter from './CellAdapter';
1010

1111
export interface ICellState {
12-
source: string;
13-
outputsCount: number;
14-
kernelAvailable: boolean;
12+
source?: string;
13+
outputsCount?: number;
1514
adapter?: CellAdapter;
1615
}
1716

18-
export type CellState = ICellState & {
19-
setSource: (source: string) => void;
20-
setOutputsCount: (outputsCount: number) => void;
17+
export interface ICellsState {
18+
cells: Map<string, ICellState>;
19+
kernelAvailable: boolean; // Currently shared between all cells
20+
}
21+
22+
export type CellState = ICellsState & {
23+
setCells: (cells: Map<string, ICellState>) => void;
24+
setSource: (id: string, source: string) => void;
25+
setOutputsCount: (id: string, outputsCount: number) => void;
2126
setKernelAvailable: (kernelAvailable: boolean) => void;
22-
setAdapter: (adapter?: CellAdapter) => void;
23-
execute: () => void;
27+
setAdapter: (id: string, adapter?: CellAdapter) => void;
28+
getAdapter: (id: string) => CellAdapter | undefined;
29+
getSource: (id: string) => string | undefined;
30+
getOutputsCount: (id: string) => number | undefined;
31+
execute: (id?: string) => void;
2432
};
2533

2634
export const cellStore = createStore<CellState>((set, get) => ({
35+
cells: new Map<string, ICellState>(),
2736
source: '',
2837
outputsCount: 0,
2938
kernelAvailable: false,
3039
adapter: undefined,
31-
setSource: (source: string) => set((state: CellState) => ({ source })),
32-
setOutputsCount: (outputsCount: number) => set((state: CellState) => ({ outputsCount })),
40+
setCells: (cells: Map<string, ICellState>) => set((cell: CellState) => ({ cells })),
41+
42+
setSource: (id: string, source: string) => {
43+
const cells = get().cells;
44+
const cell = cells.get(id);
45+
if (cell) {
46+
cell.source = source;
47+
} else {
48+
cells.set(id, {source});
49+
}
50+
set((cell: CellState) => ({ cells }))
51+
},
52+
setOutputsCount: (id: string, outputsCount: number) => {
53+
const cells = get().cells;
54+
const cell = cells.get(id);
55+
if (cell) {
56+
cell.outputsCount = outputsCount;
57+
} else {
58+
cells.set(id, {outputsCount});
59+
}
60+
set((state: CellState) => ({ cells }))
61+
},
3362
setKernelAvailable: (kernelAvailable: boolean) => set((state: CellState) => ({ kernelAvailable })),
34-
setAdapter: (adapter?: CellAdapter) => set((state: CellState) => ({ adapter })),
35-
execute: () => { get().adapter?.execute() },
63+
setAdapter: (id: string, adapter?: CellAdapter) => {
64+
const cells = get().cells;
65+
const cell = cells.get(id);
66+
if (cell) {
67+
cell.adapter = adapter;
68+
} else {
69+
cells.set(id, { adapter });
70+
}
71+
set((cell: CellState) => ({ cells }))
72+
},
73+
getAdapter: (id: string) => {
74+
return get().cells.get(id)?.adapter;
75+
},
76+
getSource: (id: string): string | undefined => {
77+
return get().cells.get(id)?.source;
78+
},
79+
getOutputsCount: (id: string): number | undefined => {
80+
return get().cells.get(id)?.outputsCount;
81+
},
82+
execute: (id: string) => {
83+
const cells = get().cells;
84+
const cell = cells.get(id);
85+
if (cell) {
86+
cell.adapter?.execute()
87+
} else {
88+
get().cells.forEach((cell) => cell.adapter?.execute())
89+
}
90+
},
3691
}));
3792

3893
export function useCellStore(): CellState;

packages/react/src/examples/All.tsx

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import Terminal from '../components/terminal/Terminal';
1919
import CellSidebarNew from '../components/notebook/cell/sidebar/CellSidebarButton';
2020
import CellSidebar from '../components/notebook/cell/sidebar/CellSidebar';
2121
import Console from '../components/console/Console';
22-
import { cellStore, useCellStore } from '../components/cell/CellState';
22+
import { useCellStore } from '../components/cell/CellState';
2323
import useNotebookStore from '../components/notebook/NotebookState';
2424

2525
import notebook from './notebooks/NotebookExample1.ipynb.json';
@@ -49,39 +49,44 @@ widgets.IntSlider(
4949
step=1
5050
)`;
5151

52-
const CellPreview = () => {
53-
const { source, kernelAvailable } = useCellStore()
52+
interface ICellToolProps {
53+
id: string; // Cell id
54+
}
55+
56+
const CellPreview = (props: ICellToolProps) => {
57+
const cellStore = useCellStore();
5458
return (
5559
<>
56-
<>source: {source}</>
57-
<>kernel available: {String(kernelAvailable)}</>
60+
<>source: {cellStore.getSource(props.id)}</>
61+
<>kernel available: {String(cellStore.kernelAvailable)}</>
5862
</>
5963
);
6064
};
6165

62-
const CellToolbar = () => {
63-
const { outputsCount } = useCellStore();
66+
const CellToolbar = (props: ICellToolProps) => {
67+
const {id} = props;
68+
const cellStore = useCellStore();
6469
return (
6570
<>
6671
<Box display="flex">
6772
<ButtonGroup>
6873
<Button
6974
variant="default"
7075
size="small"
71-
onClick={() => cellStore.getState().execute()}
76+
onClick={() => cellStore.execute(id)}
7277
>
7378
Run the cell
7479
</Button>
7580
<Button
7681
variant="invisible"
7782
size="small"
78-
onClick={() => cellStore.getState().setOutputsCount(0)}
83+
onClick={() => cellStore.setOutputsCount(id, 0)}
7984
>
8085
Reset outputs count
8186
</Button>
8287
</ButtonGroup>
8388
</Box>
84-
<Box>Outputs count: {outputsCount}</Box>
89+
<Box>Outputs count: {cellStore.getOutputsCount(id)}</Box>
8590
</>
8691
);
8792
};
@@ -184,6 +189,7 @@ const Outputs = () => {
184189
const div = document.createElement('div');
185190
document.body.appendChild(div);
186191
const root = createRoot(div);
192+
const cellId = 'my-cell-1'
187193

188194
root.render(
189195
<Jupyter terminals={true}>
@@ -198,9 +204,9 @@ root.render(
198204
<hr />
199205
<Console />
200206
<hr />
201-
<CellPreview />
202-
<CellToolbar />
203-
<Cell />
207+
<CellPreview id={cellId} />
208+
<CellToolbar id={cellId}/>
209+
<Cell id={cellId}/>
204210
<hr />
205211
<Outputs />
206212
<hr />

packages/react/src/examples/Cell.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,22 @@ for i in range(100):
2222

2323
const CellExample = () => {
2424
const cellStore = useJupyterStore().cellStore();
25-
console.log('Cell Outputs', (cellStore.adapter?.cell as CodeCell).outputArea.model.toJSON());
25+
const cellId = 'cell-1'
26+
27+
console.log('Cell Outputs', (cellStore.getAdapter(cellId)?.cell as CodeCell).outputArea.model.toJSON());
2628
return (
2729
<Jupyter>
2830
<Box as="h1">A Jupyter Cell</Box>
2931
<Box>
30-
Outputs Count: {cellStore.outputsCount}
32+
Outputs Count: {cellStore.getOutputsCount(cellId)}
3133
</Box>
3234
<Box>
33-
Source: {cellStore.source}
35+
Source: {cellStore.getSource(cellId)}
3436
</Box>
3537
<Box>
36-
<Button onClick={() => cellStore.execute()}>Run cell</Button>
38+
<Button onClick={() => cellStore.execute(cellId)}>Run cell</Button>
3739
</Box>
38-
<Cell source={DEFAULT_SOURCE}/>
40+
<Cell source={DEFAULT_SOURCE} id={cellId}/>
3941
</Jupyter>
4042
)
4143
}

0 commit comments

Comments
 (0)