Skip to content

Commit 789afb0

Browse files
committed
add execution queue, code reformat and some cleaning
1 parent e694a6b commit 789afb0

File tree

9 files changed

+89
-79
lines changed

9 files changed

+89
-79
lines changed

src/components/paginationControls.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const PaginationControls: React.FC<PaginationControlsProps> = ({
5252
<div className="mljar-variable-inspector-pagination-container">
5353
<div className="mljar-variable-inspector-pagination-item">
5454
<div className="mljar-variable-inspector-choose-range">
55-
<span>Displaying rows from </span>
55+
<span>Rows from </span>
5656
<button
5757
onClick={e => handlePrevRowPage('first')}
5858
className="mljar-variable-inspector-skip-button"
@@ -114,11 +114,11 @@ export const PaginationControls: React.FC<PaginationControlsProps> = ({
114114
<skipRightIcon.react className="mljar-variable-inspector-skip-icon" />
115115
</button>
116116
<span>
117-
from total <span style={{ fontWeight: 600 }}>{rowsCount}</span> rows
117+
Total <span style={{ fontWeight: 600 }}>{rowsCount}</span> rows
118118
</span>
119119
</div>
120120
<div className="mljar-variable-inspector-choose-range">
121-
<span>Displaying columns from </span>
121+
<span>Columns from </span>
122122
<button
123123
onClick={e => handlePrevColumnPage('first')}
124124
className="mljar-variable-inspector-skip-button"
@@ -180,8 +180,7 @@ export const PaginationControls: React.FC<PaginationControlsProps> = ({
180180
<skipRightIcon.react className="mljar-variable-inspector-skip-icon" />
181181
</button>
182182
<span>
183-
from total <span style={{ fontWeight: 600 }}>{colsCount}</span>{' '}
184-
columns
183+
Total <span style={{ fontWeight: 600 }}>{colsCount}</span> columns
185184
</span>
186185
</div>
187186
{/* Goto Cell section */}

src/components/variableItem.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,14 @@ export const VariableItem: React.FC<VariableItemProps> = ({
5353
const content = result.content;
5454
try {
5555
if (type === 'list') {
56-
setPreview(`[ ${content}...]`);
56+
let listLen = 10;
57+
try {
58+
listLen = parseInt(vrb.shape);
59+
} catch {
60+
/* empty */
61+
} finally {
62+
setPreview(`[${content}${listLen > 10 ? '...' : ''}]`);
63+
}
5764
}
5865
if (type === 'dict') {
5966
const jsonStr = JSON.stringify(content);

src/components/variableList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export const VariableList: React.FC<VariableListProps> = ({
7373
</div>
7474
) : variables.length === 0 ? (
7575
<div className="mljar-variable-inspector-message">
76-
No variables available.
76+
Sorry, no variables available.
7777
</div>
7878
) : (
7979
<ul className="mljar-variable-inspector-list">

src/components/variablePanel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { useThemeContext } from '../context/themeContext';
1313
import { transformMatrixData } from '../utils/utils';
1414
import { PaginationControls } from './paginationControls';
1515

16-
interface VariablePanelProps {
16+
interface IVariablePanelProps {
1717
variableName: string;
1818
initVariableType: string;
1919
initVariableShape: string;
@@ -23,7 +23,7 @@ interface VariablePanelProps {
2323
const AutoSizer = RVAutoSizer as unknown as React.ComponentType<any>;
2424
const MultiGrid = RVMultiGrid as unknown as React.ComponentType<any>;
2525

26-
export const VariablePanel: React.FC<VariablePanelProps> = ({
26+
export const VariablePanel: React.FC<IVariablePanelProps> = ({
2727
variableName,
2828
initVariableType,
2929
initVariableShape,
@@ -226,7 +226,7 @@ export const VariablePanel: React.FC<VariablePanelProps> = ({
226226
let maxLength = 0;
227227
for (let row = 0; row < rowCount; row++) {
228228
const cell = data[row][col];
229-
const cellStr = cell != null ? cell.toString() : '';
229+
const cellStr = cell !== null ? cell.toString() : '';
230230
if (cellStr.length > maxLength) {
231231
maxLength = cellStr.length;
232232
}

src/context/codeExecutionContext.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { VARIABLE_INSPECTOR_ID, autoRefreshProperty } from '../index';
1414
import { ISettingRegistry } from '@jupyterlab/settingregistry';
1515
import { variableDict } from '../python_code/getVariables';
1616

17-
interface ICodeExecutionContext { }
17+
interface ICodeExecutionContext {}
1818

1919
interface CodeExecutionContextProviderProps {
2020
children: ReactNode;
@@ -32,8 +32,6 @@ export const CodeExecutionContextProvider: React.FC<
3232
const kernelReady = useNotebookKernelContext();
3333
const { refreshVariables } = useVariableContext();
3434
const getVariableCode = variableDict;
35-
const matrixFunctionHeader =
36-
'__mljar_variable_inspector_get_matrix_content()';
3735
const [autoRefresh, setAutoRefresh] = useState(true);
3836

3937
const loadAutoRefresh = () => {
@@ -74,16 +72,18 @@ export const CodeExecutionContextProvider: React.FC<
7472
if (msg.header.msg_type === 'execute_input') {
7573
const inputMsg = msg as IExecuteInputMsg;
7674
const code = inputMsg.content.code;
75+
const variableInspectorPrefix = '_jupyterlab_variableinspector';
76+
const mljarPrefix = '__mljar';
7777
if (
7878
code !== getVariableCode &&
79-
!code.includes(matrixFunctionHeader) &&
79+
!code.includes(variableInspectorPrefix) &&
80+
!code.includes(mljarPrefix) &&
8081
autoRefresh
8182
) {
8283
refreshVariables();
8384
}
8485
}
8586
};
86-
8787
kernel.iopubMessage.connect(handleIOPubMessage);
8888

8989
return () => {

src/context/notebookVariableContext.tsx

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { KernelMessage } from '@jupyterlab/services';
1111
import { withIgnoredSidebarKernelUpdates } from '../utils/kernelOperationNotifier';
1212
import { variableDict } from '../python_code/getVariables';
1313

14-
interface VariableInfo {
14+
interface IVariableInfo {
1515
name: string;
1616
type: string;
1717
shape: string;
@@ -20,8 +20,8 @@ interface VariableInfo {
2020
value: string;
2121
}
2222

23-
interface VariableContextProps {
24-
variables: VariableInfo[];
23+
interface IVariableContextProps {
24+
variables: IVariableInfo[];
2525
loading: boolean;
2626
error: string | null;
2727
searchTerm: string;
@@ -31,34 +31,82 @@ interface VariableContextProps {
3131
refreshCount: number;
3232
}
3333

34-
const VariableContext = createContext<VariableContextProps | undefined>(
34+
const VariableContext = createContext<IVariableContextProps | undefined>(
3535
undefined
3636
);
3737

38+
type Task = () => Promise<void> | void;
39+
40+
class DebouncedTaskQueue {
41+
// Holds the timer handle.
42+
private timer: ReturnType<typeof setTimeout> | null = null;
43+
// Holds the most recently added task.
44+
private lastTask: Task | null = null;
45+
private delay: number;
46+
47+
/**
48+
* @param delay Time in milliseconds to wait before executing the last task.
49+
*/
50+
constructor(delay: number = 500) {
51+
this.delay = delay;
52+
}
53+
54+
/**
55+
* Adds a new task to the queue. Only the last task added within the delay period will be executed.
56+
* @param task A function representing the task.
57+
*/
58+
add(task: Task): void {
59+
// Save (or overwrite) the latest task.
60+
this.lastTask = task;
61+
62+
// If there’s already a pending timer, clear it.
63+
if (this.timer) {
64+
clearTimeout(this.timer);
65+
}
66+
67+
// Start (or restart) the timer.
68+
this.timer = setTimeout(async () => {
69+
if (this.lastTask) {
70+
try {
71+
// Execute the latest task.
72+
await this.lastTask();
73+
} catch (error) {
74+
console.error('Task execution failed:', error);
75+
}
76+
}
77+
// After execution, clear the stored task and timer.
78+
this.lastTask = null;
79+
this.timer = null;
80+
}, this.delay);
81+
}
82+
}
83+
3884
export const VariableContextProvider: React.FC<{
3985
children: React.ReactNode;
4086
}> = ({ children }) => {
4187
const notebookPanel = useNotebookPanelContext();
4288
const kernel = useNotebookKernelContext();
43-
const [variables, setVariables] = useState<VariableInfo[]>([]);
89+
const [variables, setVariables] = useState<IVariableInfo[]>([]);
4490
const [loading, setLoading] = useState<boolean>(false);
4591
const [error, setError] = useState<string | null>(null);
4692
const [searchTerm, setSearchTerm] = useState<string>('');
4793
const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
4894
const [refreshCount, setRefreshCount] = useState<number>(0);
95+
const queue = new DebouncedTaskQueue(250);
4996

5097
const executeCode = useCallback(async () => {
5198
await withIgnoredSidebarKernelUpdates(async () => {
52-
setIsRefreshing(true);
53-
setLoading(true);
99+
//setIsRefreshing(true);
100+
//setLoading(true);
54101
setError(null);
55102

56103
if (!notebookPanel) {
104+
setVariables([]);
57105
setLoading(false);
58106
setIsRefreshing(false);
59107
return;
60108
}
61-
setVariables([]);
109+
//setVariables([]);
62110

63111
try {
64112
const future =
@@ -86,10 +134,10 @@ export const VariableContextProvider: React.FC<{
86134
try {
87135
const cleanedData = textData.replace(/^['"]|['"]$/g, '');
88136
const doubleQuotedData = cleanedData.replace(/'/g, '"');
89-
const parsedData: VariableInfo[] =
137+
const parsedData: IVariableInfo[] =
90138
JSON.parse(doubleQuotedData);
91139
if (Array.isArray(parsedData)) {
92-
const mappedVariables: VariableInfo[] = parsedData.map(
140+
const mappedVariables: IVariableInfo[] = parsedData.map(
93141
(item: any) => ({
94142
name: item.varName,
95143
type: item.varType,
@@ -108,6 +156,7 @@ export const VariableContextProvider: React.FC<{
108156
setRefreshCount(prev => prev + 1);
109157
} catch (err) {
110158
setError('Error during export JSON.');
159+
setVariables([]);
111160
setLoading(false);
112161
setIsRefreshing(false);
113162
}
@@ -121,6 +170,7 @@ export const VariableContextProvider: React.FC<{
121170
setIsRefreshing(false);
122171
}
123172
});
173+
return;
124174
}, [notebookPanel, kernel]);
125175

126176
useEffect(() => {
@@ -135,7 +185,7 @@ export const VariableContextProvider: React.FC<{
135185
error,
136186
searchTerm,
137187
setSearchTerm,
138-
refreshVariables: executeCode,
188+
refreshVariables: () => queue.add(() => executeCode()),
139189
isRefreshing,
140190
refreshCount
141191
}}
@@ -145,7 +195,7 @@ export const VariableContextProvider: React.FC<{
145195
);
146196
};
147197

148-
export const useVariableContext = (): VariableContextProps => {
198+
export const useVariableContext = (): IVariableContextProps => {
149199
const context = useContext(VariableContext);
150200
if (context === undefined) {
151201
throw new Error(

src/utils/executeGetMatrix.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const executeMatrixContent = async (
4747
outputData += content.data['text/plain'];
4848
}
4949
} else if (msgType === 'stream') {
50+
/* empty */
5051
} else if (msgType === 'error') {
5152
console.error('Python error:', msg.content);
5253
reject(new Error('Error during Python execution.'));

src/watchers/notebookWatcher.ts

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,6 @@ function getNotebook(widget: Widget | null): Notebook | null {
1818
return content;
1919
}
2020

21-
export type NotebookSelection = {
22-
start: { line: number; column: number };
23-
end: { line: number; column: number };
24-
text: string;
25-
numLines: number;
26-
widgetId: string;
27-
cellId?: string;
28-
};
29-
30-
export type NotebookSelections = NotebookSelection[];
31-
32-
export function getNotebookSelections(notebook: Notebook): NotebookSelections {
33-
const selections: NotebookSelections = [];
34-
35-
const cellModels = notebook.model?.cells;
36-
37-
if (cellModels) {
38-
for (let i = 0; i < cellModels.length; i++) {
39-
const cell = cellModels.get(i);
40-
const cellSource = cell?.sharedModel.getSource();
41-
const cellId = cell?.id;
42-
43-
if (cellSource && cellId) {
44-
const numLines = cellSource.split('\n').length;
45-
46-
const selection: NotebookSelection = {
47-
start: { line: 0, column: 0 },
48-
end: { line: numLines - 1, column: cellSource.length },
49-
text: cellSource,
50-
numLines,
51-
widgetId: notebook.id,
52-
cellId
53-
};
54-
55-
selections.push(selection);
56-
}
57-
}
58-
}
59-
60-
return selections;
61-
}
62-
6321
export class NotebookWatcher {
6422
constructor(shell: JupyterFrontEnd.IShell) {
6523
this._shell = shell;
@@ -71,14 +29,6 @@ export class NotebookWatcher {
7129
});
7230
}
7331

74-
get selection(): NotebookSelections {
75-
return this._selections;
76-
}
77-
78-
get selectionChanged(): Signal<this, NotebookSelections> {
79-
return this._selectionChanged;
80-
}
81-
8232
get notebookPanelChanged(): Signal<this, NotebookPanel | null> {
8333
return this._notebookPanelChanged;
8434
}
@@ -146,8 +96,6 @@ export class NotebookWatcher {
14696
protected _kernelChanged = new Signal<this, KernelInfo | null>(this);
14797
protected _shell: JupyterFrontEnd.IShell;
14898
protected _mainAreaWidget: Widget | null = null;
149-
protected _selections: NotebookSelections = [];
150-
protected _selectionChanged = new Signal<this, NotebookSelections>(this);
15199
protected _notebookPanel: NotebookPanel | null = null;
152100
protected _notebookPanelChanged = new Signal<this, NotebookPanel | null>(
153101
this

style/base.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,3 +464,8 @@
464464
text-decoration: underline;
465465
background-color: #d3d3d3;
466466
}
467+
468+
.mljar-variable-inspector-variable-preview,
469+
.mljar-variable-inspector-variable-value {
470+
padding-left: 7px;
471+
}

0 commit comments

Comments
 (0)