diff --git a/packages/application-extension/schema/menus.json b/packages/application-extension/schema/menus.json new file mode 100644 index 000000000..b8c97f2fc --- /dev/null +++ b/packages/application-extension/schema/menus.json @@ -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" +} diff --git a/packages/application-extension/src/index.ts b/packages/application-extension/src/index.ts index b8424907a..9fd0bb3fc 100644 --- a/packages/application-extension/src/index.ts +++ b/packages/application-extension/src/index.ts @@ -79,6 +79,11 @@ namespace CommandIDs { */ export const openTree = 'application:open-tree'; + /** + * Rename the current document + */ + export const rename = 'application:rename'; + /** * Resolve tree path */ @@ -187,14 +192,34 @@ const opener: JupyterFrontEndPlugin = { }; /** - * 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 = { - id: '@retrolab/application-extension:no-tabs-menu', +const menus: JupyterFrontEndPlugin = { + 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; + } } }; @@ -384,19 +409,23 @@ const tabTitle: JupyterFrontEndPlugin = { const title: JupyterFrontEndPlugin = { 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 => { const current = shell.currentWidget; if (!current || !(current instanceof DocumentWidget)) { return; @@ -404,6 +433,7 @@ const title: JupyterFrontEndPlugin = { if (widget.node.children.length > 0) { return; } + const h = document.createElement('h1'); h.textContent = current.title.label; widget.node.appendChild(h); @@ -411,38 +441,56 @@ const title: JupyterFrontEndPlugin = { 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(); } }; @@ -665,7 +713,7 @@ const zen: JupyterFrontEndPlugin = { const plugins: JupyterFrontEndPlugin[] = [ dirty, logo, - noTabsMenu, + menus, opener, pages, paths, diff --git a/packages/tree-extension/src/index.ts b/packages/tree-extension/src/index.ts index 05564b7fd..02afac694 100644 --- a/packages/tree-extension/src/index.ts +++ b/packages/tree-extension/src/index.ts @@ -135,7 +135,7 @@ const newTerminal: JupyterFrontEndPlugin = { * A plugin to add the file browser widget to an ILabShell */ const browserWidget: JupyterFrontEndPlugin = { - id: '@jupyterlab-classic/tree-extension:widget', + id: '@retrolab/tree-extension:widget', requires: [IFileBrowserFactory, ITranslator], optional: [IRunningSessionManagers], autoStart: true, diff --git a/ui-tests/test/editor.spec.ts b/ui-tests/test/editor.spec.ts new file mode 100644 index 000000000..da2ad8342 --- /dev/null +++ b/ui-tests/test/editor.spec.ts @@ -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); + }); +}); diff --git a/ui-tests/test/menus.spec.ts b/ui-tests/test/menus.spec.ts new file mode 100644 index 000000000..f2a8464ae --- /dev/null +++ b/ui-tests/test/menus.spec.ts @@ -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()); + }); + }); +}); diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-chromium-linux.png new file mode 100644 index 000000000..530509abc Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-firefox-linux.png new file mode 100644 index 000000000..75d5fa45a Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-edit-firefox-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-chromium-linux.png new file mode 100644 index 000000000..f2fb814bc Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-firefox-linux.png new file mode 100644 index 000000000..be06040af Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-firefox-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-chromium-linux.png new file mode 100644 index 000000000..04b6330d5 Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-firefox-linux.png new file mode 100644 index 000000000..3a2a755ab Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-file-new-firefox-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-chromium-linux.png new file mode 100644 index 000000000..c805166c5 Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-firefox-linux.png new file mode 100644 index 000000000..bed6e38ed Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-help-firefox-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-chromium-linux.png new file mode 100644 index 000000000..5b79bb34f Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-firefox-linux.png new file mode 100644 index 000000000..0e4bf87fb Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-kernel-firefox-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-chromium-linux.png new file mode 100644 index 000000000..405dcbd7d Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-firefox-linux.png new file mode 100644 index 000000000..50191e69c Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-run-firefox-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-chromium-linux.png new file mode 100644 index 000000000..b9a98a38f Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-firefox-linux.png new file mode 100644 index 000000000..2abad7f0c Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-firefox-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-chromium-linux.png new file mode 100644 index 000000000..d8ffb4906 Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-firefox-linux.png new file mode 100644 index 000000000..215536d8f Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-settings-theme-firefox-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-chromium-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-chromium-linux.png new file mode 100644 index 000000000..56d39531a Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-chromium-linux.png differ diff --git a/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-firefox-linux.png b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-firefox-linux.png new file mode 100644 index 000000000..5ebafe99e Binary files /dev/null and b/ui-tests/test/menus.spec.ts-snapshots/opened-menu-view-firefox-linux.png differ diff --git a/ui-tests/test/notebooks/empty.ipynb b/ui-tests/test/notebooks/empty.ipynb new file mode 100644 index 000000000..bca949aef --- /dev/null +++ b/ui-tests/test/notebooks/empty.ipynb @@ -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 +} diff --git a/ui-tests/test/settings.spec.ts-snapshots/top-hidden-chromium-linux.png b/ui-tests/test/settings.spec.ts-snapshots/top-hidden-chromium-linux.png index d8a4cf430..dcd8a855c 100644 Binary files a/ui-tests/test/settings.spec.ts-snapshots/top-hidden-chromium-linux.png and b/ui-tests/test/settings.spec.ts-snapshots/top-hidden-chromium-linux.png differ diff --git a/ui-tests/test/settings.spec.ts-snapshots/top-hidden-firefox-linux.png b/ui-tests/test/settings.spec.ts-snapshots/top-hidden-firefox-linux.png index 684743f27..61f5d7777 100644 Binary files a/ui-tests/test/settings.spec.ts-snapshots/top-hidden-firefox-linux.png and b/ui-tests/test/settings.spec.ts-snapshots/top-hidden-firefox-linux.png differ diff --git a/ui-tests/test/settings.spec.ts-snapshots/top-visible-chromium-linux.png b/ui-tests/test/settings.spec.ts-snapshots/top-visible-chromium-linux.png index 7ad0488eb..82e426564 100644 Binary files a/ui-tests/test/settings.spec.ts-snapshots/top-visible-chromium-linux.png and b/ui-tests/test/settings.spec.ts-snapshots/top-visible-chromium-linux.png differ diff --git a/ui-tests/test/settings.spec.ts-snapshots/top-visible-firefox-linux.png b/ui-tests/test/settings.spec.ts-snapshots/top-visible-firefox-linux.png index 778a6fff6..f75727def 100644 Binary files a/ui-tests/test/settings.spec.ts-snapshots/top-visible-firefox-linux.png and b/ui-tests/test/settings.spec.ts-snapshots/top-visible-firefox-linux.png differ