Skip to content

Commit 6f6d790

Browse files
authored
Merge pull request #100 from jupytercalpoly/dev
Metadata dashboards
2 parents cdd0cc7 + 8712188 commit 6f6d790

File tree

11 files changed

+348
-119
lines changed

11 files changed

+348
-119
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module.exports = {
2929
{ avoidEscape: true, allowTemplateLiterals: false }
3030
],
3131
curly: ['error', 'all'],
32-
eqeqeq: 'error',
32+
eqeqeq: ['error', 'smart'],
3333
'prefer-arrow-callback': 'error'
3434
}
3535
};

src/commands.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@ export namespace CommandIDs {
1919
export const startFullscreen = 'dashboard:start-fullscreen';
2020
export const createNew = 'dashboard:create-new';
2121
export const setTileSize = 'dashboard:set-tile-size';
22+
export const saveToMetadata = 'dashboard:save-to-metadata';
23+
export const openFromMetadata = 'dashboard:open-from-metadata';
24+
export const toggleWidgetMode = 'dashboard:toggle-widget-mode';
25+
export const toggleInfiniteScroll = 'dashboard:toggle-infinite-scroll';
26+
export const trimDashboard = 'dashboard:trim-dashboard';
2227
}

src/dashboard.tsx

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { NotebookPanel } from '@jupyterlab/notebook';
22

33
import { CodeCell, MarkdownCell, Cell } from '@jupyterlab/cells';
44

5+
import { map, toArray, each } from '@lumino/algorithm';
6+
57
import * as React from 'react';
68

79
import {
@@ -10,6 +12,7 @@ import {
1012
IWidgetTracker,
1113
Toolbar,
1214
ReactWidget,
15+
// MainAreaWidget,
1316
} from '@jupyterlab/apputils';
1417

1518
import { CommandRegistry } from '@lumino/commands';
@@ -26,9 +29,15 @@ import { DashboardLayout } from './layout';
2629

2730
import { DashboardWidget } from './widget';
2831

29-
import { Widgetstore } from './widgetstore';
32+
import { WidgetPosition, Widgetstore } from './widgetstore';
3033

31-
import { addCellId, addNotebookId } from './utils';
34+
import {
35+
addCellId,
36+
addNotebookId,
37+
getNotebookById,
38+
getCellId,
39+
updateMetadata,
40+
} from './utils';
3241

3342
import {
3443
DocumentWidget,
@@ -43,17 +52,19 @@ import { CommandIDs } from './commands';
4352

4453
import { HTMLSelect } from '@jupyterlab/ui-components';
4554

55+
import { UUID } from '@lumino/coreutils';
56+
4657
// HTML element classes
4758

48-
const DASHBOARD_CLASS = 'pr-JupyterDashboard';
59+
export const DASHBOARD_CLASS = 'pr-JupyterDashboard';
4960

50-
const DROP_TARGET_CLASS = 'pr-DropTarget';
61+
export const DROP_TARGET_CLASS = 'pr-DropTarget';
5162

52-
const TOOLBAR_MODE_SWITCHER_CLASS = 'pr-ToolbarModeSwitcher';
63+
export const TOOLBAR_MODE_SWITCHER_CLASS = 'pr-ToolbarModeSwitcher';
5364

54-
const TOOLBAR_SELECT_CLASS = 'pr-ToolbarSelector';
65+
export const TOOLBAR_SELECT_CLASS = 'pr-ToolbarSelector';
5566

56-
const TOOLBAR_CLASS = 'pr-DashboardToolbar';
67+
export const TOOLBAR_CLASS = 'pr-DashboardToolbar';
5768

5869
export const IDashboardTracker = new Token<IDashboardTracker>(
5970
'jupyterlab-interactive-dashboard-editor'
@@ -70,9 +81,13 @@ export class Dashboard extends Widget {
7081
constructor(options: Dashboard.IOptions) {
7182
super(options);
7283

73-
const { outputTracker, model, context } = options;
84+
this.id = UUID.uuid4();
85+
86+
const { outputTracker, model } = options;
7487
this._model = model;
75-
this._context = context;
88+
if (options.context !== undefined) {
89+
this._context = options.context;
90+
}
7691
const { widgetstore, mode } = model;
7792

7893
this.layout = new DashboardLayout({
@@ -141,7 +156,7 @@ export class Dashboard extends Widget {
141156
event.dropAction = 'copy';
142157
const source = event.source as DashboardWidget;
143158
const pos = source?.pos;
144-
if (pos) {
159+
if (pos && source.mode === 'grid-edit') {
145160
pos.left = event.offsetX + this.node.scrollLeft;
146161
pos.top = event.offsetY + this.node.scrollTop;
147162
(this.layout as DashboardLayout).drawDropZone(pos, '#2b98f0');
@@ -349,6 +364,43 @@ export class Dashboard extends Widget {
349364
return (this.layout as DashboardLayout).createWidget(info, fit);
350365
}
351366

367+
saveToNotebookMetadata(): void {
368+
// Get a list of all notebookIds used in the dashboard.
369+
const widgets = toArray(this.model.widgetstore.getWidgets());
370+
371+
const notebookIds = toArray(map(widgets, (record) => record.notebookId));
372+
373+
if (!notebookIds.every((v) => v === notebookIds[0])) {
374+
throw new Error(
375+
'Only single notebook dashboards can be saved to metadata.'
376+
);
377+
}
378+
379+
const notebookId = notebookIds[0];
380+
const notebookTracker = this.model.notebookTracker;
381+
const notebook = getNotebookById(notebookId, notebookTracker);
382+
383+
updateMetadata(notebook, { hasDashboard: true });
384+
385+
const cells = notebook.content.widgets;
386+
387+
const widgetMap = new Map<string, WidgetPosition>(
388+
widgets.map((widget) => [widget.cellId, widget.pos])
389+
);
390+
391+
each(cells, (cell) => {
392+
const cellId = getCellId(cell);
393+
const pos = widgetMap.get(cellId);
394+
if (pos != null) {
395+
updateMetadata(cell, { pos, hidden: false });
396+
} else {
397+
updateMetadata(cell, { hidden: true });
398+
}
399+
});
400+
401+
notebook.context.save();
402+
}
403+
352404
get model(): IDashboardModel {
353405
return this._model;
354406
}
@@ -367,7 +419,7 @@ export class Dashboard extends Widget {
367419
* Namespace for DashboardArea options.
368420
*/
369421
export namespace Dashboard {
370-
export type Mode = 'edit' | 'present' | 'grid';
422+
export type Mode = 'free-edit' | 'present' | 'grid-edit';
371423

372424
export type ScrollMode = 'infinite' | 'constrained';
373425

@@ -394,7 +446,7 @@ export namespace Dashboard {
394446

395447
model: IDashboardModel;
396448

397-
context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
449+
context?: DocumentRegistry.IContext<DocumentRegistry.IModel>;
398450
}
399451
}
400452

@@ -403,6 +455,7 @@ export class DashboardDocument extends DocumentWidget<Dashboard> {
403455
let { content, reveal } = options;
404456
const { context, commandRegistry } = options;
405457
const model = context.model as DashboardModel;
458+
model.path = context.path;
406459
content = content || new Dashboard({ ...options, model, context });
407460
reveal = Promise.all([reveal, context.ready]);
408461
super({
@@ -416,7 +469,7 @@ export class DashboardDocument extends DocumentWidget<Dashboard> {
416469
this.toolbar.addClass(TOOLBAR_CLASS);
417470

418471
const commands = commandRegistry;
419-
const { save, undo, redo, cut, copy, paste, runOutput } = CommandIDs;
472+
const { save, undo, redo, cut, copy, paste } = CommandIDs;
420473

421474
const args = { toolbar: true, dashboardId: content.id };
422475

@@ -438,15 +491,13 @@ export class DashboardDocument extends DocumentWidget<Dashboard> {
438491
paste,
439492
'Paste outputs from the clipboard'
440493
);
441-
const runButton = makeToolbarButton(runOutput, 'Run the selected outputs');
442494

443495
this.toolbar.addItem(save, saveButton);
444496
this.toolbar.addItem(undo, undoButton);
445497
this.toolbar.addItem(redo, redoButton);
446498
this.toolbar.addItem(cut, cutButton);
447499
this.toolbar.addItem(copy, copyButton);
448500
this.toolbar.addItem(paste, pasteButton);
449-
this.toolbar.addItem(runOutput, runButton);
450501
this.toolbar.addItem('spacer', Toolbar.createSpacerItem());
451502
this.toolbar.addItem(
452503
'switchMode',
@@ -473,7 +524,7 @@ export namespace DashboardDocument {
473524
name?: string;
474525

475526
/**
476-
* Dashboard canvas width (default is 1280).
527+
* Optional widgetstore to restore from.
477528
*/
478529
store?: Widgetstore;
479530

@@ -523,8 +574,8 @@ export namespace DashboardDocument {
523574
aria-label={'Mode'}
524575
>
525576
<option value="present">Present</option>
526-
<option value="edit">Free Layout</option>
527-
<option value="grid">Tile Layout</option>
577+
{/* <option value="free-edit">Free Layout</option> */}
578+
<option value="grid-edit">Edit</option>
528579
</HTMLSelect>
529580
);
530581
}

src/drag.ts

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ export class Drag implements IDisposable {
137137
this.proposedAction = options.proposedAction || 'copy';
138138
this.supportedActions = options.supportedActions || 'all';
139139
this.source = options.source || null;
140-
this._widgetX = options.widgetX || 0;
141-
this._widgetY = options.widgetY || 0;
140+
this._dragAdjustX = options.dragAdjustX || 0;
141+
this._dragAdjustY = options.dragAdjustY || 0;
142142
}
143143

144144
/**
@@ -228,8 +228,8 @@ export class Drag implements IDisposable {
228228
return this._promise;
229229
}
230230

231-
this._deltaX = this._widgetX - clientX;
232-
this._deltaY = this._widgetY - clientY;
231+
this._dragOffsetX = this._dragAdjustX - clientX;
232+
this._dragOffsetY = this._dragAdjustY - clientY;
233233

234234
// Install the document listeners for the drag object.
235235
this._addListeners();
@@ -295,7 +295,7 @@ export class Drag implements IDisposable {
295295

296296
// Move the drag image to the specified client position. This is
297297
// performed *after* dispatching to prevent unnecessary reflows.
298-
this._moveDragImage(event.clientX, event.clientY);
298+
this.moveDragImage(event.clientX, event.clientY);
299299
}
300300

301301
/**
@@ -416,8 +416,8 @@ export class Drag implements IDisposable {
416416

417417
// Find the current indicated element at the given position.
418418
const currElems = document.elementsFromPoint(
419-
event.clientX + this._deltaX,
420-
event.clientY + this._deltaY
419+
event.clientX + this._dragOffsetX,
420+
event.clientY + this._dragOffsetY
421421
);
422422

423423
let currElem = currElems.find((elem) =>
@@ -470,8 +470,8 @@ export class Drag implements IDisposable {
470470
const style = this.dragImage.style;
471471
style.pointerEvents = 'none';
472472
style.position = 'fixed';
473-
style.top = `${clientY + this._deltaY}px`;
474-
style.left = `${clientX + this._deltaX}px`;
473+
style.top = `${clientY + this._dragOffsetY}px`;
474+
style.left = `${clientX + this._dragOffsetX}px`;
475475
document.body.appendChild(this.dragImage);
476476
}
477477

@@ -480,13 +480,13 @@ export class Drag implements IDisposable {
480480
*
481481
* This is a no-op if there is no drag image element.
482482
*/
483-
private _moveDragImage(clientX: number, clientY: number): void {
483+
protected moveDragImage(clientX: number, clientY: number): void {
484484
if (!this.dragImage) {
485485
return;
486486
}
487487
const style = this.dragImage.style;
488-
style.top = `${clientY + this._deltaY}px`;
489-
style.left = `${clientX + this._deltaX}px`;
488+
style.top = `${clientY + this.dragOffsetY}px`;
489+
style.left = `${clientX + this.dragOffsetX}px`;
490490
}
491491

492492
/**
@@ -607,12 +607,12 @@ export class Drag implements IDisposable {
607607
requestAnimationFrame(this._onScrollFrame);
608608
};
609609

610-
get deltaX(): number {
611-
return this._deltaX;
610+
get dragOffsetX(): number {
611+
return this._dragOffsetX;
612612
}
613613

614-
get deltaY(): number {
615-
return this._deltaY;
614+
get dragOffsetY(): number {
615+
return this._dragOffsetY;
616616
}
617617

618618
private _disposed = false;
@@ -623,10 +623,10 @@ export class Drag implements IDisposable {
623623
private _promise: Promise<DropAction> | null = null;
624624
private _scrollTarget: Private.IScrollTarget | null = null;
625625
private _resolve: ((value: DropAction) => void) | null = null;
626-
private _widgetX: number;
627-
private _widgetY: number;
628-
private _deltaX: number;
629-
private _deltaY: number;
626+
private _dragAdjustX: number;
627+
private _dragAdjustY: number;
628+
private _dragOffsetX: number;
629+
private _dragOffsetY: number;
630630
}
631631

632632
/**
@@ -702,14 +702,14 @@ export namespace Drag {
702702
*
703703
* The default value is 0.
704704
*/
705-
widgetX?: number;
705+
dragAdjustX?: number;
706706

707707
/**
708708
* How many pixels to offset the drag/image in the y direction.
709709
*
710710
* The default value is 0.
711711
*/
712-
widgetY?: number;
712+
dragAdjustY?: number;
713713
}
714714

715715
/**
@@ -842,8 +842,8 @@ namespace Private {
842842
drag: Drag
843843
): IScrollTarget | null {
844844
// Look up the client mouse position.
845-
const x = event.clientX + drag.deltaX;
846-
const y = event.clientY + drag.deltaY;
845+
const x = event.clientX + drag.dragOffsetX;
846+
const y = event.clientY + drag.dragOffsetY;
847847

848848
// Get the element under the mouse.
849849
let element: Element | null = document.elementFromPoint(x, y);
@@ -1199,10 +1199,10 @@ namespace Private {
11991199
true,
12001200
window,
12011201
0,
1202-
event.screenX + drag.deltaX,
1203-
event.screenY + drag.deltaY,
1204-
event.clientX + drag.deltaX,
1205-
event.clientY + drag.deltaY,
1202+
event.screenX + drag.dragOffsetX,
1203+
event.screenY + drag.dragOffsetY,
1204+
event.clientX + drag.dragOffsetX,
1205+
event.clientY + drag.dragOffsetY,
12061206
event.ctrlKey,
12071207
event.altKey,
12081208
event.shiftKey,

0 commit comments

Comments
 (0)