From 9e788de298bb86d9b692389542f602bbbbcb0f14 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Thu, 6 Jul 2023 22:06:32 +0200 Subject: [PATCH 1/3] Add a layer to a viewer with drag and drop --- glue_jupyterlab/glue_session.py | 8 ++++++++ src/commands.ts | 2 ++ src/leftPanel/plugin.ts | 17 ++++++++++++++++ src/viewPanel/gridStackItem.ts | 36 ++++++++++++++++++++++++++++++--- src/viewPanel/sessionWidget.ts | 8 +++++++- src/viewPanel/tabLayout.ts | 21 +++++++++++++++++++ src/viewPanel/tabView.ts | 3 ++- 7 files changed, 90 insertions(+), 5 deletions(-) diff --git a/glue_jupyterlab/glue_session.py b/glue_jupyterlab/glue_session.py index 0fa6aa0..22f76a7 100644 --- a/glue_jupyterlab/glue_session.py +++ b/glue_jupyterlab/glue_session.py @@ -349,6 +349,14 @@ def _init_ydoc(self) -> None: ydoc=self._sessionYDoc, ) + def add_viewer_layer(self, tab_name: str, viewer_name: str, data_name:str) -> None: + """Add a layer with new dataset to a viewer""" + viewer = self._viewers.get(tab_name, {}).get(viewer_name, {}).get("widget", None) + if not viewer: + return + data = self._data[data_name] + viewer.add_data(data) + def add_data(self, file_path: str) -> None: """Add a new data file to the session""" relative_path = Path(file_path).relative_to(Path(self._path).parent) diff --git a/src/commands.ts b/src/commands.ts index a4ec2a8..266db11 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -19,6 +19,8 @@ export namespace CommandIDs { export const openControlPanel = 'glue-control:open-control-panel'; export const closeControlPanel = 'glue-control:close-control-panel'; + + export const addViewerLayer = 'glue-control:add-viewer-layer'; } export interface INewViewerArgs { diff --git a/src/leftPanel/plugin.ts b/src/leftPanel/plugin.ts index 7904321..d6fd6b1 100644 --- a/src/leftPanel/plugin.ts +++ b/src/leftPanel/plugin.ts @@ -229,6 +229,23 @@ function addCommands( controlModel.clearConfig(); } }); + + commands.addCommand(CommandIDs.addViewerLayer, { + execute: async args => { + const kernel = controlModel.currentSessionKernel(); + if (kernel === undefined) { + // TODO Show an error dialog + return; + } + + const code = ` + GLUE_SESSION.add_viewer_layer("${args.tab}", "${args.viewer}", "${args.data}") + `; + + const future = kernel.requestExecute({ code }, false); + await future.done; + } + }); } export const controlPanel: JupyterFrontEndPlugin = { diff --git a/src/viewPanel/gridStackItem.ts b/src/viewPanel/gridStackItem.ts index 208c8d5..878103f 100644 --- a/src/viewPanel/gridStackItem.ts +++ b/src/viewPanel/gridStackItem.ts @@ -1,6 +1,8 @@ import { Panel, Widget } from '@lumino/widgets'; import { Toolbar, ToolbarButton, closeIcon } from '@jupyterlab/ui-components'; +import { Message } from '@lumino/messaging'; import { ISignal, Signal } from '@lumino/signaling'; +import { DATASET_MIME } from '../types'; export class GridStackItem extends Panel { constructor(options: GridStackItem.IOptions) { @@ -10,12 +12,13 @@ export class GridStackItem extends Panel { this.addClass('grid-stack-item'); this.addClass('glue-item'); - const { cellIdentity, cell, itemTitle = '', pos, size } = options; + const { cellIdentity, cell, itemTitle = '', pos, size, tabName } = options; this._cellOutput = cell; this.cellIdentity = cellIdentity; this._pos = pos; this._size = size; this._title = itemTitle; + this._tabName = tabName; const content = new Panel(); content.addClass('grid-stack-item-content'); @@ -63,10 +66,35 @@ export class GridStackItem extends Panel { this._size = value; } + get tabName(): string { + return this._tabName; + } + + set tabName(value: string) { + this._tabName = value; + } get changed(): ISignal { return this._changed; } + protected onAfterAttach(msg: Message): void { + super.onAfterAttach(msg); + this.node.addEventListener('drop', this._ondrop.bind(this)); + } + + protected onBeforeDetach(msg: Message): void { + this.node.removeEventListener('drop', this._ondrop.bind(this)); + super.onBeforeDetach(msg); + } + + private async _ondrop(event: DragEvent) { + const datasetId = event.dataTransfer?.getData(DATASET_MIME); + if (!datasetId) { + return; + } + this._changed.emit({ action: 'layer', dataLayer: datasetId }); + } + private _createToolbar(itemTitle: string): Toolbar { const toolbar = new Toolbar(); toolbar.node.addEventListener('click', () => { @@ -97,7 +125,7 @@ export class GridStackItem extends Panel { private _size: number[]; private _title: string; private _cellOutput: Widget; - + private _tabName: string; private _changed: Signal; } @@ -108,9 +136,11 @@ export namespace GridStackItem { itemTitle?: string; pos: number[]; size: number[]; + tabName: string; } export interface IChange { - action: 'close' | 'lock' | 'edit'; + action: 'close' | 'lock' | 'edit' | 'layer'; + dataLayer?: string; } } diff --git a/src/viewPanel/sessionWidget.ts b/src/viewPanel/sessionWidget.ts index 0c46ada..fde361b 100644 --- a/src/viewPanel/sessionWidget.ts +++ b/src/viewPanel/sessionWidget.ts @@ -96,8 +96,14 @@ export class SessionWidget extends BoxPanel { } private async _ondrop(event: DragEvent) { - const datasetId = event.dataTransfer?.getData(DATASET_MIME); + const target = event.target as HTMLElement; + const viewer = target.closest('.grid-stack-item.glue-item'); + // No-op if the target is a viewer, it will be managed by the viewer itself. + if (viewer) { + return; + } + const datasetId = event.dataTransfer?.getData(DATASET_MIME); const items: IDict = { Histogram: CommandIDs.new1DHistogram, '1D Profile': CommandIDs.new1DProfile, diff --git a/src/viewPanel/tabLayout.ts b/src/viewPanel/tabLayout.ts index 0161c9e..4bfca3d 100644 --- a/src/viewPanel/tabLayout.ts +++ b/src/viewPanel/tabLayout.ts @@ -318,6 +318,21 @@ export class TabLayout extends Layout { gridItem: item as any }); } + + /** + * Request to add a layer with a dataset to the viewer. + * + * @param item - the viewer where to add a layer. + * @param dataName - the data to add to the viewer. + */ + private _addViewerLayer(item: GridStackItem, dataName: string): void { + this._commands.execute(CommandIDs.addViewerLayer, { + tab: item.tabName, + viewer: item.cellIdentity, + data: dataName + }); + } + /** * Handle change-event messages sent to from gridstack. */ @@ -376,6 +391,12 @@ export class TabLayout extends Layout { case 'edit': this._handleEdit(sender); break; + case 'layer': + if (!change.dataLayer) { + return; + } + this._addViewerLayer(sender, change.dataLayer); + break; default: break; } diff --git a/src/viewPanel/tabView.ts b/src/viewPanel/tabView.ts index 7d7d7bd..98394a8 100644 --- a/src/viewPanel/tabView.ts +++ b/src/viewPanel/tabView.ts @@ -153,7 +153,8 @@ export class TabView extends Widget { cell: out, itemTitle: itemTitle.match(/($[a-z])|[A-Z][^A-Z]+/g)?.join(' '), pos: viewerData.pos, - size: viewerData.size + size: viewerData.size, + tabName: tabName }); const cellOutput = item.cellOutput as SimplifiedOutputArea; if (this._context) { From 8d83c7530bc0b47aa7512ae1529dae037163b979 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Jul 2023 20:11:35 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- glue_jupyterlab/glue_session.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/glue_jupyterlab/glue_session.py b/glue_jupyterlab/glue_session.py index 22f76a7..b3f8404 100644 --- a/glue_jupyterlab/glue_session.py +++ b/glue_jupyterlab/glue_session.py @@ -349,9 +349,11 @@ def _init_ydoc(self) -> None: ydoc=self._sessionYDoc, ) - def add_viewer_layer(self, tab_name: str, viewer_name: str, data_name:str) -> None: + def add_viewer_layer(self, tab_name: str, viewer_name: str, data_name: str) -> None: """Add a layer with new dataset to a viewer""" - viewer = self._viewers.get(tab_name, {}).get(viewer_name, {}).get("widget", None) + viewer = ( + self._viewers.get(tab_name, {}).get(viewer_name, {}).get("widget", None) + ) if not viewer: return data = self._data[data_name] From c3cf70eb4ab4a7a6b5646e6cee2f06c418beb0a7 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Fri, 7 Jul 2023 09:20:03 +0200 Subject: [PATCH 3/3] add retires on ui-test --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aefdda4..8303b36 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -130,7 +130,7 @@ jobs: - name: Execute integration tests working-directory: ui-tests run: | - jlpm run test + jlpm run test --retries=3 - name: Upload Playwright Test report if: always()