From 2cd00762a7eca6a2328bc38d6cd6e579f9e396b3 Mon Sep 17 00:00:00 2001 From: Nick Byrne Date: Thu, 19 Sep 2024 13:42:41 -0300 Subject: [PATCH 1/8] Added commands to open URL in new window, and to open deploy App in new window --- src/index.ts | 60 +++++++++++++++++++++++++++++++++++ style/base.css | 7 ++++ style/icons/rocket-symbol.svg | 3 ++ 3 files changed, 70 insertions(+) create mode 100644 style/icons/rocket-symbol.svg diff --git a/src/index.ts b/src/index.ts index ef1da62..5c2bfe7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import { PageConfig, URLExt } from '@jupyterlab/coreutils'; import { Widget } from '@lumino/widgets'; import { CommandRegistry } from '@lumino/commands'; import { nebariIcon } from './icons'; +import { DocumentWidget } from '@jupyterlab/docregistry'; class NebariLogo extends Widget { constructor(options: { paths: JupyterFrontEnd.IPaths }) { @@ -60,6 +61,14 @@ namespace CommandIDs { * but assumes all properties of the first enabled command. */ export const runFirstEnabled = 'nebari:run-first-enabled'; + /** + * Opens a URL in a new tab. + */ + export const openURL = 'nebari:open-url'; + /** + * Opens the URL to deploy the application with pre-populated fields + */ + export const deployApp = 'nebari:deploy-app'; } interface IOpenProxyArgs { @@ -69,6 +78,17 @@ interface IOpenProxyArgs { name?: string; } +interface IOpenURLArgs { + /** + * URL to open. + */ + url?: string; + /** + * Alias for the URL. + */ + alias?: string; +} + interface ICommandDescription extends Omit { /** @@ -194,6 +214,46 @@ const commandsPlugin: JupyterFrontEndPlugin = { return returnFirstEnabled(args, 'className') ?? ''; } }); + + const resolveURLAndAlias = (args: IOpenURLArgs = {}) => { + const { url = '/', alias } = args; + const resolvedAlias = + alias || (url === '/' ? 'JupyterHub' : 'user-configured URL'); + return { url, alias: resolvedAlias }; + }; + + app.commands.addCommand(CommandIDs.openURL, { + execute: async (args: IOpenURLArgs) => { + const { url } = resolveURLAndAlias(args); + try { + window.open(url, '_blank', 'noopener,noreferrer'); + } catch (error) { + console.warn('Error opening URL:', url, error); + return; + } + }, + label: (args: IOpenURLArgs = {}) => { + const { alias } = resolveURLAndAlias(args); + return `Open ${alias}`; + } + }); + + app.commands.addCommand(CommandIDs.deployApp, { + execute: async () => { + const currentWidget = app.shell.currentWidget; + const currentNotebookPath = + currentWidget && currentWidget instanceof DocumentWidget + ? currentWidget.context.path + : ''; + const deployUrl = `/services/japps/create-app?filepath=${encodeURIComponent(currentNotebookPath)}`; + + await app.commands.execute(CommandIDs.openURL, { + url: deployUrl + }); + }, + label: () => 'Deploy App', + iconClass: () => 'nebari-DeployAppIcon' + }); } }; diff --git a/style/base.css b/style/base.css index 51df235..200a5c1 100644 --- a/style/base.css +++ b/style/base.css @@ -17,3 +17,10 @@ outline-offset: -3px; outline-style: auto; } + +.nebari-DeployAppIcon { + background-image: url('../style/icons/rocket-symbol.svg'); + background-repeat: no-repeat; + width: 100%; + height: 100%; +} diff --git a/style/icons/rocket-symbol.svg b/style/icons/rocket-symbol.svg new file mode 100644 index 0000000..55bdb64 --- /dev/null +++ b/style/icons/rocket-symbol.svg @@ -0,0 +1,3 @@ + + + From 7e8918f090a0d492892a295b09589b33fc5dd4f5 Mon Sep 17 00:00:00 2001 From: Nick Byrne Date: Thu, 19 Sep 2024 16:57:05 -0300 Subject: [PATCH 2/8] Remove open URL command, move deploy app command to new plugin --- src/index.ts | 58 ++++++++++++++++------------------------------------ 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5c2bfe7..136535a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,10 +61,6 @@ namespace CommandIDs { * but assumes all properties of the first enabled command. */ export const runFirstEnabled = 'nebari:run-first-enabled'; - /** - * Opens a URL in a new tab. - */ - export const openURL = 'nebari:open-url'; /** * Opens the URL to deploy the application with pre-populated fields */ @@ -78,17 +74,6 @@ interface IOpenProxyArgs { name?: string; } -interface IOpenURLArgs { - /** - * URL to open. - */ - url?: string; - /** - * Alias for the URL. - */ - alias?: string; -} - interface ICommandDescription extends Omit { /** @@ -214,29 +199,23 @@ const commandsPlugin: JupyterFrontEndPlugin = { return returnFirstEnabled(args, 'className') ?? ''; } }); + } +}; - const resolveURLAndAlias = (args: IOpenURLArgs = {}) => { - const { url = '/', alias } = args; - const resolvedAlias = - alias || (url === '/' ? 'JupyterHub' : 'user-configured URL'); - return { url, alias: resolvedAlias }; - }; +const jhubAppsPlugin: JupyterFrontEndPlugin = { + id: 'jhub-apps-integration:commands', + description: 'Adds additional commands used by jhub apps.', + autoStart: true, + requires: [], + activate: async (app: JupyterFrontEnd) => { - app.commands.addCommand(CommandIDs.openURL, { - execute: async (args: IOpenURLArgs) => { - const { url } = resolveURLAndAlias(args); - try { - window.open(url, '_blank', 'noopener,noreferrer'); - } catch (error) { - console.warn('Error opening URL:', url, error); - return; - } - }, - label: (args: IOpenURLArgs = {}) => { - const { alias } = resolveURLAndAlias(args); - return `Open ${alias}`; + const openURL = (url: string) => { + try { + window.open(url, '_blank', 'noopener,noreferrer'); + } catch (error) { + console.warn('Error opening URL:', url, error); } - }); + } app.commands.addCommand(CommandIDs.deployApp, { execute: async () => { @@ -247,15 +226,14 @@ const commandsPlugin: JupyterFrontEndPlugin = { : ''; const deployUrl = `/services/japps/create-app?filepath=${encodeURIComponent(currentNotebookPath)}`; - await app.commands.execute(CommandIDs.openURL, { - url: deployUrl - }); + await openURL(deployUrl); + }, label: () => 'Deploy App', iconClass: () => 'nebari-DeployAppIcon' }); } -}; +}; const logoPlugin: JupyterFrontEndPlugin = { id: 'jupyterlab-nebari-mode:logo', @@ -272,6 +250,6 @@ const logoPlugin: JupyterFrontEndPlugin = { } }; -const plugins = [commandsPlugin, logoPlugin]; +const plugins = [commandsPlugin, logoPlugin, jhubAppsPlugin]; export default plugins; From e4dfa10611c00547150c619dc94cd499fe207541 Mon Sep 17 00:00:00 2001 From: Nick Byrne Date: Thu, 19 Sep 2024 17:18:45 -0300 Subject: [PATCH 3/8] Replace CSS with proper icon --- src/icons.ts | 7 ++++++- src/index.ts | 4 ++-- style/base.css | 6 ------ style/icons/{rocket-symbol.svg => deploy-app.svg} | 0 4 files changed, 8 insertions(+), 9 deletions(-) rename style/icons/{rocket-symbol.svg => deploy-app.svg} (100%) diff --git a/src/icons.ts b/src/icons.ts index 3388566..7b0b160 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -1,7 +1,12 @@ import { LabIcon } from '@jupyterlab/ui-components'; import nebariLogo from '../style/icons/nebari-logo.svg'; - +import deployApp from '../style/icons/deploy-app.svg'; export const nebariIcon = new LabIcon({ name: 'nebari:logo', svgstr: nebariLogo }); + +export const deployAppIcon = new LabIcon({ + name: 'jhub-apps:deploy-app', + svgstr: deployApp +}); diff --git a/src/index.ts b/src/index.ts index 136535a..4430298 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import { ServerConnection } from '@jupyterlab/services'; import { PageConfig, URLExt } from '@jupyterlab/coreutils'; import { Widget } from '@lumino/widgets'; import { CommandRegistry } from '@lumino/commands'; -import { nebariIcon } from './icons'; +import { nebariIcon, deployAppIcon } from './icons'; import { DocumentWidget } from '@jupyterlab/docregistry'; class NebariLogo extends Widget { @@ -230,7 +230,7 @@ const jhubAppsPlugin: JupyterFrontEndPlugin = { }, label: () => 'Deploy App', - iconClass: () => 'nebari-DeployAppIcon' + icon: deployAppIcon }); } }; diff --git a/style/base.css b/style/base.css index 200a5c1..645a7fb 100644 --- a/style/base.css +++ b/style/base.css @@ -18,9 +18,3 @@ outline-style: auto; } -.nebari-DeployAppIcon { - background-image: url('../style/icons/rocket-symbol.svg'); - background-repeat: no-repeat; - width: 100%; - height: 100%; -} diff --git a/style/icons/rocket-symbol.svg b/style/icons/deploy-app.svg similarity index 100% rename from style/icons/rocket-symbol.svg rename to style/icons/deploy-app.svg From d2dc03a786c804af015386f675a7f751e0b9b17e Mon Sep 17 00:00:00 2001 From: Nick Byrne Date: Thu, 19 Sep 2024 18:38:19 -0300 Subject: [PATCH 4/8] Add button to toolbar and context menu --- package.json | 6 +++-- schema/jhub-apps-integration.json | 34 +++++++++++++++++++++++ src/index.ts | 45 ++++++++++++++++--------------- 3 files changed, 61 insertions(+), 24 deletions(-) create mode 100644 schema/jhub-apps-integration.json diff --git a/package.json b/package.json index 08aec2e..f8fd3a7 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "files": [ "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}", - "src/**/*.{ts,tsx}" + "src/**/*.{ts,tsx}", + "schema/*.json" ], "main": "lib/index.js", "types": "lib/index.d.ts", @@ -99,7 +100,8 @@ "@jupyterlab/application-extension:logo" ], "extension": true, - "outputDir": "jupyterlab_nebari_mode/labextension" + "outputDir": "jupyterlab_nebari_mode/labextension", + "schemaDir": "schema" }, "eslintIgnore": [ "node_modules", diff --git a/schema/jhub-apps-integration.json b/schema/jhub-apps-integration.json new file mode 100644 index 0000000..a3aa59e --- /dev/null +++ b/schema/jhub-apps-integration.json @@ -0,0 +1,34 @@ +{ + "jupyter.lab.menus": { + "main": [ + { + "id": "jp-mainmenu-deploy-app", + "label": "Services", + "disabled": false, + "rank": 80, + "items": [ + { + "command": "jhub-apps:deploy-app" + } + ] + } + ], + "context": [ + { + "command": "jhub-apps:deploy-app", + "selector": ".jp-DirListing-item", + "rank": 3 + } + ] + }, + "jupyter.lab.toolbars": { + "Notebook": [ + { + "name": "deploy-app", + "command": "jhub-apps:deploy-app" + } + ] + }, + "additionalProperties": false, + "type": "object" +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 4430298..946e18c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,7 +64,7 @@ namespace CommandIDs { /** * Opens the URL to deploy the application with pre-populated fields */ - export const deployApp = 'nebari:deploy-app'; + export const deployApp = 'jhub-apps:deploy-app'; } interface IOpenProxyArgs { @@ -202,20 +202,34 @@ const commandsPlugin: JupyterFrontEndPlugin = { } }; +const logoPlugin: JupyterFrontEndPlugin = { + id: 'jupyterlab-nebari-mode:logo', + description: 'Sets the application logo.', + autoStart: true, + requires: [ILabShell, JupyterFrontEnd.IPaths], + activate: ( + app: JupyterFrontEnd, + shell: ILabShell, + paths: JupyterFrontEnd.IPaths + ) => { + const logo = new NebariLogo({ paths }); + shell.add(logo, 'top', { rank: 0 }); + } +}; + const jhubAppsPlugin: JupyterFrontEndPlugin = { - id: 'jhub-apps-integration:commands', - description: 'Adds additional commands used by jhub apps.', + id: 'jupyterlab-nebari-mode:jhub-apps-integration', + description: 'Deploys an application with (almost) one click.', autoStart: true, requires: [], activate: async (app: JupyterFrontEnd) => { - const openURL = (url: string) => { try { window.open(url, '_blank', 'noopener,noreferrer'); } catch (error) { console.warn('Error opening URL:', url, error); } - } + }; app.commands.addCommand(CommandIDs.deployApp, { execute: async () => { @@ -227,27 +241,14 @@ const jhubAppsPlugin: JupyterFrontEndPlugin = { const deployUrl = `/services/japps/create-app?filepath=${encodeURIComponent(currentNotebookPath)}`; await openURL(deployUrl); - }, label: () => 'Deploy App', - icon: deployAppIcon + icon: () => { + // Add logic to only display the icon if the command is not in the main menu + return deployAppIcon; + } }); } -}; - -const logoPlugin: JupyterFrontEndPlugin = { - id: 'jupyterlab-nebari-mode:logo', - description: 'Sets the application logo.', - autoStart: true, - requires: [ILabShell, JupyterFrontEnd.IPaths], - activate: ( - app: JupyterFrontEnd, - shell: ILabShell, - paths: JupyterFrontEnd.IPaths - ) => { - const logo = new NebariLogo({ paths }); - shell.add(logo, 'top', { rank: 0 }); - } }; const plugins = [commandsPlugin, logoPlugin, jhubAppsPlugin]; From 68a600271dba520776a5ff02da1a4407ffc3ab84 Mon Sep 17 00:00:00 2001 From: Nick Byrne Date: Thu, 19 Sep 2024 18:46:57 -0300 Subject: [PATCH 5/8] lint --- schema/jhub-apps-integration.json | 2 +- src/icons.ts | 1 + style/base.css | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schema/jhub-apps-integration.json b/schema/jhub-apps-integration.json index a3aa59e..5b5ce53 100644 --- a/schema/jhub-apps-integration.json +++ b/schema/jhub-apps-integration.json @@ -31,4 +31,4 @@ }, "additionalProperties": false, "type": "object" -} \ No newline at end of file +} diff --git a/src/icons.ts b/src/icons.ts index 7b0b160..a312370 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -1,6 +1,7 @@ import { LabIcon } from '@jupyterlab/ui-components'; import nebariLogo from '../style/icons/nebari-logo.svg'; import deployApp from '../style/icons/deploy-app.svg'; + export const nebariIcon = new LabIcon({ name: 'nebari:logo', svgstr: nebariLogo diff --git a/style/base.css b/style/base.css index 645a7fb..51df235 100644 --- a/style/base.css +++ b/style/base.css @@ -17,4 +17,3 @@ outline-offset: -3px; outline-style: auto; } - From a6fa6499af5f3ade347e0687d9c738f768752bb9 Mon Sep 17 00:00:00 2001 From: Nick Byrne Date: Fri, 20 Sep 2024 09:10:37 -0300 Subject: [PATCH 6/8] Restrict context menu to files --- schema/jhub-apps-integration.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/jhub-apps-integration.json b/schema/jhub-apps-integration.json index 5b5ce53..608910c 100644 --- a/schema/jhub-apps-integration.json +++ b/schema/jhub-apps-integration.json @@ -16,7 +16,7 @@ "context": [ { "command": "jhub-apps:deploy-app", - "selector": ".jp-DirListing-item", + "selector": ".jp-DirListing-item[data-file-type=\"text\"]", "rank": 3 } ] From 9db2b26f2ffe676bf9c4ccba9ce0c2923a49b03b Mon Sep 17 00:00:00 2001 From: Nick Byrne Date: Fri, 20 Sep 2024 11:46:33 -0300 Subject: [PATCH 7/8] Add origin argument to schema JSON to control icon display --- schema/jhub-apps-integration.json | 10 ++++++++-- src/index.ts | 28 ++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/schema/jhub-apps-integration.json b/schema/jhub-apps-integration.json index 608910c..af94fc1 100644 --- a/schema/jhub-apps-integration.json +++ b/schema/jhub-apps-integration.json @@ -8,7 +8,10 @@ "rank": 80, "items": [ { - "command": "jhub-apps:deploy-app" + "command": "jhub-apps:deploy-app", + "args": { + "origin": "main-menu" + } } ] } @@ -16,7 +19,10 @@ "context": [ { "command": "jhub-apps:deploy-app", - "selector": ".jp-DirListing-item[data-file-type=\"text\"]", + "args": { + "origin": "context-menu" + }, + "selector": ".jp-DirListing-item[data-isdir=\"false\"]", "rank": 3 } ] diff --git a/src/index.ts b/src/index.ts index 946e18c..58ac5f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -93,6 +93,10 @@ interface IRunFirstEnabledArgs { commands?: ICommandDescription[]; } +interface IDeployAppArgs { + origin?: string; +} + const commandsPlugin: JupyterFrontEndPlugin = { id: 'jupyterlab-nebari-mode:commands', description: 'Adds additional commands used by nebari.', @@ -223,11 +227,24 @@ const jhubAppsPlugin: JupyterFrontEndPlugin = { autoStart: true, requires: [], activate: async (app: JupyterFrontEnd) => { - const openURL = (url: string) => { + const openURL = async (url: string) => { try { window.open(url, '_blank', 'noopener,noreferrer'); } catch (error) { - console.warn('Error opening URL:', url, error); + console.warn(`Error opening ${url}: ${error}`); + } + }; + + const calculateIcon = (args: IDeployAppArgs) => { + switch (args.origin) { + case 'main-menu': + return undefined; + case 'context-menu': + return deployAppIcon; + case undefined: + return deployAppIcon; + default: + return deployAppIcon; } }; @@ -242,10 +259,9 @@ const jhubAppsPlugin: JupyterFrontEndPlugin = { await openURL(deployUrl); }, - label: () => 'Deploy App', - icon: () => { - // Add logic to only display the icon if the command is not in the main menu - return deployAppIcon; + label: 'Deploy App', + icon: args => { + return calculateIcon(args); } }); } From 186aea2a1cf627898d63eeea17eb778d0aa950aa Mon Sep 17 00:00:00 2001 From: Nick Byrne Date: Fri, 20 Sep 2024 11:51:13 -0300 Subject: [PATCH 8/8] lint json --- schema/jhub-apps-integration.json | 43 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/schema/jhub-apps-integration.json b/schema/jhub-apps-integration.json index af94fc1..4fd9425 100644 --- a/schema/jhub-apps-integration.json +++ b/schema/jhub-apps-integration.json @@ -1,33 +1,32 @@ { - "jupyter.lab.menus": { - "main": [ - { - "id": "jp-mainmenu-deploy-app", - "label": "Services", - "disabled": false, - "rank": 80, - "items": [ - { - "command": "jhub-apps:deploy-app", - "args": { - "origin": "main-menu" - } + "jupyter.lab.menus": { + "main": [ + { + "id": "jp-mainmenu-services", + "label": "Services", + "rank": 1000, + "items": [ + { + "command": "jhub-apps:deploy-app", + "args": { + "origin": "main-menu" } - ] - } - ], - "context": [ + } + ] + } + ], + "context": [ { "command": "jhub-apps:deploy-app", "args": { - "origin": "context-menu" - }, + "origin": "context-menu" + }, "selector": ".jp-DirListing-item[data-isdir=\"false\"]", "rank": 3 } ] }, - "jupyter.lab.toolbars": { + "jupyter.lab.toolbars": { "Notebook": [ { "name": "deploy-app", @@ -35,6 +34,6 @@ } ] }, - "additionalProperties": false, - "type": "object" + "additionalProperties": false, + "type": "object" }