diff --git a/dev_mode/package.json b/dev_mode/package.json index ae19f83edb4d..b5b3660526e6 100644 --- a/dev_mode/package.json +++ b/dev_mode/package.json @@ -20,8 +20,8 @@ "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.9.6", - "@jupyter/react-components": "^0.16.3", - "@jupyter/web-components": "^0.16.3", + "@jupyter/react-components": "^0.16.6", + "@jupyter/web-components": "^0.16.6", "@jupyter/ydoc": "^2.0.1", "@jupyterlab/application": "~4.3.0-alpha.2", "@jupyterlab/application-extension": "~4.3.0-alpha.2", diff --git a/examples/cell/package.json b/examples/cell/package.json index 03e590fe2389..30a0ad81cdaf 100644 --- a/examples/cell/package.json +++ b/examples/cell/package.json @@ -7,7 +7,7 @@ "clean": "rimraf build" }, "dependencies": { - "@jupyter/web-components": "^0.16.3", + "@jupyter/web-components": "^0.16.6", "@jupyter/ydoc": "^2.0.1", "@jupyterlab/application": "^4.3.0-alpha.2", "@jupyterlab/apputils": "^4.4.0-alpha.2", diff --git a/examples/console/package.json b/examples/console/package.json index 9d492f92967e..a597cbb86da1 100644 --- a/examples/console/package.json +++ b/examples/console/package.json @@ -7,7 +7,7 @@ "clean": "rimraf build" }, "dependencies": { - "@jupyter/web-components": "^0.16.3", + "@jupyter/web-components": "^0.16.6", "@jupyter/ydoc": "^2.0.1", "@jupyterlab/application": "^4.3.0-alpha.2", "@jupyterlab/codemirror": "^4.3.0-alpha.2", diff --git a/examples/filebrowser/package.json b/examples/filebrowser/package.json index 59aeddcf48fc..a7a02fc17797 100644 --- a/examples/filebrowser/package.json +++ b/examples/filebrowser/package.json @@ -7,7 +7,7 @@ "clean": "rimraf build" }, "dependencies": { - "@jupyter/web-components": "^0.16.3", + "@jupyter/web-components": "^0.16.6", "@jupyterlab/application": "^4.3.0-alpha.2", "@jupyterlab/apputils": "^4.4.0-alpha.2", "@jupyterlab/codemirror": "^4.3.0-alpha.2", diff --git a/examples/notebook/package.json b/examples/notebook/package.json index 05ae348d6c05..ff141df9ddab 100644 --- a/examples/notebook/package.json +++ b/examples/notebook/package.json @@ -7,7 +7,7 @@ "clean": "rimraf build" }, "dependencies": { - "@jupyter/web-components": "^0.16.3", + "@jupyter/web-components": "^0.16.6", "@jupyter/ydoc": "^2.0.1", "@jupyterlab/application": "^4.3.0-alpha.2", "@jupyterlab/apputils": "^4.4.0-alpha.2", diff --git a/examples/terminal/package.json b/examples/terminal/package.json index 213ed7401331..3b6e02bee599 100644 --- a/examples/terminal/package.json +++ b/examples/terminal/package.json @@ -7,7 +7,7 @@ "clean": "rimraf build" }, "dependencies": { - "@jupyter/web-components": "^0.16.3", + "@jupyter/web-components": "^0.16.6", "@jupyterlab/application": "^4.3.0-alpha.2", "@jupyterlab/coreutils": "^6.3.0-alpha.2", "@jupyterlab/services": "^7.3.0-alpha.2", diff --git a/galata/README.md b/galata/README.md index 9a47c6935e50..ce4f3f147763 100644 --- a/galata/README.md +++ b/galata/README.md @@ -454,6 +454,36 @@ Possible values are: By default the user is stored in-memory. +### kernels + +- type: \ | null> + +Kernels created during the test. +Possible values are: + +- null: The kernels API won't be mocked +- Map\: The kernels created during a test. + By default the kernels created during a test will be tracked and disposed at the end. + +Example: + +```ts +test('should return the active kernels', async ({ page, kernels }) => { + await page.notebook.createNew(); + + // Wait for the poll to tick + await page.waitForResponse( + async response => + response.url().includes('api/kernels') && + response.request().method() === 'GET' && + ((await response.json()) as any[]).length === 1 + ); + + expect(kernels.size).toEqual(1); + // You can introspect the kernels.values()[0] if needed +}); +``` + ### sessions - type: \ | null> diff --git a/galata/src/fixtures.ts b/galata/src/fixtures.ts index 088ec0673797..99e820648419 100644 --- a/galata/src/fixtures.ts +++ b/galata/src/fixtures.ts @@ -2,7 +2,7 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import type { Session, TerminalAPI, User } from '@jupyterlab/services'; +import type { Kernel, Session, TerminalAPI, User } from '@jupyterlab/services'; import { test as base, Page, @@ -56,6 +56,16 @@ export type GalataOptions = { * Default: true */ autoGoto: boolean; + /** + * Kernels created during the test. + * + * Possible values are: + * - null: The kernels API won't be mocked + * - Map: The kernels created during a test. + * + * By default the kernels created during a test will be tracked and disposed at the end. + */ + kernels: Map | null; /** * Mock JupyterLab config in-memory or not. * @@ -180,6 +190,24 @@ export const test: TestType< * Note: Setting it to false allows to register new route mock-ups for example. */ autoGoto: [true, { option: true }], + /** + * Kernels created during the test. + * + * Possible values are: + * - null: The kernels API won't be mocked + * - Map: The kernels created during a test. + * + * By default the kernels created during a test will be tracked and disposed at the end. + */ + kernels: async ({ request }, use) => { + const kernels = new Map(); + + await use(kernels); + + if (kernels.size > 0) { + await galata.Mock.clearRunners(request, [...kernels.keys()], 'kernels'); + } + }, /** * Mock JupyterLab config in-memory or not. * @@ -353,6 +381,7 @@ export const test: TestType< appPath, autoGoto, baseURL, + kernels, mockConfig, mockSettings, mockState, @@ -378,7 +407,8 @@ export const test: TestType< sessions, terminals, tmpPath, - waitForApplication + waitForApplication, + kernels ) ); } diff --git a/galata/src/galata.ts b/galata/src/galata.ts index fbd640c5e9a4..2ade83ecf943 100644 --- a/galata/src/galata.ts +++ b/galata/src/galata.ts @@ -4,6 +4,7 @@ import type * as nbformat from '@jupyterlab/nbformat'; import type { + Kernel, Session, TerminalAPI, User, @@ -150,6 +151,12 @@ export namespace galata { * Default true */ mockUser?: boolean | Partial; + /** + * Whether to store kernels in memory or not. + * + * Default true + */ + mockKernels?: boolean; /** * Whether to store sessions in memory or not. * @@ -216,7 +223,8 @@ export namespace galata { sessions: Map | null, terminals: Map | null, tmpPath: string, - waitForApplication: (page: Page, helpers: IJupyterLabPage) => Promise + waitForApplication: (page: Page, helpers: IJupyterLabPage) => Promise, + kernels?: Map | null ): Promise { // Hook the helpers const jlabWithPage = addHelpersToPage( @@ -273,9 +281,12 @@ export namespace galata { await Mock.mockUser(page, user); } - // Add sessions and terminals trackers + // Add kernels, sessions and terminals trackers + if (kernels) { + await Mock.mockRunners(page, kernels, 'kernels'); + } if (sessions) { - await Mock.mockRunners(page, sessions, 'sessions'); + await Mock.mockRunners(page, sessions, 'sessions', kernels ?? undefined); } if (terminals) { await Mock.mockRunners(page, terminals, 'terminals'); @@ -310,6 +321,7 @@ export namespace galata { */ export async function newPage(options: INewPageOption): Promise<{ page: IJupyterLabPageFixture; + kernels: Map | null; sessions: Map | null; terminals: Map | null; }> { @@ -320,6 +332,7 @@ export namespace galata { browser, waitForApplication, mockConfig, + mockKernels, mockSessions, mockSettings, mockState, @@ -330,6 +343,7 @@ export namespace galata { appPath: '/lab', autoGoto: true, mockConfig: true, + mockKernels: true, mockSessions: true, mockSettings: galata.DEFAULT_SETTINGS, mockState: true, @@ -341,6 +355,7 @@ export namespace galata { const context = await browser.newContext(); const page = await context.newPage(); + const kernels = mockKernels ? new Map() : null; const sessions = mockSessions ? new Map() : null; const terminals = mockTerminals ? new Map() @@ -359,8 +374,10 @@ export namespace galata { sessions, terminals, tmpPath, - waitForApplication + waitForApplication, + kernels ), + kernels, sessions, terminals }; @@ -407,6 +424,15 @@ export namespace galata { */ export const extensions = /.*\/lab\/api\/extensions.*/; + /** + * Kernels API + * + * The kernel id can be found in the named group `id`. + * + * The id will be prefixed by '/'. + */ + export const kernels = /.*\/api\/kernels(?!pecs)(?\/[@:-\w]+)?/; + /** * Sessions API * @@ -703,7 +729,7 @@ export namespace galata { export async function clearRunners( request: APIRequestContext, runners: string[], - type: 'sessions' | 'terminals' + type: 'kernels' | 'sessions' | 'terminals' ): Promise { const responses = await Promise.all( [...new Set(runners)].map(id => @@ -780,6 +806,12 @@ export namespace galata { }); } + const routes = { + kernels: Routes.kernels, + sessions: Routes.sessions, + terminals: Routes.terminals + }; + /** * Mock the runners API to display only those created during a test * @@ -790,10 +822,10 @@ export namespace galata { export function mockRunners( page: Page, runners: Map, - type: 'sessions' | 'terminals' + type: 'kernels' | 'sessions' | 'terminals', + kernels?: Map ): Promise { - const routeRegex = - type === 'sessions' ? Routes.sessions : Routes.terminals; + const routeRegex = routes[type]; // Listen for closing connection (may happen when request are still being processed) let isClosed = false; const ctxt = page.context(); @@ -836,7 +868,7 @@ export namespace galata { } const data = await response.json(); // Update stored runners - runners.set(type === 'sessions' ? data.id : data.name, data); + runners.set(type === 'terminals' ? data.name : data.id, data); if (!page.isClosed() && !isClosed) { return route.fulfill({ @@ -870,7 +902,7 @@ export namespace galata { const updated = new Set(); data.forEach(item => { const itemID: string = - type === 'sessions' ? item.id : item.name; + type === 'terminals' ? item.name : item.id; if (runners.has(itemID)) { updated.add(itemID); runners.set(itemID, item); @@ -909,7 +941,11 @@ export namespace galata { } const data = await response.json(); // Update stored runners - runners.set(type === 'sessions' ? data.id : data.name, data); + runners.set(type === 'terminals' ? data.name : data.id, data); + // Update kernels + if (kernels && type === 'sessions' && data.kernel.id) { + kernels.set(data.kernel.id, data.kernel); + } if (!page.isClosed() && !isClosed) { return route.fulfill({ @@ -933,11 +969,15 @@ export namespace galata { break; } const data = await response.json(); - const id = type === 'sessions' ? data.id : data.name; + const id = type === 'terminals' ? data.name : data.id; runners.set(id, data); + // Update kernels + if (kernels && type === 'sessions' && data.kernel.id) { + kernels.set(data.kernel.id, data.kernel); + } if (!page.isClosed() && !isClosed) { return route.fulfill({ - status: type === 'sessions' ? 201 : 200, + status: type === 'terminals' ? 200 : 201, body: JSON.stringify(data), contentType: 'application/json', headers: response.headers as any diff --git a/galata/src/helpers/debuggerpanel.ts b/galata/src/helpers/debuggerpanel.ts index 108133617873..46eac57c2771 100644 --- a/galata/src/helpers/debuggerpanel.ts +++ b/galata/src/helpers/debuggerpanel.ts @@ -100,7 +100,10 @@ export class DebuggerHelper { * Waits for variables to be populated in the variables panel */ async waitForVariables(): Promise { - await this.page.locator('.jp-DebuggerVariables-body ul').waitFor(); + await this.page + .locator('.jp-DebuggerVariables-body') + .getByRole('tree') + .waitFor(); } /** @@ -108,11 +111,9 @@ export class DebuggerHelper { */ async renderVariable(name: string): Promise { await this.page - .locator(`.jp-DebuggerVariables :text("${name}")`) + .getByRole('treeitem', { name: `${name}:` }) .click({ button: 'right' }); - await this.page - .locator('.lm-Menu-itemLabel:text("Render Variable")') - .click(); + await this.page.getByRole('menuitem', { name: 'Render Variable' }).click(); await this.page.locator('.jp-VariableRendererPanel-renderer').waitFor(); } diff --git a/galata/test/documentation/debugger.test.ts-snapshots/debugger-variables-documentation-linux.png b/galata/test/documentation/debugger.test.ts-snapshots/debugger-variables-documentation-linux.png index 587bde0044d3..ec79bc916222 100644 Binary files a/galata/test/documentation/debugger.test.ts-snapshots/debugger-variables-documentation-linux.png and b/galata/test/documentation/debugger.test.ts-snapshots/debugger-variables-documentation-linux.png differ diff --git a/galata/test/documentation/general.test.ts-snapshots/running-layout-documentation-linux.png b/galata/test/documentation/general.test.ts-snapshots/running-layout-documentation-linux.png index 072ac496f644..e729d4458b65 100644 Binary files a/galata/test/documentation/general.test.ts-snapshots/running-layout-documentation-linux.png and b/galata/test/documentation/general.test.ts-snapshots/running-layout-documentation-linux.png differ diff --git a/galata/test/documentation/general.test.ts-snapshots/running-modal-documentation-linux.png b/galata/test/documentation/general.test.ts-snapshots/running-modal-documentation-linux.png index 1cbb6fd9d1ea..769b8fdbfa87 100644 Binary files a/galata/test/documentation/general.test.ts-snapshots/running-modal-documentation-linux.png and b/galata/test/documentation/general.test.ts-snapshots/running-modal-documentation-linux.png differ diff --git a/galata/test/documentation/overview.test.ts-snapshots/interface-tabs-documentation-linux.png b/galata/test/documentation/overview.test.ts-snapshots/interface-tabs-documentation-linux.png index 84bce3c7a208..b3953a5fb056 100644 Binary files a/galata/test/documentation/overview.test.ts-snapshots/interface-tabs-documentation-linux.png and b/galata/test/documentation/overview.test.ts-snapshots/interface-tabs-documentation-linux.png differ diff --git a/galata/test/documentation/workspaces.test.ts b/galata/test/documentation/workspaces.test.ts index 8009315bd3df..9c949610492d 100644 --- a/galata/test/documentation/workspaces.test.ts +++ b/galata/test/documentation/workspaces.test.ts @@ -6,6 +6,7 @@ import * as path from 'path'; import { positionMouseOver } from './utils'; test.use({ + autoGoto: false, viewport: { height: 720, width: 1280 }, mockState: false, tmpPath: 'workspaces-sidebar' @@ -25,6 +26,7 @@ test.describe('Workspaces sidebar', () => { }); test.beforeEach(async ({ page, tmpPath }) => { + await page.goto('?reset'); await page.filebrowser.openDirectory(tmpPath); }); @@ -38,11 +40,7 @@ test.describe('Workspaces sidebar', () => { await page.dblclick( `.jp-DirListing-item span:has-text("${testWorkspace}")` ); - await page - .locator( - `.jp-RunningSessions-item.jp-mod-workspace >> text=${workspaceName}` - ) - .waitFor(); + await page.getByRole('treeitem', { name: workspaceName }).waitFor(); await galata.Mock.mockRunners(page, new Map(), 'sessions'); @@ -61,14 +59,12 @@ test.describe('Workspaces sidebar', () => { }` }); - const workspaceItem = page.locator( - '.jp-RunningSessions-item.jp-mod-workspace >> text=default' - ); + const workspaceItem = page.getByRole('treeitem', { name: 'default' }); // Open menu for the shot await workspaceItem.click({ button: 'right' }); - const renameWorkspace = page.locator( - '.lm-Menu-itemLabel:text("Rename Workspace")' - ); + const renameWorkspace = page.getByRole('menuitem', { + name: 'Rename Workspace' + }); await renameWorkspace.hover(); // Inject mouse await page.evaluate( diff --git a/galata/test/documentation/workspaces.test.ts-snapshots/workspaces-sidebar-documentation-linux.png b/galata/test/documentation/workspaces.test.ts-snapshots/workspaces-sidebar-documentation-linux.png index 9f33168213b5..093e01673858 100644 Binary files a/galata/test/documentation/workspaces.test.ts-snapshots/workspaces-sidebar-documentation-linux.png and b/galata/test/documentation/workspaces.test.ts-snapshots/workspaces-sidebar-documentation-linux.png differ diff --git a/galata/test/galata/fixture.spec.ts b/galata/test/galata/fixture.spec.ts index 30fb065c4f43..5cb3d622a88c 100644 --- a/galata/test/galata/fixture.spec.ts +++ b/galata/test/galata/fixture.spec.ts @@ -105,6 +105,41 @@ test.describe('mockState', () => { }); }); +test.describe('kernels', () => { + test('should return the active kernels', async ({ page, kernels }) => { + await page.notebook.createNew(); + await page.locator('text= | Idle').waitFor(); + + await page + .getByRole('tab', { name: 'Running Terminals and Kernels' }) + .click(); + + await Promise.all([ + page.waitForResponse( + async response => + response.url().includes('api/kernels') && + response.request().method() === 'GET' && + ((await response.json()) as any[]).length === 1 + ), + page.getByRole('button', { name: 'Refresh List' }).click() + ]); + + expect.soft(kernels.size).toEqual(1); + + await page.menu.clickMenuItem('File>New>Console'); + await page.locator('.jp-Dialog').waitFor(); + await page.click('.jp-Dialog .jp-mod-accept'); + await page.locator('text= | Idle').waitFor(); + + await page.getByRole('button', { name: 'Refresh List' }).click(); + expect(kernels.size).toEqual(2); + }); + + test('should have no kernels at first', ({ kernels }) => { + expect(kernels.size).toEqual(0); + }); +}); + test.describe('sessions', () => { test('should return the active sessions', async ({ page, sessions }) => { await page.notebook.createNew(); diff --git a/galata/test/jupyterlab/debugger.test.ts b/galata/test/jupyterlab/debugger.test.ts index b8f39c773210..4ff3fe41d51c 100644 --- a/galata/test/jupyterlab/debugger.test.ts +++ b/galata/test/jupyterlab/debugger.test.ts @@ -95,22 +95,22 @@ test.describe('Debugger Tests', () => { await page.debugger.waitForVariables(); const variablesPanel = await page.debugger.getVariablesPanelLocator(); - expect(await variablesPanel.screenshot()).toMatchSnapshot( - 'image-debug-session-global-variables.png' - ); + expect + .soft(await variablesPanel.screenshot()) + .toMatchSnapshot('image-debug-session-global-variables.png'); await page.debugger.renderVariable(globalVar); let richVariableTab = await page.activity.getPanelLocator( `${globalVar} - ${notebookName}` ); - expect(await richVariableTab?.screenshot()).toMatchSnapshot( - 'image-debug-session-global-rich-variable.png' - ); + expect + .soft(await richVariableTab?.screenshot()) + .toMatchSnapshot('image-debug-session-global-rich-variable.png'); await page.activity.closePanel(`${globalVar} - ${notebookName}`); - await page.locator('jp-button[title="Continue (F9)"]').click(); - await expect(variablesPanel).not.toContain('ul'); + await page.getByRole('button', { name: 'Continue (F9)' }).click(); + await expect.soft(variablesPanel.getByRole('tree')).toHaveCount(1); await page.debugger.waitForVariables(); await page.debugger.renderVariable(localVar); @@ -130,13 +130,14 @@ test.describe('Debugger Tests', () => { }); const menu = await page.menu.getOpenMenuLocator(); - await menu?.locator('[data-command="fileeditor:create-console"]')?.click(); + await menu + ?.getByRole('menuitem', { name: 'Create Console for Editor' }) + .click(); - await page.locator('.jp-Dialog-body').waitFor(); - const select = page.locator('.jp-Dialog-body >> select'); - const option = select.locator('option:has-text("ipykernel")'); - await select.selectOption(await option.textContent()); - await page.click('div.jp-Dialog-content >> button:has-text("Select")'); + await page.getByRole('dialog').waitFor(); + const select = page.getByRole('dialog').getByRole('combobox'); + await select.selectOption('Python 3 (ipykernel)'); + await page.getByRole('button', { name: 'Select Kernel' }).click(); await page.getByText('Python 3 (ipykernel) | Idle').waitFor(); @@ -177,9 +178,23 @@ test.describe('Debugger Tests', () => { test.describe('Debugger Variables', () => { test.use({ autoGoto: false }); - const copyToGlobalsRequest = new PromiseDelegate(); + async function init({ page, tmpPath }) { + // Initialize the debugger. + await page.goto(`tree/${tmpPath}`); + await createNotebook(page); + + await page.debugger.switchOn(); + await page.waitForCondition(() => page.debugger.isOpen()); + + await setBreakpoint(page); + } + + test('Copy to globals should work only for local variables', async ({ + page, + tmpPath + }) => { + const copyToGlobalsRequest = new PromiseDelegate(); - test.beforeEach(async ({ page, tmpPath }) => { // Listener to the websocket, to catch the 'copyToGlobals' request. page.on('websocket', ws => { ws.on('framesent', event => { @@ -193,19 +208,8 @@ test.describe('Debugger Variables', () => { }); }); - // Initialize the debugger. - await page.goto(`tree/${tmpPath}`); - await createNotebook(page); + await init({ page, tmpPath }); - await page.debugger.switchOn(); - await page.waitForCondition(() => page.debugger.isOpen()); - - await setBreakpoint(page); - }); - - test('Copy to globals should work only for local variables', async ({ - page - }) => { // Kernel supports copyToGlobals. await page.evaluate(async () => { const debuggerService = await window.galata.getPlugin( @@ -221,38 +225,36 @@ test.describe('Debugger Variables', () => { await page.debugger.waitForCallStack(); // Expect the copy entry to be in the menu. - await page.locator('select[aria-label="Scope"]').selectOption('Locals'); - await page.click('.jp-DebuggerVariables-body li span:text("local_var")', { + await page.getByLabel('Scope').selectOption('Locals'); + await page.getByRole('treeitem', { name: 'local_var:' }).click({ button: 'right' }); - await expect( - page.locator('.lm-Menu-content li div:text("Copy Variable to Globals")') - ).toHaveCount(1); - - await expect( - page.locator('.lm-Menu-content li div:text("Copy Variable to Globals")') - ).toBeVisible(); // Request the copy of the local variable to globals scope. - await page.click( - '.lm-Menu-content li[data-command="debugger:copy-to-globals"]' - ); + await page + .getByRole('menuitem', { name: 'Copy Variable to Globals' }) + .click(); // Wait for the request to be sent. await copyToGlobalsRequest.promise; // Expect the context menu for global variables to not have the 'copy' entry. - await page.locator('select[aria-label="Scope"]').selectOption('Globals'); - await page.click(`.jp-DebuggerVariables-body li span:text("global_var")`, { + await page.getByLabel('Scope').selectOption('Globals'); + await page.getByRole('treeitem', { name: 'global_var:' }).click({ button: 'right' }); - await expect(page.locator('.lm-Menu-content')).toBeVisible(); + await expect.soft(page.getByRole('menu')).toBeVisible(); await expect( - page.locator('.lm-Menu-content li div:text("Copy Variable to Globals")') + page.getByRole('menuitem', { name: 'Copy Variable to Globals' }) ).toHaveCount(0); }); - test('Copy to globals not available from kernel', async ({ page }) => { + test('Copy to globals not available from kernel', async ({ + page, + tmpPath + }) => { + await init({ page, tmpPath }); + // Kernel doesn't support copyToGlobals. await page.evaluate(async () => { const debuggerService = await window.galata.getPlugin( @@ -267,24 +269,26 @@ test.describe('Debugger Variables', () => { // Wait to be stopped on the breakpoint and the local variables to be displayed. await page.debugger.waitForCallStack(); - await page.locator('select[aria-label="Scope"]').selectOption('Locals'); + await page.getByLabel('Scope').selectOption('Locals'); // Expect the menu entry not to be visible. - await page.click('.jp-DebuggerVariables-body li span:text("local_var")', { + await page.getByRole('treeitem', { name: 'local_var:' }).click({ button: 'right' }); - await expect( - page.locator('.lm-Menu-content li div:text("Copy Variable to Globals")') - ).not.toBeVisible(); + await expect + .soft(page.getByRole('menuitem', { name: 'Copy Variable to Globals' })) + .not.toBeVisible(); // Close the contextual menu await page.keyboard.press('Escape'); await expect( - page.locator('li.lm-Menu-item[data-command="debugger:copy-to-clipboard"]') + page.getByRole('menuitem', { name: 'Copy to Clipboard' }) ).toHaveCount(0); }); - test('Copy to clipboard', async ({ page }) => { + test('Copy to clipboard', async ({ page, tmpPath }) => { + await init({ page, tmpPath }); + // Don't wait as it will be blocked. void page.notebook.runCell(1); @@ -292,26 +296,26 @@ test.describe('Debugger Variables', () => { await page.debugger.waitForCallStack(); // Copy value to clipboard - await page.locator('select[aria-label="Scope"]').selectOption('Locals'); - await page.click('.jp-DebuggerVariables-body li span:text("local_var")', { + await page.getByLabel('Scope').selectOption('Locals'); + await page.getByRole('treeitem', { name: 'local_var:' }).click({ button: 'right' }); - await page.locator('.lm-Menu-itemLabel:text("Copy to Clipboard")').click(); + await page.getByRole('menuitem', { name: 'Copy to Clipboard' }).click(); expect(await page.evaluate(() => navigator.clipboard.readText())).toBe('3'); // Copy to clipboard disabled for variables with empty value - await page.locator('select[aria-label="Scope"]').selectOption('Globals'); + await page.getByLabel('Scope').selectOption('Globals'); await page - .locator('.jp-DebuggerVariables-body :text("special variables")') + .getByRole('treeitem', { name: 'special variables:' }) .click({ button: 'right' }); await expect( - page.locator('li.lm-Menu-item[data-command="debugger:copy-to-clipboard"]') - ).toHaveAttribute('aria-disabled', 'true'); + page.getByRole('menuitem', { name: 'Copy to Clipboard' }) + ).toBeDisabled(); // Close the contextual menu await page.keyboard.press('Escape'); await expect( - page.locator('li.lm-Menu-item[data-command="debugger:copy-to-clipboard"]') + page.getByRole('menuitem', { name: 'Copy to Clipboard' }) ).toHaveCount(0); }); }); diff --git a/galata/test/jupyterlab/debugger.test.ts-snapshots/image-debug-session-global-variables-jupyterlab-linux.png b/galata/test/jupyterlab/debugger.test.ts-snapshots/image-debug-session-global-variables-jupyterlab-linux.png index f1eb2b763760..b9020c82f650 100644 Binary files a/galata/test/jupyterlab/debugger.test.ts-snapshots/image-debug-session-global-variables-jupyterlab-linux.png and b/galata/test/jupyterlab/debugger.test.ts-snapshots/image-debug-session-global-variables-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/debugger.test.ts-snapshots/start-debug-session-script-variables-jupyterlab-linux.png b/galata/test/jupyterlab/debugger.test.ts-snapshots/start-debug-session-script-variables-jupyterlab-linux.png index f2f45a4bf292..f0bf07b8bffe 100644 Binary files a/galata/test/jupyterlab/debugger.test.ts-snapshots/start-debug-session-script-variables-jupyterlab-linux.png and b/galata/test/jupyterlab/debugger.test.ts-snapshots/start-debug-session-script-variables-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/debugger.test.ts-snapshots/start-debug-session-variables-jupyterlab-linux.png b/galata/test/jupyterlab/debugger.test.ts-snapshots/start-debug-session-variables-jupyterlab-linux.png index f2f45a4bf292..f0bf07b8bffe 100644 Binary files a/galata/test/jupyterlab/debugger.test.ts-snapshots/start-debug-session-variables-jupyterlab-linux.png and b/galata/test/jupyterlab/debugger.test.ts-snapshots/start-debug-session-variables-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/sidebars.test.ts-snapshots/opened-sidebar-jp-running-sessions-jupyterlab-linux.png b/galata/test/jupyterlab/sidebars.test.ts-snapshots/opened-sidebar-jp-running-sessions-jupyterlab-linux.png index c8efa71a5331..6b964d5bd564 100644 Binary files a/galata/test/jupyterlab/sidebars.test.ts-snapshots/opened-sidebar-jp-running-sessions-jupyterlab-linux.png and b/galata/test/jupyterlab/sidebars.test.ts-snapshots/opened-sidebar-jp-running-sessions-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/toc-running.test.ts b/galata/test/jupyterlab/toc-running.test.ts index dc765140cd9d..252db576b1cd 100644 --- a/galata/test/jupyterlab/toc-running.test.ts +++ b/galata/test/jupyterlab/toc-running.test.ts @@ -16,9 +16,7 @@ test.describe('ToC Running indicator', () => { await page.sidebar.openTab('table-of-contents'); // Wait until the last heading has loaded into the ToC await page - .locator( - '.jp-TableOfContents-content[data-document-type="notebook"] >> text=Title 1.3' - ) + .getByRole('treeitem', { name: 'Title 1.3', exact: true }) .waitFor(); }); @@ -71,13 +69,16 @@ test.describe('ToC Running indicator', () => { await page.notebook.run(); // Collapse ToC - await page.click( - '[aria-label="Table of Contents section"] >> button:left-of(:text("Title 1"))' - ); + await page + .getByRole('treeitem', { name: 'Title 1', exact: true }) + .locator('.expand-collapse-button') + .click(); const executed = page.notebook.runCell(5); - await tocPanel.locator('[data-running="1"]').waitFor(); + await expect( + tocPanel.getByTitle('Title 1', { exact: true }) + ).toHaveAttribute('data-running', '1'); expect(await tocPanel.screenshot()).toMatchSnapshot( 'toc-running-indicator-top-level.png' ); diff --git a/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicator-error-jupyterlab-linux.png b/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicator-error-jupyterlab-linux.png index 0ff6ed3eabf2..30e101e1977d 100644 Binary files a/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicator-error-jupyterlab-linux.png and b/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicator-error-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicator-top-level-jupyterlab-linux.png b/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicator-top-level-jupyterlab-linux.png index f51e3a36dbaf..27956ca36055 100644 Binary files a/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicator-top-level-jupyterlab-linux.png and b/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicator-top-level-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicators-jupyterlab-linux.png b/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicators-jupyterlab-linux.png index 3596bf1964bc..2f9f642d0ba0 100644 Binary files a/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicators-jupyterlab-linux.png and b/galata/test/jupyterlab/toc-running.test.ts-snapshots/toc-running-indicators-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/toc.test.ts b/galata/test/jupyterlab/toc.test.ts index 1d17094ab8b2..0f982633ee63 100644 --- a/galata/test/jupyterlab/toc.test.ts +++ b/galata/test/jupyterlab/toc.test.ts @@ -23,7 +23,9 @@ test.describe('Table of Contents', () => { await page.sidebar.openTab('table-of-contents'); - await page.click('.jp-toc-numberingButton'); + await page + .getByRole('button', { name: 'Show heading number in the' }) + .click(); }); test.afterEach(async ({ page }) => { @@ -50,13 +52,11 @@ test.describe('Table of Contents', () => { const tocPanel = page.sidebar.getContentPanelLocator( (await page.sidebar.getTabPosition('table-of-contents')) ?? undefined ); - const numberingButton = tocPanel.locator( - 'jp-button[data-command="toc:display-numbering"]' - ); - await expect(numberingButton).toHaveCount(1); const imageName = 'toggle-numbered-list.png'; - await numberingButton.click(); + await page + .getByRole('button', { name: 'Show heading number in the' }) + .click(); expect(await tocPanel.screenshot()).toMatchSnapshot(imageName); }); @@ -68,26 +68,17 @@ test.describe('Table of Contents', () => { (await page.sidebar.getTabPosition('table-of-contents')) ?? undefined ); - await Promise.all([ - page.locator( - '.jp-TableOfContents-tree >> .jp-tocItem-active >> text="2. Multiple output types"' - ), - page - .locator('.jp-TableOfContents-tree >> text="2. Multiple output types"') - .click({ - button: 'right' - }) - ]); + await page.getByRole('treeitem', { name: 'Multiple output types' }).click({ + button: 'right' + }); const menu = await page.menu.getOpenMenuLocator(); await menu - ?.locator('text=Select and Run Cell(s) for this Heading') - ?.click(); + ?.getByRole('menuitem', { name: 'Select and Run Cell(s) for' }) + .click(); - await page - .locator('.jp-TableOfContents-tree >> text="2. HTML title"') - .waitFor(); + await page.getByRole('treeitem', { name: 'HTML title' }).waitFor(); expect(await tocPanel.screenshot()).toMatchSnapshot( 'notebook-output-headings.png' diff --git a/galata/test/jupyterlab/toc.test.ts-snapshots/notebook-output-headings-jupyterlab-linux.png b/galata/test/jupyterlab/toc.test.ts-snapshots/notebook-output-headings-jupyterlab-linux.png index 5482f90d510c..61c32240d9a2 100644 Binary files a/galata/test/jupyterlab/toc.test.ts-snapshots/notebook-output-headings-jupyterlab-linux.png and b/galata/test/jupyterlab/toc.test.ts-snapshots/notebook-output-headings-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/toc.test.ts-snapshots/toc-panel-jupyterlab-linux.png b/galata/test/jupyterlab/toc.test.ts-snapshots/toc-panel-jupyterlab-linux.png index 44e407d7abb8..33670267a745 100644 Binary files a/galata/test/jupyterlab/toc.test.ts-snapshots/toc-panel-jupyterlab-linux.png and b/galata/test/jupyterlab/toc.test.ts-snapshots/toc-panel-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/toc.test.ts-snapshots/toggle-numbered-list-jupyterlab-linux.png b/galata/test/jupyterlab/toc.test.ts-snapshots/toggle-numbered-list-jupyterlab-linux.png index 837c76e1ae24..b990b46edb11 100644 Binary files a/galata/test/jupyterlab/toc.test.ts-snapshots/toggle-numbered-list-jupyterlab-linux.png and b/galata/test/jupyterlab/toc.test.ts-snapshots/toggle-numbered-list-jupyterlab-linux.png differ diff --git a/packages/debugger/package.json b/packages/debugger/package.json index b6e426506019..9d17269352eb 100644 --- a/packages/debugger/package.json +++ b/packages/debugger/package.json @@ -50,7 +50,7 @@ "dependencies": { "@codemirror/state": "^6.4.1", "@codemirror/view": "^6.26.3", - "@jupyter/react-components": "^0.16.3", + "@jupyter/react-components": "^0.16.6", "@jupyter/ydoc": "^2.0.1", "@jupyterlab/application": "^4.3.0-alpha.2", "@jupyterlab/apputils": "^4.4.0-alpha.2", diff --git a/packages/debugger/src/panels/variables/index.ts b/packages/debugger/src/panels/variables/index.ts index 00df08a97d12..7461ccdbf0cb 100644 --- a/packages/debugger/src/panels/variables/index.ts +++ b/packages/debugger/src/panels/variables/index.ts @@ -73,14 +73,14 @@ export class Variables extends PanelWithToolbar { const treeViewButton = new ToolbarButton({ icon: treeViewIcon, - className: 'jp-TreeView', + className: 'jp-TreeView-Button', onClick: onViewChange, tooltip: trans.__('Tree View') }); const tableViewButton = new ToolbarButton({ icon: tableRowsIcon, - className: 'jp-TableView', + className: 'jp-TableView-Button', onClick: onViewChange, tooltip: trans.__('Table View') }); diff --git a/packages/debugger/src/panels/variables/tree.tsx b/packages/debugger/src/panels/variables/tree.tsx index c5fd29041f31..44c296b31aed 100644 --- a/packages/debugger/src/panels/variables/tree.tsx +++ b/packages/debugger/src/panels/variables/tree.tsx @@ -3,21 +3,21 @@ import { ITranslator, nullTranslator } from '@jupyterlab/translation'; -import { ISignal, Signal } from '@lumino/signaling'; - import { - caretDownEmptyIcon, + getTreeItemElement, ReactWidget, searchIcon } from '@jupyterlab/ui-components'; +import { Button, TreeItem, TreeView } from '@jupyter/react-components'; + import { ArrayExt } from '@lumino/algorithm'; import { CommandRegistry } from '@lumino/commands'; import { DebugProtocol } from '@vscode/debugprotocol'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { convertType } from '.'; @@ -27,8 +27,6 @@ import { IDebugger } from '../../tokens'; import { VariablesModel } from './model'; -const BUTTONS_CLASS = 'jp-DebuggerVariables-buttons'; - /** * The body for tree of variables. */ @@ -43,7 +41,6 @@ export class VariablesBodyTree extends ReactWidget { this._commands = options.commands; this._service = options.service; this._translator = options.translator; - this._hoverChanged = new Signal(this); const model = (this.model = options.model); model.changed.connect(this._updateScopes, this); @@ -61,9 +58,6 @@ export class VariablesBodyTree extends ReactWidget { const handleSelectVariable = (variable: IDebugger.IVariable) => { this.model.selectedVariable = variable; }; - const collapserIcon = ( - - ); if (scope?.name !== 'Globals') { this.addClass('jp-debuggerVariables-local'); @@ -73,25 +67,17 @@ export class VariablesBodyTree extends ReactWidget { return scope ? ( <> - { - this._hoverChanged.emit(data); - }} - collapserIcon={collapserIcon} - /> - + + + ) : (
@@ -134,152 +120,8 @@ export class VariablesBodyTree extends ReactWidget { private _filter = new Set(); private _service: IDebugger; private _translator: ITranslator | undefined; - private _hoverChanged: Signal; -} - -interface IHoverData { - /** - * The mouse target. - */ - target: (EventTarget & HTMLElement) | null; - /** - * The variable corresponding to node under cursor. - */ - variable: IDebugger.IVariable | null; } -interface ITreeButtonsProps { - /** - * The commands registry. - */ - commands: CommandRegistry; - /** - * The debugger service. - */ - service: IDebugger; - /** - * The application language translator - */ - translator?: ITranslator; - /** - * Callback on variable selection - */ - handleSelectVariable: (variable: IDebugger.IVariable) => void; - /** - * Signal to be emitted on mouse over event. - */ - hoverChanged: ISignal; -} - -/** - * The singleton buttons bar shown by the variables. - */ -const TreeButtons = (props: ITreeButtonsProps): JSX.Element => { - const { commands, service, translator, handleSelectVariable } = props; - const trans = (translator ?? nullTranslator).load('jupyterlab'); - - const [buttonsTop, setButtonsTop] = useState(0); - const [variable, setVariable] = useState(null); - - let stateRefreshLock = 0; - - // Empty dependency array is to only register once per lifetime. - const handleHover = useCallback((_: VariablesBodyTree, data: IHoverData) => { - const current = ++stateRefreshLock; - if (!data.variable) { - // Handle mouse leave. - if (current !== stateRefreshLock) { - return; - } - const target = data.target; - if ( - target && - // Note: Element, not HTMLElement to permit entering icon. - target instanceof Element && - target.closest(`.${BUTTONS_CLASS}`) - ) { - // Allow to enter the buttons. - return; - } - setVariable(null); - } else { - // Handle mouse over. - setVariable(data.variable); - requestAnimationFrame(() => { - if (current !== stateRefreshLock || !data.target) { - return; - } - setButtonsTop(data.target.offsetTop); - }); - } - }, []); - - useEffect(() => { - props.hoverChanged.connect(handleHover); - return () => { - props.hoverChanged.disconnect(handleHover); - }; - }, [handleHover]); - - return ( -
- -
- ); -}; - interface IVariablesBranchProps { /** * The commands registry. @@ -296,14 +138,6 @@ interface IVariablesBranchProps { * Callback on variable selection */ handleSelectVariable?: (variable: IDebugger.IVariable) => void; - /** - * Callback on mouseOver/mouseLeave event. - */ - onHoverChanged?: (data: IHoverData) => void; - /** - * Collapser icon component - */ - collapserIcon: JSX.Element; } /** @@ -315,16 +149,8 @@ interface IVariablesBranchProps { * @param props.filter Optional variable filter list. */ const VariablesBranch = (props: IVariablesBranchProps): JSX.Element => { - const { - commands, - data, - service, - filter, - translator, - handleSelectVariable, - onHoverChanged, - collapserIcon - } = props; + const { commands, data, service, filter, translator, handleSelectVariable } = + props; const [variables, setVariables] = useState(data); useEffect(() => { @@ -332,7 +158,7 @@ const VariablesBranch = (props: IVariablesBranchProps): JSX.Element => { }, [data]); return ( -
    + <> {variables .filter( variable => !(filter || new Set()).has(variable.evaluateName || '') @@ -348,12 +174,10 @@ const VariablesBranch = (props: IVariablesBranchProps): JSX.Element => { filter={filter} translator={translator} onSelect={handleSelectVariable} - onHoverChanged={onHoverChanged} - collapserIcon={collapserIcon} /> ); })} -
+ ); }; @@ -385,14 +209,6 @@ interface IVariableComponentProps { * Callback on selection */ onSelect?: (variable: IDebugger.IVariable) => void; - /** - * Callback on mouseOver/mouseLeave event. - */ - onHoverChanged?: (data: IHoverData) => void; - /** - * Collapser icon component - */ - collapserIcon: JSX.Element; } function _prepareDetail(variable: IDebugger.IVariable) { @@ -420,76 +236,156 @@ function _prepareDetail(variable: IDebugger.IVariable) { * @param props.filter Optional variable filter list. */ const VariableComponent = (props: IVariableComponentProps): JSX.Element => { - const { - commands, - data, - service, - filter, - translator, - onSelect, - onHoverChanged, - collapserIcon - } = props; + const { commands, data, service, filter, translator, onSelect } = props; const [variable] = useState(data); - const [expanded, setExpanded] = useState(); - const [variables, setVariables] = useState(); + const [showDetailsButton, setShowDetailsButton] = useState(false); + const [expanded, setExpanded] = useState(false); + const [variables, setVariables] = useState( + null + ); + const trans = useMemo( + () => (translator ?? nullTranslator).load('jupyterlab'), + [translator] + ); const onSelection = onSelect ?? (() => void 0); - const expandable = - variable.variablesReference !== 0 || variable.type === 'function'; + const expandable = useMemo( + () => variable.variablesReference !== 0 || variable.type === 'function', + [variable.variablesReference, variable.type] + ); - const onVariableClicked = async (e: React.MouseEvent): Promise => { - if (!expandable) { - return; + const details = useMemo(() => _prepareDetail(variable), [variable]); + + const hasMimeRenderer = useMemo( + () => + ![ + 'special variables', + 'protected variables', + 'function variables', + 'class variables' + ].includes(variable.name), + [variable.name] + ); + + const disableMimeRenderer = useMemo( + () => + !service.model.hasRichVariableRendering || + !commands.isEnabled(Debugger.CommandIDs.renderMimeVariable, { + name: variable.name, + frameID: service.model.callstack.frame?.id + } as any), + [ + service.model.hasRichVariableRendering, + variable.name, + service.model.callstack.frame?.id + ] + ); + + const fetchChildren = useCallback(async () => { + if (expandable && !variables) { + setVariables(await service.inspectVariable(variable.variablesReference)); } - e.stopPropagation(); - const variables = await service.inspectVariable( - variable.variablesReference - ); - setExpanded(!expanded); - setVariables(variables); - }; + }, [expandable, service, variable.variablesReference, variables]); + + const onVariableClicked = useCallback( + async (event: React.MouseEvent): Promise => { + const item = getTreeItemElement(event.target as HTMLElement); + if (event.currentTarget !== item) { + return; + } + + if (!expandable) { + return; + } + setExpanded(!expanded); + }, + [expandable, expanded] + ); + + const onSelectChange = useCallback( + (event: CustomEvent) => { + if (event.currentTarget === event.detail && event.detail.selected) { + onSelection(variable); + } + }, + [variable] + ); + + const renderVariable = useCallback(() => { + commands + .execute(Debugger.CommandIDs.renderMimeVariable, { + name: variable.name, + frameID: service.model.callstack.frame?.id + } as any) + .catch(reason => { + console.error(`Failed to render variable ${variable?.name}`, reason); + }); + }, [commands, variable.name, service.model.callstack.frame?.id]); + + const onContextMenu = useCallback( + (event: React.MouseEvent): void => { + const item = getTreeItemElement(event.target as HTMLElement); + if (event.currentTarget !== item) { + return; + } + + onSelection(variable); + }, + [variable] + ); return ( -
  • => onVariableClicked(e)} - onMouseDown={e => { - e.stopPropagation(); - onSelection(variable); - }} - onMouseOver={(event: React.MouseEvent) => { - if (onHoverChanged) { - onHoverChanged({ target: event.currentTarget, variable }); - event.stopPropagation(); + onContextMenu={onContextMenu} + onKeyDown={event => { + if (event.key == 'Enter') { + if (hasMimeRenderer && showDetailsButton) { + onSelection(variable); + renderVariable(); + } } }} - onMouseLeave={(event: React.MouseEvent) => { - if (onHoverChanged) { - onHoverChanged({ - target: event.relatedTarget as EventTarget & HTMLElement, - variable: null - }); - event.stopPropagation(); - } + onFocus={event => { + setShowDetailsButton(!event.defaultPrevented); + event.preventDefault(); + }} + onBlur={event => { + setShowDetailsButton(false); + }} + onMouseOver={(event: React.MouseEvent) => { + setShowDetailsButton(!event.defaultPrevented); + event.preventDefault(); + }} + onMouseLeave={(event: React.MouseEvent) => { + setShowDetailsButton(false); }} > - - { - // note: using React.cloneElement due to high typestyle cost - expandable ? React.cloneElement(collapserIcon) : null - } - {variable.name} - - {_prepareDetail(variable)} - - {expanded && variables && ( + {details && ( + {details} + )} + {hasMimeRenderer && showDetailsButton && ( + + )} + {variables ? ( { filter={filter} translator={translator} handleSelectVariable={onSelect} - onHoverChanged={onHoverChanged} - collapserIcon={collapserIcon} /> + ) : ( + /* Trick to ensure collapse button is displayed + when variables are not loaded yet */ + expandable && )} -
  • + ); }; diff --git a/packages/debugger/style/variables.css b/packages/debugger/style/variables.css index 7a6d7ebe35e2..3d1d1948b971 100644 --- a/packages/debugger/style/variables.css +++ b/packages/debugger/style/variables.css @@ -20,60 +20,15 @@ position: relative; } -.jp-DebuggerVariables-branch { - list-style: none; - margin: 0; - padding: 0; -} - -.jp-DebuggerVariables-body - .jp-DebuggerVariables-branch - .jp-DebuggerVariables-branch { - grid-area: nested; -} - -.jp-DebuggerVariables-body > .jp-DebuggerVariables-branch { - padding-top: 0.1em; -} - -.jp-DebuggerVariables-branch li { - padding: 3px 0; - cursor: pointer; - color: var(--jp-content-font-color1); - display: grid; - grid-template: - 'collapser name detail' - 'nested nested nested'; - grid-template-columns: max-content max-content 1fr; -} - -.jp-DebuggerVariables-branch li:not(:has(li:hover)):hover { - background: var(--jp-layout-color2); -} - -.jp-DebuggerVariables-collapser { - width: 16px; - grid-area: collapser; - transform: rotate(-90deg); - transition: transform 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); -} - -.jp-DebuggerVariables-collapser.jp-mod-expanded { - transform: rotate(0); -} - -.jp-DebuggerVariables-buttons { - position: absolute; - top: 0; - right: 8px; - margin-top: 1px; -} - .jp-DebuggerVariables-name { color: var(--jp-mirror-editor-attribute-color); grid-area: name; } +.jp-DebuggerVariables-name:last-of-type { + flex: 1 1 auto; +} + .jp-DebuggerVariables-name::after { content: ':'; margin-right: 5px; @@ -82,30 +37,10 @@ .jp-DebuggerVariables-detail { /* detail contains value for primitive types or name of the type otherwise */ color: var(--jp-mirror-editor-string-color); -} - -.jp-DebuggerVariables-renderVariable { - border: none; - background: none; - cursor: pointer; - transform-origin: center center; - transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1); -} - -.jp-DebuggerVariables-renderVariable:active { - transform: scale(1.35); -} - -.jp-DebuggerVariables-renderVariable:hover { - background-color: var(--jp-layout-color2); -} - -.jp-DebuggerVariables-renderVariable:disabled { - cursor: default; -} - -.jp-DebuggerVariables-branch li > .jp-DebuggerVariables-branch { - margin-left: 12px; + flex: 1 1 auto; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .jp-DebuggerVariables-grid { diff --git a/packages/lsp-extension/src/index.ts b/packages/lsp-extension/src/index.ts index c52a15be999c..e5afc7cfdd46 100644 --- a/packages/lsp-extension/src/index.ts +++ b/packages/lsp-extension/src/index.ts @@ -283,6 +283,7 @@ function addRunningSessionManager( let currentRunning: RunningLanguageServer[] = []; managers.add({ name: trans.__('Language servers'), + supportsMultipleViews: false, running: () => { const connections = new Set([...lsManager.connections.values()]); diff --git a/packages/running-extension/src/kernels.tsx b/packages/running-extension/src/kernels.tsx index ea85fd22efd3..448c2736a780 100644 --- a/packages/running-extension/src/kernels.tsx +++ b/packages/running-extension/src/kernels.tsx @@ -52,7 +52,8 @@ export async function addKernelRunningSessionManager( // Add the kernels pane to the running sidebar. managers.add({ name: trans.__('Kernels'), - running: () => { + supportsMultipleViews: true, + running: (options: { mode: 'tree' | 'list' }) => { const kernelsBySpec = new Map(); for (const kernel of kernels.running()) { @@ -69,7 +70,7 @@ export async function addKernelRunningSessionManager( ); } - return Array.from(kernelsBySpec.entries()).map( + const treeItems = Array.from(kernelsBySpec.entries()).map( ([spec, kernels]) => new Private.KernelSpecItem({ name: spec, @@ -78,6 +79,11 @@ export async function addKernelRunningSessionManager( trans }) ); + return options.mode === 'tree' + ? treeItems + : treeItems + .map(item => item.children.map(i => i.children ?? []).flat()) + .flat(); }, shutdownAll: () => kernels.shutdownAll(), refreshRunning: () => diff --git a/packages/running-extension/src/opentabs.ts b/packages/running-extension/src/opentabs.ts index 53751356c222..4597c21f9281 100644 --- a/packages/running-extension/src/opentabs.ts +++ b/packages/running-extension/src/opentabs.ts @@ -68,6 +68,7 @@ export function addOpenTabsSessionManager( managers.add({ name: trans.__('Open Tabs'), + supportsMultipleViews: false, running: () => { return Array.from(labShell.widgets('main')).map((widget: Widget) => { signaler.addWidget(widget); diff --git a/packages/running-extension/src/recents.ts b/packages/running-extension/src/recents.ts index 55422e3a6bda..1cb6dc6eae4c 100644 --- a/packages/running-extension/src/recents.ts +++ b/packages/running-extension/src/recents.ts @@ -29,6 +29,7 @@ export function addRecentlyClosedSessionManager( managers.add({ name: trans.__('Recently Closed'), + supportsMultipleViews: false, running: () => { return recentsManager.recentlyClosed.map((recent: RecentDocument) => { return new RecentItem(recent); diff --git a/packages/running-extension/tsconfig.json b/packages/running-extension/tsconfig.json index dc5352c57eb3..292a12327796 100644 --- a/packages/running-extension/tsconfig.json +++ b/packages/running-extension/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../tsconfigbase", "compilerOptions": { "outDir": "lib", - "rootDir": "src" + "rootDir": "src", + "lib": ["DOM", "DOM.Iterable", "ES2019", "ES2020.Intl"] }, "include": ["src/*"], "references": [ diff --git a/packages/running/package.json b/packages/running/package.json index a8dcd64bad02..9a9bc6ea773c 100644 --- a/packages/running/package.json +++ b/packages/running/package.json @@ -36,6 +36,7 @@ "watch": "tsc -b --watch" }, "dependencies": { + "@jupyter/react-components": "^0.16.6", "@jupyterlab/apputils": "^4.4.0-alpha.2", "@jupyterlab/statedb": "^4.3.0-alpha.2", "@jupyterlab/translation": "^4.3.0-alpha.2", diff --git a/packages/running/src/index.tsx b/packages/running/src/index.tsx index 59f588e1c011..2c6b498af739 100644 --- a/packages/running/src/index.tsx +++ b/packages/running/src/index.tsx @@ -5,19 +5,20 @@ * @module running */ +import { Button, TreeItem, TreeView } from '@jupyter/react-components'; import { Dialog, showDialog } from '@jupyterlab/apputils'; +import { IStateDB } from '@jupyterlab/statedb'; import { ITranslator, nullTranslator, TranslationBundle } from '@jupyterlab/translation'; import { - caretDownIcon, - caretRightIcon, closeIcon, collapseAllIcon, expandAllIcon, FilterBox, + getTreeItemElement, IScore, LabIcon, PanelWithToolbar, @@ -27,18 +28,16 @@ import { tableRowsIcon, Toolbar, ToolbarButton, - ToolbarButtonComponent, treeViewIcon, UseSignal } from '@jupyterlab/ui-components'; -import { IStateDB } from '@jupyterlab/statedb'; import { Token } from '@lumino/coreutils'; import { DisposableDelegate, IDisposable } from '@lumino/disposable'; import { ElementExt } from '@lumino/domutils'; import { Message } from '@lumino/messaging'; import { ISignal, Signal } from '@lumino/signaling'; import { Panel, Widget } from '@lumino/widgets'; -import React, { isValidElement, ReactNode } from 'react'; +import React, { isValidElement, ReactNode, useCallback } from 'react'; /** * The class name added to a running widget. @@ -60,11 +59,6 @@ const SECTION_CLASS = 'jp-RunningSessions-section'; */ const CONTAINER_CLASS = 'jp-RunningSessions-sectionContainer'; -/** - * The class name added to the running kernel sessions section list. - */ -const LIST_CLASS = 'jp-RunningSessions-sectionList'; - /** * The class name added to the running sessions items. */ @@ -90,11 +84,6 @@ const SHUTDOWN_BUTTON_CLASS = 'jp-RunningSessions-itemShutdown'; */ const SHUTDOWN_ALL_BUTTON_CLASS = 'jp-RunningSessions-shutdownAll'; -/** - * The class name added to a collapse/expand carets. - */ -const CARET_CLASS = 'jp-RunningSessions-caret'; - /** * The class name added to icons. */ @@ -213,15 +202,16 @@ function Item(props: { const trans = translator.load('jupyterlab'); // Handle shutdown requests. - let stopPropagation = false; const shutdownItemIcon = props.shutdownItemIcon || closeIcon; const shutdownLabel = (typeof props.shutdownLabel === 'function' ? props.shutdownLabel(runningItem) : props.shutdownLabel) ?? trans.__('Shut Down'); - const shutdown = () => { - stopPropagation = true; - runningItem.shutdown?.(); + const shutdown = (event: React.MouseEvent) => { + if (!event.defaultPrevented) { + event.preventDefault(); + runningItem.shutdown?.(); + } }; // Materialise getter to avoid triggering it repeatedly @@ -230,9 +220,18 @@ function Item(props: { // Manage collapsed state. Use the shutdown flag in lieu of `stopPropagation`. const [collapsed, collapse] = React.useState(false); const collapsible = !!children?.length; - const onClick = collapsible - ? () => !stopPropagation && collapse(!collapsed) - : undefined; + const onClick = useCallback( + (event: React.MouseEvent) => { + const item = getTreeItemElement(event.target as HTMLElement); + if (event.currentTarget !== item) { + return; + } + if (collapsible) { + collapse(!collapsed); + } + }, + [collapsible, collapsed] + ); // Listen to signal to collapse from outside props.collapseToggled.connect((_emitter, newCollapseState) => @@ -242,61 +241,50 @@ function Item(props: { if (runningItem.className) { classList.push(runningItem.className); } - if (props.child) { - classList.push('jp-mod-running-child'); - } - if (props.child && !children) { - classList.push('jp-mod-running-leaf'); - } return ( <> -
  • -
    + {icon ? ( + typeof icon === 'string' ? ( + + ) : ( + + ) + ) : undefined} + runningItem.open!())} > - {collapsible && - (collapsed ? ( - - ) : ( - - ))} - {icon ? ( - typeof icon === 'string' ? ( - - ) : ( - - ) - ) : undefined} - runningItem.open!())} + {runningItem.label()} + + {detail && {detail}} + {runningItem.shutdown && ( +
    - {collapsible && !collapsed && ( + + + )} + {children && ( )} -
  • + ); } @@ -326,7 +314,7 @@ function List(props: { .map(({ item }) => item) : props.runningItems; return ( -
      + <> {items.map((item, i) => ( ))} -
    + ); } @@ -415,6 +403,8 @@ class FilterWidget extends ReactWidget implements IFilterProvider { } class ListWidget extends ReactWidget { + private _mode: 'tree' | 'list'; + constructor( private _options: { manager: IRunningSessions.IManager; @@ -431,6 +421,20 @@ class ListWidget extends ReactWidget { } } + /** + * Whether the items are displayed as a tree view + * or a flat list. + */ + get mode(): 'tree' | 'list' { + return this._mode; + } + set mode(v: 'tree' | 'list') { + if (this._mode !== v) { + this._mode = v; + this._update.emit(); + } + } + dispose() { Signal.clearData(this); super.dispose(); @@ -452,18 +456,24 @@ class ListWidget extends ReactWidget { if (cached) { cached = false; } else { - options.runningItems = options.manager.running(); + options.runningItems = options.manager.running({ mode: this.mode }); + } + const classes = ['jp-TreeView']; + if (this.mode === 'list') { + classes.push('jp-mod-flat'); } return (
    - + + +
    ); }} @@ -513,6 +523,7 @@ class ListWidget extends ReactWidget { class Section extends PanelWithToolbar { constructor(options: Section.IOptions) { super(); + this._listView = (options.viewMode ?? 'tree') === 'list'; this._manager = options.manager; this._filterProvider = options.filterProvider; const translator = options.translator || nullTranslator; @@ -527,19 +538,27 @@ class Section extends PanelWithToolbar { } this._updateEmptyClass(); - let runningItems = options.manager.running(); + const runningItems = options.manager.running({ + mode: + options.manager.supportsMultipleViews && !this._listView + ? 'tree' + : 'list' + }); if (options.showToolbar !== false) { this._initializeToolbar(runningItems); } - this.addWidget( - new ListWidget({ - runningItems, - collapseToggled: this._collapseToggled, - ...options - }) - ); + this._listWidget = new ListWidget({ + runningItems, + collapseToggled: this._collapseToggled, + ...options + }); + this._listWidget.mode = + options.manager.supportsMultipleViews && !this._listView + ? 'tree' + : 'list'; + this.addWidget(this._listWidget); } /** @@ -553,8 +572,12 @@ class Section extends PanelWithToolbar { switchViewButton.pressed = newState; } this._collapseToggled.emit(false); - this.toggleClass(LIST_VIEW_CLASS, newState); + if (this._manager.supportsMultipleViews === undefined) { + this.toggleClass(LIST_VIEW_CLASS, newState); + } this._updateButtons(); + this._listWidget.mode = + this._manager.supportsMultipleViews && !this._listView ? 'tree' : 'list'; this._viewChanged.emit({ mode: newState ? 'list' : 'tree' }); } @@ -653,7 +676,14 @@ class Section extends PanelWithToolbar { private _updateEmptyClass(): void { if (this._filterProvider) { - const items = this._manager.running().filter(this._filterProvider.filter); + const items = this._manager + .running({ + mode: + this._manager.supportsMultipleViews && !this._listView + ? 'tree' + : 'list' + }) + .filter(this._filterProvider.filter); const empty = items.length === 0; if (empty) { this.node.classList.toggle('jp-mod-empty', true); @@ -671,10 +701,18 @@ class Section extends PanelWithToolbar { if (!this._buttons) { return; } - let runningItems = this._manager.running(); + let runningItems = this._manager.running({ + mode: + this._manager.supportsMultipleViews && !this._listView ? 'tree' : 'list' + }); const enabled = runningItems.length > 0; - const hasNesting = runningItems.filter(item => item.children).length !== 0; + const hasNesting = + // If the flag is undefined fallback to the old behavior + // @deprecated we should remove the fallback in the next iteration + this._manager.supportsMultipleViews === undefined + ? runningItems.filter(item => item.children).length !== 0 + : this._manager.supportsMultipleViews; const inTreeView = hasNesting && !this._buttons['switch-view'].pressed; this._buttons['switch-view'].node.style.display = hasNesting @@ -696,6 +734,7 @@ class Section extends PanelWithToolbar { } | null = null; private _manager: IRunningSessions.IManager; private _listView: boolean = false; + private _listWidget: ListWidget; private _filterProvider?: IFilterProvider; private _collapseToggled = new Signal(this); private _viewChanged = new Signal(this); @@ -714,6 +753,7 @@ namespace Section { showToolbar?: boolean; filterProvider?: IFilterProvider; translator?: ITranslator; + viewMode?: 'tree' | 'list'; } /** * Information about section view state. @@ -1082,7 +1122,8 @@ export class SearchableSessionsList extends Panel { manager, translator: this._translator, showToolbar: false, - filterProvider: this._filterWidget + filterProvider: this._filterWidget, + viewMode: 'list' }); // Do not use tree view in searchable list section.toggleListView(true); @@ -1117,8 +1158,13 @@ export namespace IRunningSessions { /** * List the running models. + * + * If the manager supports tree view, it should set the flag + * {@link supportsMultipleViews}. + * It must return nested item if mode is `tree`. + * Otherwise it must return a flat list. */ - running(): IRunningItem[]; + running(options: { mode: 'tree' | 'list' }): IRunningItem[]; /** * Force a refresh of the running models. @@ -1149,6 +1195,12 @@ export namespace IRunningSessions { * The icon to show for shutting down an individual item in this section. */ shutdownItemIcon?: LabIcon; + + /** + * Whether the manager supports tree view for its items + * or only a flat list. + */ + supportsMultipleViews?: boolean; } /** diff --git a/packages/running/style/base.css b/packages/running/style/base.css index 03e6d4b07b79..183cac78f609 100644 --- a/packages/running/style/base.css +++ b/packages/running/style/base.css @@ -3,14 +3,6 @@ * Distributed under the terms of the Modified BSD License. */ -/*----------------------------------------------------------------------------- -| Variables -|----------------------------------------------------------------------------*/ - -:root { - --jp-private-running-item-height: 24px; -} - .jp-RunningSessions { display: flex; flex-direction: column; @@ -45,46 +37,34 @@ padding-left: 14px; } -.jp-RunningSessions-item { - display: flex; - flex-direction: row; - color: var(--jp-ui-font-color1); - height: var(--jp-private-running-item-height); - line-height: var(--jp-private-running-item-height); - padding: 0 8px; -} - -.jp-RunningSessions-item:hover { - background-color: var(--jp-layout-color2); - cursor: pointer; -} - -.jp-mod-running-leaf { - /** Account for lack of collapser */ - margin-left: 16px; -} - -.jp-RunningSessions-sectionContainer > .jp-RunningSessions-sectionList { - padding-left: 0; -} - .jp-RunningSessions-viewButton[aria-pressed='true'] { box-shadow: none; } +/* @deprecated For backward compatibility when sessions manager does not specify supportsMultipleViews */ .jp-mod-running-list-view .jp-RunningSessions-sectionList { padding-left: 0; } +/* @deprecated For backward compatibility when sessions manager does not specify supportsMultipleViews */ .jp-mod-running-list-view .jp-mod-running-leaf { margin-left: 0; } -.jp-mod-running-list-view .jp-RunningSessions-item.jp-mod-kernel, -.jp-mod-running-list-view .jp-RunningSessions-item.jp-mod-kernelspec { +/* @deprecated For backward compatibility when sessions manager does not specify supportsMultipleViews */ +.jp-mod-running-list-view + .jp-RunningSessions-item.jp-mod-kernelspec::part(positioning-region), +.jp-mod-running-list-view + .jp-RunningSessions-item.jp-mod-kernel::part(positioning-region) { display: none; } +/* @deprecated For backward compatibility when sessions manager does not specify supportsMultipleViews */ +.jp-mod-running-list-view + .jp-RunningSessions-item.jp-mod-kernel-widget::part(content-region) { + margin-inline-start: -2.6em; +} + .jp-RunningSessions-item.jp-mod-kernelspec, .jp-RunningSessions-item.jp-mod-kernel { user-select: none; @@ -98,10 +78,6 @@ box-shadow: none; } -.jp-RunningSessions-icon { - margin: 0 4px; -} - .jp-RunningSessions-toolbar { min-width: max-content; } @@ -130,16 +106,11 @@ span.jp-RunningSessions-icon > svg { .jp-RunningSessions-itemLabel { font-size: var(--jp-ui-font-size1); flex: 1 1 55%; - padding: 0 4px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } -.jp-RunningSessions-itemLabel:focus { - background-color: var(--jp-layout-color2); -} - .jp-RunningSessions-itemDetail { font-size: var(--jp-ui-font-size1); flex: 1 1 45%; @@ -149,32 +120,10 @@ span.jp-RunningSessions-icon > svg { white-space: nowrap; } -.jp-RunningSessions-caret { - align-items: center; - display: flex; - padding-right: 4px; -} - -.jp-RunningSessions-caret > svg { - height: 16px; - width: 16px; -} - -.jp-RunningSessions-item .jp-RunningSessions-itemShutdown { - border-radius: 0; - margin: 0; -} - .jp-RunningSessions-item:not(:hover) .jp-RunningSessions-itemShutdown { visibility: hidden; } -.jp-RunningSessions-sectionList - .jp-RunningSessions-item - .jp-Button.jp-RunningSessions-itemShutdown:hover { - background: var(--jp-layout-color3); -} - .jp-RunningSessions-shutdownAll.jp-ToolbarButtonComponent { color: var(--jp-warn-color1); } @@ -193,6 +142,10 @@ span.jp-RunningSessions-icon > svg { outline-offset: -2px; } +.jp-RunningSessions-item.jp-mod-active.jp-TreeItem::part(positioning-region) { + background-color: transparent; +} + .jp-SearchableSessions-list > .jp-RunningSessions-section { min-height: auto; } diff --git a/packages/terminal-extension/src/index.ts b/packages/terminal-extension/src/index.ts index 130a5d022596..0cadb5aa6e68 100644 --- a/packages/terminal-extension/src/index.ts +++ b/packages/terminal-extension/src/index.ts @@ -314,6 +314,7 @@ function addRunningSessionManager( managers.add({ name: trans.__('Terminals'), + supportsMultipleViews: false, running: () => Array.from(manager.running()).map(model => new RunningTerminal(model)), shutdownAll: () => manager.shutdownAll(), diff --git a/packages/toc/package.json b/packages/toc/package.json index 40f5116f5679..07055f92ddea 100644 --- a/packages/toc/package.json +++ b/packages/toc/package.json @@ -40,6 +40,7 @@ "watch": "tsc -b --watch" }, "dependencies": { + "@jupyter/react-components": "^0.16.6", "@jupyterlab/apputils": "^4.4.0-alpha.2", "@jupyterlab/coreutils": "^6.3.0-alpha.2", "@jupyterlab/docregistry": "^4.3.0-alpha.2", diff --git a/packages/toc/src/tocitem.tsx b/packages/toc/src/tocitem.tsx index c069a4675613..bddbd00a4df1 100644 --- a/packages/toc/src/tocitem.tsx +++ b/packages/toc/src/tocitem.tsx @@ -1,9 +1,9 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { caretDownIcon, caretRightIcon } from '@jupyterlab/ui-components'; import * as React from 'react'; import { TableOfContents } from './tokens'; +import { TreeItem, TreeItemElement } from '@jupyter/react-components'; /** * Interface describing component properties. @@ -24,7 +24,7 @@ export interface ITableOfContentsItemsProps { onMouseDown: (heading: TableOfContents.IHeading) => void; /** - * Collapse event callback. + * Collapse/Expand event callback. */ onCollapse: (heading: TableOfContents.IHeading) => void; } @@ -43,34 +43,42 @@ export class TableOfContentsItem extends React.PureComponent< render(): JSX.Element | null { const { children, isActive, heading, onCollapse, onMouseDown } = this.props; + // Handling toggle of collapse and expand + const handleToggle = (event: CustomEvent) => { + // This will toggle the state and call the appropriate collapse or expand function + if ( + !event.defaultPrevented && + (event.target as TreeItemElement).expanded !== !heading.collapsed + ) { + event.preventDefault(); + onCollapse(heading); + } + }; + return ( -
  • -
    ) => { - // React only on deepest item - if (!event.defaultPrevented) { - event.preventDefault(); - onMouseDown(heading); - } - }} - > - + ) => { + // React only on deepest item + if (!event.defaultPrevented) { + event.preventDefault(); + onMouseDown(heading); + } + }} + onKeyUp={event => { + // React on key up because key down is used for tree view navigation + // and therefore key-down on Enter is default prevented to change the + // selection state + if (!event.defaultPrevented && event.key === 'Enter' && !isActive) { + event.preventDefault(); + onMouseDown(heading); + } + }} + > +
    - {children && !heading.collapsed &&
      {children}
    } -
  • + {children} + ); } } diff --git a/packages/toc/src/toctree.tsx b/packages/toc/src/toctree.tsx index 93abcb0b6110..3a9a9cbe1ed5 100644 --- a/packages/toc/src/toctree.tsx +++ b/packages/toc/src/toctree.tsx @@ -1,6 +1,7 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. +import { TreeView } from '@jupyter/react-components'; import * as React from 'react'; import { TableOfContentsItem } from './tocitem'; import { TableOfContents } from './tokens'; @@ -41,12 +42,12 @@ export class TableOfContentsTree extends React.PureComponent {this.buildTree()} - + ); } diff --git a/packages/toc/style/base.css b/packages/toc/style/base.css index ec32191be179..5280649c53f3 100644 --- a/packages/toc/style/base.css +++ b/packages/toc/style/base.css @@ -7,10 +7,6 @@ | Table of Contents |----------------------------------------------------------------------------*/ -:root { - --jp-private-toc-active-width: 4px; -} - .jp-TableOfContents { display: flex; flex-direction: column; @@ -41,19 +37,7 @@ margin: 4px; } -.jp-TableOfContents ol { - list-style-type: none; -} - -/* stylelint-disable-next-line selector-max-type */ -.jp-TableOfContents li > ol { - /* Align left border with triangle icon center */ - padding-left: 11px; -} - .jp-TableOfContents-content { - /* left margin for the active heading indicator */ - margin: 0 0 0 var(--jp-private-toc-active-width); padding: 0; background-color: var(--jp-layout-color1); } @@ -68,10 +52,7 @@ .jp-tocItem-heading { display: flex; cursor: pointer; -} - -.jp-tocItem-heading:hover { - background-color: var(--jp-layout-color2); + width: 100%; } .jp-tocItem-content { @@ -81,37 +62,3 @@ text-overflow: ellipsis; overflow-x: hidden; } - -.jp-tocItem-collapser { - height: 20px; - margin: 2px 2px 0; - padding: 0; - background: none; - border: none; - cursor: pointer; -} - -.jp-tocItem-collapser:hover { - background-color: var(--jp-layout-color3); -} - -/* Active heading indicator */ - -.jp-tocItem-heading::before { - content: ' '; - background: transparent; - width: var(--jp-private-toc-active-width); - height: 24px; - position: absolute; - left: 0; - border-radius: var(--jp-border-radius); -} - -.jp-tocItem-heading.jp-tocItem-active::before { - background-color: var(--jp-brand-color1); -} - -.jp-tocItem-heading:hover.jp-tocItem-active::before { - background: var(--jp-brand-color0); - opacity: 1; -} diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index 1b95bfc35def..dcad337d3de9 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -40,8 +40,8 @@ "watch": "tsc -b --watch" }, "dependencies": { - "@jupyter/react-components": "^0.16.3", - "@jupyter/web-components": "^0.16.3", + "@jupyter/react-components": "^0.16.6", + "@jupyter/web-components": "^0.16.6", "@jupyterlab/coreutils": "^6.3.0-alpha.2", "@jupyterlab/observables": "^5.3.0-alpha.2", "@jupyterlab/rendermime-interfaces": "^3.11.0-alpha.2", diff --git a/packages/ui-components/src/components/toolbar.tsx b/packages/ui-components/src/components/toolbar.tsx index 9a75fe331ada..47bd3f7d8a9c 100644 --- a/packages/ui-components/src/components/toolbar.tsx +++ b/packages/ui-components/src/components/toolbar.tsx @@ -863,7 +863,6 @@ export function ToolbarButtonComponent( onMouseDown={handleMouseDown} onKeyDown={handleKeyDown} title={title} - scale="xsmall" > {(props.icon || props.iconClass) && ( .jp-TreeItem::part(content-region) { + margin-inline-start: calc(var(--design-unit) * 2px); +} + +/* Styles for tree item */ +.jp-TreeItem::part(expand-collapse-button) { + color: var(--jp-inverse-layout-color3); +} + +/* Tune hover for stealth buttons otherwise they won't stand out */ +.jp-TreeItem jp-button[appearance='stealth']:hover { + background: var(--tree-item-expand-collapse-hover); +} + +.jp-TreeItem[selected] jp-button[appearance='stealth']:hover { + background: var(--tree-item-expand-collapse-selected-hover); +} diff --git a/packages/workspaces-extension/src/sidebar.ts b/packages/workspaces-extension/src/sidebar.ts index 7d6ce26ae139..000d2d43e246 100644 --- a/packages/workspaces-extension/src/sidebar.ts +++ b/packages/workspaces-extension/src/sidebar.ts @@ -79,6 +79,7 @@ export const workspacesSidebar: JupyterFrontEndPlugin = { } managers.add({ name: trans.__('Workspaces'), + supportsMultipleViews: false, running: () => { return model.workspaces.map((workspace: Workspace.IWorkspace) => { return new WorkspaceItem(workspace); diff --git a/yarn.lock b/yarn.lock index eb9b8db30b32..a9e95ce89099 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2052,25 +2052,25 @@ __metadata: languageName: node linkType: hard -"@jupyter/react-components@npm:^0.16.3": - version: 0.16.3 - resolution: "@jupyter/react-components@npm:0.16.3" +"@jupyter/react-components@npm:^0.16.6": + version: 0.16.6 + resolution: "@jupyter/react-components@npm:0.16.6" dependencies: - "@jupyter/web-components": ^0.16.3 + "@jupyter/web-components": ^0.16.6 react: ">=17.0.0 <19.0.0" - checksum: 4eef89daacce6173162d61b42d18d3d9311e6728329dd0181199f50cfaaab6d8aadce6c4d290597c5096ac0940b83fc45495d3f59706d67c217b2395ee0ab4a3 + checksum: 4619ae8a41a987aa7529d8625d1b2f7921f977091e710f47a6c60190ef49c15ac9849289ffcf0327d18ef2d0493959c7aac7010a205f9328569301a248b6ecfb languageName: node linkType: hard -"@jupyter/web-components@npm:^0.16.3": - version: 0.16.3 - resolution: "@jupyter/web-components@npm:0.16.3" +"@jupyter/web-components@npm:^0.16.6": + version: 0.16.6 + resolution: "@jupyter/web-components@npm:0.16.6" dependencies: "@microsoft/fast-colors": ^5.3.1 "@microsoft/fast-element": ^1.12.0 "@microsoft/fast-foundation": ^2.49.4 "@microsoft/fast-web-utilities": ^5.4.1 - checksum: f47b87864c0d89ca1d86e05b206e10d059cc9f7d5b9e295c2e965a1f16d3bb84f49354668c40f9c79f72050cc3aae60ed5a91e254381ec86e25b02bec0480eab + checksum: 41a4a2b5bb2177cbb562c858c8327a868eab9205679dbc8f99105b1580882a2d14a6e2bd9202a4acdec539c44cee8d5a5b64f7fea6a03baa3ad2905b95a6538c languageName: node linkType: hard @@ -2796,7 +2796,7 @@ __metadata: dependencies: "@codemirror/state": ^6.4.1 "@codemirror/view": ^6.26.3 - "@jupyter/react-components": ^0.16.3 + "@jupyter/react-components": ^0.16.6 "@jupyter/ydoc": ^2.0.1 "@jupyterlab/application": ^4.3.0-alpha.2 "@jupyterlab/apputils": ^4.4.0-alpha.2 @@ -3013,7 +3013,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyterlab/example-cell@workspace:examples/cell" dependencies: - "@jupyter/web-components": ^0.16.3 + "@jupyter/web-components": ^0.16.6 "@jupyter/ydoc": ^2.0.1 "@jupyterlab/application": ^4.3.0-alpha.2 "@jupyterlab/apputils": ^4.4.0-alpha.2 @@ -3042,7 +3042,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyterlab/example-console@workspace:examples/console" dependencies: - "@jupyter/web-components": ^0.16.3 + "@jupyter/web-components": ^0.16.6 "@jupyter/ydoc": ^2.0.1 "@jupyterlab/application": ^4.3.0-alpha.2 "@jupyterlab/codemirror": ^4.3.0-alpha.2 @@ -3158,7 +3158,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyterlab/example-filebrowser@workspace:examples/filebrowser" dependencies: - "@jupyter/web-components": ^0.16.3 + "@jupyter/web-components": ^0.16.6 "@jupyterlab/application": ^4.3.0-alpha.2 "@jupyterlab/apputils": ^4.4.0-alpha.2 "@jupyterlab/codemirror": ^4.3.0-alpha.2 @@ -3188,7 +3188,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyterlab/example-notebook@workspace:examples/notebook" dependencies: - "@jupyter/web-components": ^0.16.3 + "@jupyter/web-components": ^0.16.6 "@jupyter/ydoc": ^2.0.1 "@jupyterlab/application": ^4.3.0-alpha.2 "@jupyterlab/apputils": ^4.4.0-alpha.2 @@ -3276,7 +3276,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyterlab/example-terminal@workspace:examples/terminal" dependencies: - "@jupyter/web-components": ^0.16.3 + "@jupyter/web-components": ^0.16.6 "@jupyterlab/application": ^4.3.0-alpha.2 "@jupyterlab/coreutils": ^6.3.0-alpha.2 "@jupyterlab/services": ^7.3.0-alpha.2 @@ -4499,6 +4499,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyterlab/running@workspace:packages/running" dependencies: + "@jupyter/react-components": ^0.16.6 "@jupyterlab/apputils": ^4.4.0-alpha.2 "@jupyterlab/statedb": ^4.3.0-alpha.2 "@jupyterlab/translation": ^4.3.0-alpha.2 @@ -4850,6 +4851,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyterlab/toc@workspace:packages/toc" dependencies: + "@jupyter/react-components": ^0.16.6 "@jupyterlab/apputils": ^4.4.0-alpha.2 "@jupyterlab/coreutils": ^6.3.0-alpha.2 "@jupyterlab/docregistry": ^4.3.0-alpha.2 @@ -4957,8 +4959,8 @@ __metadata: version: 0.0.0-use.local resolution: "@jupyterlab/ui-components@workspace:packages/ui-components" dependencies: - "@jupyter/react-components": ^0.16.3 - "@jupyter/web-components": ^0.16.3 + "@jupyter/react-components": ^0.16.6 + "@jupyter/web-components": ^0.16.6 "@jupyterlab/coreutils": ^6.3.0-alpha.2 "@jupyterlab/observables": ^5.3.0-alpha.2 "@jupyterlab/rendermime-interfaces": ^3.11.0-alpha.2