Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

Commit

Permalink
Merge pull request #289 from jtpio/tree-menu
Browse files Browse the repository at this point in the history
Improve menus
  • Loading branch information
jtpio committed Nov 18, 2021
2 parents 748f9eb + f168b94 commit 3b9b2bb
Show file tree
Hide file tree
Showing 28 changed files with 251 additions and 31 deletions.
24 changes: 24 additions & 0 deletions packages/application-extension/schema/menus.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"title": "RetroLab Menu Entries",
"description": "RetroLab Menu Entries",
"jupyter.lab.menus": {
"main": [
{
"id": "jp-mainmenu-file",
"items": [
{
"command": "application:rename",
"rank": 4
},
{
"command": "notebook:trust",
"rank": 20
}
]
}
]
},
"properties": {},
"additionalProperties": false,
"type": "object"
}
108 changes: 78 additions & 30 deletions packages/application-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ namespace CommandIDs {
*/
export const openTree = 'application:open-tree';

/**
* Rename the current document
*/
export const rename = 'application:rename';

/**
* Resolve tree path
*/
Expand Down Expand Up @@ -187,14 +192,34 @@ const opener: JupyterFrontEndPlugin<void> = {
};

/**
* A plugin to dispose the Tabs menu
* A plugin to customize menus
*
* TODO: use this plugin to customize the menu items and their order
*/
const noTabsMenu: JupyterFrontEndPlugin<void> = {
id: '@retrolab/application-extension:no-tabs-menu',
const menus: JupyterFrontEndPlugin<void> = {
id: '@retrolab/application-extension:menus',
requires: [IMainMenu],
autoStart: true,
activate: (app: JupyterFrontEnd, menu: IMainMenu) => {
// always disable the Tabs menu
menu.tabsMenu.dispose();

const page = PageConfig.getOption('retroPage');
switch (page) {
case 'consoles':
case 'terminals':
case 'tree':
menu.editMenu.dispose();
menu.kernelMenu.dispose();
menu.runMenu.dispose();
break;
case 'edit':
menu.kernelMenu.dispose();
menu.runMenu.dispose();
break;
default:
break;
}
}
};

Expand Down Expand Up @@ -384,65 +409,88 @@ const tabTitle: JupyterFrontEndPlugin<void> = {
const title: JupyterFrontEndPlugin<void> = {
id: '@retrolab/application-extension:title',
autoStart: true,
requires: [IRetroShell],
requires: [IRetroShell, ITranslator],
optional: [IDocumentManager, IRouter],
activate: (
app: JupyterFrontEnd,
shell: IRetroShell,
translator: ITranslator,
docManager: IDocumentManager | null,
router: IRouter | null
) => {
const { commands } = app;
const trans = translator.load('retrolab');

const widget = new Widget();
widget.id = 'jp-title';
app.shell.add(widget, 'top', { rank: 10 });

const addTitle = async () => {
const addTitle = async (): Promise<void> => {
const current = shell.currentWidget;
if (!current || !(current instanceof DocumentWidget)) {
return;
}
if (widget.node.children.length > 0) {
return;
}

const h = document.createElement('h1');
h.textContent = current.title.label;
widget.node.appendChild(h);
widget.node.style.marginLeft = '10px';
if (!docManager) {
return;
}
widget.node.onclick = async () => {
const result = await renameDialog(docManager, current.context.path);

// activate the current widget to bring the focus
if (current) {
current.activate();
}
const isEnabled = () => {
const { currentWidget } = shell;
return !!(currentWidget && docManager.contextForWidget(currentWidget));
};

if (result === null) {
return;
}
commands.addCommand(CommandIDs.rename, {
label: () => trans.__('Rename…'),
isEnabled,
execute: async () => {
if (!isEnabled()) {
return;
}

const newPath = current.context.path ?? result.path;
const basename = PathExt.basename(newPath);
h.textContent = basename;
if (!router) {
return;
}
const matches = router.current.path.match(TREE_PATTERN) ?? [];
const [, route, path] = matches;
if (!route || !path) {
return;
const result = await renameDialog(docManager, current.context.path);

// activate the current widget to bring the focus
if (current) {
current.activate();
}

if (result === null) {
return;
}

const newPath = current.context.path ?? result.path;
const basename = PathExt.basename(newPath);
h.textContent = basename;
if (!router) {
return;
}
const matches = router.current.path.match(TREE_PATTERN) ?? [];
const [, route, path] = matches;
if (!route || !path) {
return;
}
const encoded = encodeURIComponent(newPath);
router.navigate(`/retro/${route}/${encoded}`, {
skipRouting: true
});
}
const encoded = encodeURIComponent(newPath);
router.navigate(`/retro/${route}/${encoded}`, {
skipRouting: true
});
});

widget.node.onclick = async () => {
void commands.execute(CommandIDs.rename);
};
};

shell.currentChanged.connect(addTitle);
addTitle();
void addTitle();
}
};

Expand Down Expand Up @@ -665,7 +713,7 @@ const zen: JupyterFrontEndPlugin<void> = {
const plugins: JupyterFrontEndPlugin<any>[] = [
dirty,
logo,
noTabsMenu,
menus,
opener,
pages,
paths,
Expand Down
2 changes: 1 addition & 1 deletion packages/tree-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const newTerminal: JupyterFrontEndPlugin<void> = {
* A plugin to add the file browser widget to an ILabShell
*/
const browserWidget: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/tree-extension:widget',
id: '@retrolab/tree-extension:widget',
requires: [IFileBrowserFactory, ITranslator],
optional: [IRunningSessionManagers],
autoStart: true,
Expand Down
70 changes: 70 additions & 0 deletions ui-tests/test/editor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import path from 'path';

import { test } from './fixtures';

import { expect } from '@playwright/test';

const FILE = 'environment.yml';

test.use({ autoGoto: false });

const processRenameDialog = async (page, prevName: string, newName: string) => {
// Rename in the input dialog
await page.fill(
`//div[normalize-space(.)='File Path${prevName}New Name']/input`,
newName
);

await Promise.all([
await page.click('text="Rename"'),
// wait until the URL is updated
await page.waitForNavigation()
]);
};

test.describe('Editor', () => {
test.beforeEach(async ({ page, tmpPath }) => {
await page.contents.uploadFile(
path.resolve(__dirname, `../../binder/${FILE}`),
`${tmpPath}/${FILE}`
);
});

test('Renaming the file by clicking on the title', async ({
page,
tmpPath
}) => {
const file = `${tmpPath}/${FILE}`;
await page.goto(`edit/${file}`);

// Click on the title
await page.click(`text="${FILE}"`);

const newName = 'test.yml';
await processRenameDialog(page, file, newName);

// Check the URL contains the new name
const url = page.url();
expect(url).toContain(newName);
});

test('Renaming the file via the menu entry', async ({ page, tmpPath }) => {
const file = `${tmpPath}/${FILE}`;
await page.goto(`edit/${file}`);

// Click on the title
await page.menu.clickMenuItem('File>Rename…');

// Rename in the input dialog
const newName = 'test.yml';

await processRenameDialog(page, file, newName);

// Check the URL contains the new name
const url = page.url();
expect(url).toContain(newName);
});
});
45 changes: 45 additions & 0 deletions ui-tests/test/menus.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import path from 'path';

import { test } from './fixtures';

import { expect } from '@playwright/test';

const NOTEBOOK = 'empty.ipynb';

const MENU_PATHS = [
'File',
'File>New',
'Edit',
'View',
'Run',
'Kernel',
'Settings',
'Settings>Theme',
'Help'
];

test.use({ autoGoto: false });

test.describe('Notebook Menus', () => {
test.beforeEach(async ({ page, tmpPath }) => {
await page.contents.uploadFile(
path.resolve(__dirname, `./notebooks/${NOTEBOOK}`),
`${tmpPath}/${NOTEBOOK}`
);
});

MENU_PATHS.forEach(menuPath => {
test(`Open menu item ${menuPath}`, async ({ page, tmpPath }) => {
await page.goto(`notebooks/${tmpPath}/${NOTEBOOK}`);
await page.menu.open(menuPath);
expect(await page.menu.isOpen(menuPath)).toBeTruthy();

const imageName = `opened-menu-${menuPath.replace(/>/g, '-')}.png`;
const menu = await page.menu.getOpenMenu();
expect(await menu.screenshot()).toMatchSnapshot(imageName.toLowerCase());
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions ui-tests/test/notebooks/empty.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "6f7028b9-4d2c-4fa2-96ee-bfa77bbee434",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 3b9b2bb

Please sign in to comment.