diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3298a2ff..17893605 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,10 @@ jobs: - basics/signals - command-palette - commands + - context-menu - launcher + - log-console/custom-log-console + - log-console/log-messages - main-menu - react/react-widget - settings diff --git a/README.md b/README.md index f9af347b..8fe17028 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,9 @@ Start with the [Hello World](basics/hello-world) and then jump to the topic you - [State](state) - [React Widget](react/react-widget) - [Widgets](widget-tracker/widgets) +- [Log Messages](log-console/log-messages) +- [Custom Log Console](log-console/custom-log-console) +- [Context Menu](context-menu) - [Kernel Output](advanced/kernel-output) - [Kernel Messaging](advanced/kernel-messaging) - [Server Hello World](advanced/server-extension) @@ -139,6 +142,28 @@ Add a new Widget element to the main window. [![Custom Tab](widget-tracker/widgets/preview.png)](widget-tracker/widgets) +## Log Console + +### [Log Messages](log-messages) + +Send a log message to the log console. + +[![Log Messages](log-console/log-messages/preview.gif)](log-console/log-messages) + +### [Custom Log Console](custom-log-console) + +Create a new log console. + +[![Custom Log Console](log-console/custom-log-console/preview.gif)](log-console/custom-log-console) + +## Context Menu + +### [Context Menu](context-menu) + +Add a new button to an existent context menu. + +[![Context Menu](context-menu/preview.gif)](context-menu) + ## Advanced ### [Kernel Output](advanced/kernel-output) diff --git a/advanced/kernel-messaging/.eslintrc.js b/advanced/kernel-messaging/.eslintrc.js index 13c63682..652320e0 100644 --- a/advanced/kernel-messaging/.eslintrc.js +++ b/advanced/kernel-messaging/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/advanced/kernel-output/.eslintrc.js b/advanced/kernel-output/.eslintrc.js index 13c63682..652320e0 100644 --- a/advanced/kernel-output/.eslintrc.js +++ b/advanced/kernel-output/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/advanced/server-extension/.eslintrc.js b/advanced/server-extension/.eslintrc.js index 13c63682..652320e0 100644 --- a/advanced/server-extension/.eslintrc.js +++ b/advanced/server-extension/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/basics/datagrid/.eslintrc.js b/basics/datagrid/.eslintrc.js index 13c63682..652320e0 100644 --- a/basics/datagrid/.eslintrc.js +++ b/basics/datagrid/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/basics/hello-world/.eslintrc.js b/basics/hello-world/.eslintrc.js index 13c63682..652320e0 100644 --- a/basics/hello-world/.eslintrc.js +++ b/basics/hello-world/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/basics/signals/.eslintrc.js b/basics/signals/.eslintrc.js index 13c63682..652320e0 100644 --- a/basics/signals/.eslintrc.js +++ b/basics/signals/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/command-palette/.eslintrc.js b/command-palette/.eslintrc.js index 13c63682..652320e0 100644 --- a/command-palette/.eslintrc.js +++ b/command-palette/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/commands/.eslintrc.js b/commands/.eslintrc.js index 13c63682..652320e0 100644 --- a/commands/.eslintrc.js +++ b/commands/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/context-menu/.eslintignore b/context-menu/.eslintignore new file mode 100644 index 00000000..8d5c8605 --- /dev/null +++ b/context-menu/.eslintignore @@ -0,0 +1,4 @@ +node_modules +dist +coverage +**/*.d.ts diff --git a/context-menu/.eslintrc.js b/context-menu/.eslintrc.js new file mode 100644 index 00000000..652320e0 --- /dev/null +++ b/context-menu/.eslintrc.js @@ -0,0 +1,47 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:jsdoc/recommended', + 'plugin:prettier/recommended', + 'plugin:react/recommended' + ], + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + sourceType: 'module' + }, + plugins: ['@typescript-eslint', 'jsdoc'], + rules: { + '@typescript-eslint/interface-name-prefix': [ + 'error', + { prefixWithI: 'always' } + ], + '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/quotes': [ + 'error', + 'single', + { avoidEscape: true, allowTemplateLiterals: false } + ], + curly: ['error', 'all'], + eqeqeq: 'error', + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-property-type': 'off', + 'jsdoc/require-returns-type': 'off', + 'jsdoc/no-types': 'warn', + 'prefer-arrow-callback': 'error' + }, + settings: { + jsdoc: { + mode: 'typescript' + }, + react: { + version: 'detect' + } + } +}; diff --git a/context-menu/.gitignore b/context-menu/.gitignore new file mode 100644 index 00000000..18cace13 --- /dev/null +++ b/context-menu/.gitignore @@ -0,0 +1,6 @@ +*.bundle.* +lib/ +node_modules/ +*.egg-info/ +.ipynb_checkpoints +*.tsbuildinfo diff --git a/context-menu/README.md b/context-menu/README.md new file mode 100644 index 00000000..cd7d9775 --- /dev/null +++ b/context-menu/README.md @@ -0,0 +1,103 @@ +# Context Menu + +> Create a new button in a context menu. + +This is a basic example to show how to add a new entry to an existent context menu. + +![context menu example](preview.gif) + +In JupyterLab plugins can expose context menus to offer an easy way to execute commands and perform actions. In this example, you will learn how to add a new entry to the file browser context menu, and at the same time how to register a new file type. + +> It is strongly recommended to read [commands](https://github.com/jupyterlab/extension-examples/tree/master/commands) example before diving into this one. + +To implement this example you need to install the `@jupyterlab/filebrowser`, where you can find the interface `IFileBrowserFactory` necessary to require the file browser instance of JupyterLab. + +> This is not necessary to create a context menu. But it is a common case to be extended. + +First of all, you will start looking into the declaration of the extension: + + +```ts +// src/index.ts#L9-L14 + +const extension: JupyterFrontEndPlugin = { + id: 'context-menu', + autoStart: true, + requires: [IFileBrowserFactory], + optional: [], + activate: (app: JupyterFrontEnd, factory: IFileBrowserFactory) => { +``` + + +For this extension, you need to require `IFileBrowserFactory` to track the file browser item clicked by the user. + +The example shows you how to create a new file type and add the entry to the context menu only to this file type. The first step is optional, you can also add your button to an existing file type (or all of them!). + +To register a new file type, you need to call the `addFileType()` method of `docRegistry` property present in the `JupyterFrontEnd` object. This method requires an `IFileType` object with some properties to define your file type. The most important are: + +- `name`: the new file type. +- `extension`: the list of extensions. +- `fileFormat`: the file content format (_base64_, _json_ or _text_). +- `contentType`: the file type (_directory_, _notebook_ or _file_). +- `mimeType`: the content mime type. + + +```ts +// src/index.ts#L15-L23 + +app.docRegistry.addFileType({ + name: 'example', + icon: runIcon, + displayName: 'Example File', + extensions: ['.example'], + fileFormat: 'text', + contentType: 'file', + mimeTypes: ['text/plain'] +}); +``` + + +The next step is to define the command that will be executed when clicking on the context menu entry. If you want to access the item information, you need to use the `IFileBrowserFactory` object to obtain the file browser selected item. + + +```ts +// src/index.ts#L25-L38 + +app.commands.addCommand('jlab-examples/context-menu:open', { + label: 'Example', + caption: "Example context menu button for file browser's items.", + icon: buildIcon, + execute: () => { + const file = factory.tracker.currentWidget.selectedItems().next(); + + showDialog({ + title: file.name, + body: 'Path: ' + file.path, + buttons: [Dialog.okButton()] + }).catch(e => console.log(e)); + } +}); +``` + + +Finally, you can add the command to a context menu using the `addItem()` method present in the `contextMenu` property. This method requires an `IItemOptions` object with the following properties: + +- `command`: the command to execute. +- `selector`: the CSS classes of the element where you want to add the entry. +- `rank`: the position in the context menu + + +```ts +// src/index.ts#L40-L44 + +app.contextMenu.addItem({ + command: 'jlab-examples/context-menu:open', + selector: '.jp-DirListing-item[data-file-type="example"]', + rank: 0 +}); +``` + + +The `selector` can be any valid [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors). In this case, the first part is the CSS class that identifies the file browser items `.jp-DirListing-item` and the second part `[data-file-type="example"]` is a attribute value to be found on the item. You can omit the second part to add the button to every file type. + +You can find some of the CSS classes that identify different widgets in JupyterLab in the [developer documentation](https://jupyterlab.readthedocs.io/en/stable/developer/css.html#commonly-used-css-selectors). diff --git a/context-menu/package.json b/context-menu/package.json new file mode 100644 index 00000000..8294540c --- /dev/null +++ b/context-menu/package.json @@ -0,0 +1,59 @@ +{ + "name": "@jupyterlab-examples/context-menu", + "version": "0.1.0", + "description": "A minimal JupyterLab example to develop a context-menu.", + "keywords": [ + "jupyter", + "jupyterlab", + "jupyterlab-extension" + ], + "homepage": "https://github.com/jupyterlab/extension-examples", + "bugs": { + "url": "https://github.com/jupyterlab/extension-examples/issues" + }, + "license": "BSD-3-Clause", + "author": "Project Jupyter Contributors", + "files": [ + "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", + "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" + ], + "main": "lib/index.js", + "types": "lib/index.d.ts", + "style": "style/index.css", + "repository": { + "type": "git", + "url": "https://github.com/jupyterlab/extension-examples.git" + }, + "scripts": { + "build": "tsc", + "clean": "rimraf lib tsconfig.tsbuildinfo", + "link": "jupyter labextension link . --no-build", + "prepare": "jlpm run clean && jlpm run build", + "eslint": "eslint . --ext .ts,.tsx --fix", + "eslint:check": "eslint . --ext .ts,.tsx", + "watch": "tsc -w" + }, + "dependencies": { + "@jupyterlab/application": "^2.1.2", + "@jupyterlab/apputils": "^2.1.1", + "@jupyterlab/filebrowser": "^2.1.2", + "@jupyterlab/ui-components": "^2.1.1" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^2.21.0", + "@typescript-eslint/parser": "^2.21.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-jsdoc": "^22.0.0", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^7.18.3", + "rimraf": "^3.0.0", + "typescript": "~3.7.5" + }, + "sideEffects": [ + "style/*.css" + ], + "jupyterlab": { + "extension": true + } +} diff --git a/context-menu/preview.gif b/context-menu/preview.gif new file mode 100644 index 00000000..03c353f9 Binary files /dev/null and b/context-menu/preview.gif differ diff --git a/context-menu/src/index.ts b/context-menu/src/index.ts new file mode 100644 index 00000000..a197cff6 --- /dev/null +++ b/context-menu/src/index.ts @@ -0,0 +1,48 @@ +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; +import { IFileBrowserFactory } from '@jupyterlab/filebrowser'; +import { showDialog, Dialog } from '@jupyterlab/apputils'; +import { buildIcon, runIcon } from '@jupyterlab/ui-components'; + +const extension: JupyterFrontEndPlugin = { + id: 'context-menu', + autoStart: true, + requires: [IFileBrowserFactory], + optional: [], + activate: (app: JupyterFrontEnd, factory: IFileBrowserFactory) => { + app.docRegistry.addFileType({ + name: 'example', + icon: runIcon, + displayName: 'Example File', + extensions: ['.example'], + fileFormat: 'text', + contentType: 'file', + mimeTypes: ['text/plain'] + }); + + app.commands.addCommand('jlab-examples/context-menu:open', { + label: 'Example', + caption: "Example context menu button for file browser's items.", + icon: buildIcon, + execute: () => { + const file = factory.tracker.currentWidget.selectedItems().next(); + + showDialog({ + title: file.name, + body: 'Path: ' + file.path, + buttons: [Dialog.okButton()] + }).catch(e => console.log(e)); + } + }); + + app.contextMenu.addItem({ + command: 'jlab-examples/context-menu:open', + selector: '.jp-DirListing-item[data-file-type="example"]', + rank: 0 + }); + } +}; + +export default extension; diff --git a/widget-tracker/README.md b/context-menu/style/index.css similarity index 100% rename from widget-tracker/README.md rename to context-menu/style/index.css diff --git a/context-menu/tsconfig.json b/context-menu/tsconfig.json new file mode 100644 index 00000000..81139f54 --- /dev/null +++ b/context-menu/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "composite": true, + "declaration": true, + "esModuleInterop": true, + "incremental": true, + "jsx": "react", + "module": "esnext", + "moduleResolution": "node", + "noEmitOnError": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "preserveWatchOutput": true, + "resolveJsonModule": true, + "outDir": "lib", + "rootDir": "src", + "strict": true, + "strictNullChecks": false, + "target": "es2017", + "types": [] + }, + "include": ["src/*"] +} diff --git a/context-menu/untitled.example b/context-menu/untitled.example new file mode 100644 index 00000000..e69de29b diff --git a/launcher/.eslintrc.js b/launcher/.eslintrc.js index 13c63682..652320e0 100644 --- a/launcher/.eslintrc.js +++ b/launcher/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/lerna.json b/lerna.json index 55fcad4a..0ad5d162 100644 --- a/lerna.json +++ b/lerna.json @@ -2,17 +2,15 @@ "packages": [ "advanced/*", "basics/*", - "commands", "command-palette", - "keyboard-shortcuts/*", + "commands", + "context-menu", "launcher", - "left-right-areas/*", + "log-console/*", "main-menu", "react/*", - "state", - "status-bar/*", "settings", - "copy-shareable-link/*", + "state", "widget-tracker/*" ], "npmClient": "jlpm", diff --git a/log-console/README.md b/log-console/README.md new file mode 100644 index 00000000..e69de29b diff --git a/log-console/custom-log-console/.eslintignore b/log-console/custom-log-console/.eslintignore new file mode 100644 index 00000000..8d5c8605 --- /dev/null +++ b/log-console/custom-log-console/.eslintignore @@ -0,0 +1,4 @@ +node_modules +dist +coverage +**/*.d.ts diff --git a/log-console/custom-log-console/.eslintrc.js b/log-console/custom-log-console/.eslintrc.js new file mode 100644 index 00000000..652320e0 --- /dev/null +++ b/log-console/custom-log-console/.eslintrc.js @@ -0,0 +1,47 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:jsdoc/recommended', + 'plugin:prettier/recommended', + 'plugin:react/recommended' + ], + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + sourceType: 'module' + }, + plugins: ['@typescript-eslint', 'jsdoc'], + rules: { + '@typescript-eslint/interface-name-prefix': [ + 'error', + { prefixWithI: 'always' } + ], + '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/quotes': [ + 'error', + 'single', + { avoidEscape: true, allowTemplateLiterals: false } + ], + curly: ['error', 'all'], + eqeqeq: 'error', + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-property-type': 'off', + 'jsdoc/require-returns-type': 'off', + 'jsdoc/no-types': 'warn', + 'prefer-arrow-callback': 'error' + }, + settings: { + jsdoc: { + mode: 'typescript' + }, + react: { + version: 'detect' + } + } +}; diff --git a/log-console/custom-log-console/.gitignore b/log-console/custom-log-console/.gitignore new file mode 100644 index 00000000..18cace13 --- /dev/null +++ b/log-console/custom-log-console/.gitignore @@ -0,0 +1,6 @@ +*.bundle.* +lib/ +node_modules/ +*.egg-info/ +.ipynb_checkpoints +*.tsbuildinfo diff --git a/log-console/custom-log-console/README.md b/log-console/custom-log-console/README.md new file mode 100644 index 00000000..cf1c9217 --- /dev/null +++ b/log-console/custom-log-console/README.md @@ -0,0 +1,195 @@ +# Custom log console + +> Create a custom log console. + +This example shows how to create a log console to print log messages from a JupyterLab extension. + +![Custom log console example](preview.gif) + +The default log console extension in JupyterLab obtains log outputs from the kernel context of the current active notebook. So you can either: + +1. Obtain the current active notebook and send message to his `Logger` instance (see the [log message](https://github.com/jupyterlab/extension-examples/tree/master/log-console/log-messages) example). +2. Create your custom log console (covered in this example). + +> It is strongly recommended to read [commands](https://github.com/jupyterlab/extension-examples/tree/master/commands), [command-palette](https://github.com/jupyterlab/extension-examples/tree/master/command-palette), [main-menu](https://github.com/jupyterlab/extension-examples/tree/master/main-menu), [widget-tracker](https://github.com/jupyterlab/extension-examples/tree/master/widget-tracker) and [react-widget](https://github.com/jupyterlab/extension-examples/tree/master/react/react-widget) examples before diving into this one. + +To implement this log console you need to install the following packages: + +- `@jupyterlab/logconsole`: Where you can find the different UI components and message format. +- `@jupyterlab/rendermime`: Used to create renderers for various mime-types. +- `@jupyterlab/nbformat`: Only necessary if you want to use the notebook output format as the type of message. + +This example has two files. In the first one `index.ts`, you will find all the logic of the extension. And the second one `logLevelSwitcher.tsx` declares the React component used in the toolbar to switch between different log levels. + +First of all, you will start by looking into the declaration of the extension: + + +```ts +// src/index.ts#L28-L39 + +const extension: JupyterFrontEndPlugin = { + id: 'custom-log-console', + autoStart: true, + requires: [ICommandPalette, IRenderMimeRegistry, IMainMenu, ILayoutRestorer], + optional: [], + activate: ( + app: JupyterFrontEnd, + palette: ICommandPalette, + rendermime: IRenderMimeRegistry, + mainMenu: IMainMenu, + restorer: ILayoutRestorer + ) => { +``` + + +To create a new log console the `IRenderMimeRegistry` token is required, which is necessary as a default _rendermime_ in the `LoggerRegistry` to render the outputs. Moreover you will need `JupyterFrontEnd` to have access to some JupyterLab features, `ICommandPalette` to register some commands. `IMainMenu` allows to add some commands to the main menu. And `ILayoutRestorer` can restore the extension layout after reloading the web page. + +In the `activate` function, the first step is to declare `logConsolePanel` and `logConsoleWidget`. Therefore, you can pass their reference to the commands. The commands will be able to interact with the widget even when deleting and creating a new one after closing the tab, as well as tracking them before launching the widget. + + +```ts +// src/index.ts#L42-L43 + +let logConsolePanel: LogConsolePanel = null; +let logConsoleWidget: MainAreaWidget = null; +``` + + +The next step is to create a new widget when clicking on the button to open the custom log console. The function `createLogConsoleWidget` has all the logic necessary to initialize a new `LogConsoleWidget` and add it to the main area: + + +```ts +// src/index.ts#L76-L76 + +const createLogConsoleWidget = (): void => { +``` + + +To initialize a new `LogConsoleWidget` you have to create a `LogConsolePanel` to hold the log messages. `LogConsoleWidget` needs a `LoggerRegistry` to add new logs to the panel. `LoggerRegistry` requires two parameters: the default rendermime and the maximum length for the logs. + + +```ts +// src/index.ts#L77-L82 + +logConsolePanel = new LogConsolePanel( + new LoggerRegistry({ + defaultRendermime: rendermime, + maxLength: 1000 + }) +); +``` + + +The `source` property identifies where the message comes from and is necessary to initialize the `logger` object present on `LogConsolePanel`. This object will let you send log messages to the log console and change the log level. In the `logconsole-extension` available in core JupyterLab, `source` identifies the kernel output of the active notebook. So when changing the active notebook, the log console change his logs. You can use any string for the value, but it is recommended to use the name of your extension. + + +```ts +// src/index.ts#L84-L84 + +logConsolePanel.source = 'custom-log-console'; +``` + + +Now you are ready to initialize a new `MainAreaWidget` passing the `logConsolePanel` as the content. + + +```ts +// src/index.ts#L86-L88 + +logConsoleWidget = new MainAreaWidget({ + content: logConsolePanel +}); +``` + + +The last step of the function `createLogConsoleWidget` is to establish how to proceed after a dispose request. The `dispose` method is present in Lumino widgets to ensure that resources can be claimed by the garbage collector. In this case, this method is called when you close the tab for a `MainAreaWidget`. So you need to delete the `logConsolePanel` and `logConsoleWidget` instances to clean all logs and be ready to initialize a new widget when you decide to open the tab again. + + +```ts +// src/index.ts#L112-L116 + +logConsoleWidget.disposed.connect(() => { + logConsoleWidget = null; + logConsolePanel = null; + commands.notifyCommandChanged(); +}); +``` + + +To launch a new log console, you can add a new command. In this case, you can use the option `isToggled` to make the button checkable to open and close the log console with the same button. In the `execute` function there is an `if` statement to check when closing and deleting the `MainAreaWidget`, and when creating and opening a new one calling `createLogConsoleWidget`. + + + +```ts +// src/index.ts#L125-L136 + +commands.addCommand('jlab-examples/custom-log-console:open', { + label: 'Custom Log Console', + caption: 'Custom log console example.', + isToggled: () => logConsoleWidget !== null, + execute: () => { + if (logConsoleWidget) { + logConsoleWidget.dispose(); + } else { + createLogConsoleWidget(); + } + } +}); +``` + +Finally, you can send log messages calling `log` method present on the `logger` property of `logConsolePanel`. This method lets you send different types: + +- HTML message with `IHtmlLog`: + +```ts +// src/index.ts#L147-L153 + +const msg: IHtmlLog = { + type: 'html', + level: 'debug', + data: '
Hello world HTML!!
' +}; + +logConsolePanel?.logger?.log(msg); +``` + + +- Raw text message with `ITextLog` + + +```ts +// src/index.ts#L161-L167 + +const msg: ITextLog = { + type: 'text', + level: 'info', + data: 'Hello world text!!' +}; + +logConsolePanel?.logger?.log(msg); +``` + + + +- Cell output message with `IOutputLog` + + +```ts +// src/index.ts#L175-L188 + +const data: nbformat.IOutput = { + output_type: 'display_data', + data: { + 'text/plain': 'Hello world nbformat!!' + } +}; + +const msg: IOutputLog = { + type: 'output', + level: 'warning', + data +}; + +logConsolePanel?.logger?.log(msg); +``` + diff --git a/log-console/custom-log-console/package.json b/log-console/custom-log-console/package.json new file mode 100644 index 00000000..be07794a --- /dev/null +++ b/log-console/custom-log-console/package.json @@ -0,0 +1,65 @@ +{ + "name": "@jupyterlab-examples/custom-log-console", + "version": "0.1.0", + "description": "A minimal JupyterLab example to develop a custom log console.", + "keywords": [ + "jupyter", + "jupyterlab", + "jupyterlab-extension" + ], + "homepage": "https://github.com/jupyterlab/extension-examples", + "bugs": { + "url": "https://github.com/jupyterlab/extension-examples/issues" + }, + "license": "BSD-3-Clause", + "author": "Project Jupyter Contributors", + "files": [ + "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", + "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" + ], + "main": "lib/index.js", + "types": "lib/index.d.ts", + "style": "style/index.css", + "repository": { + "type": "git", + "url": "https://github.com/jupyterlab/extension-examples.git" + }, + "scripts": { + "build": "tsc", + "clean": "rimraf lib tsconfig.tsbuildinfo", + "link": "jupyter labextension link . --no-build", + "prepare": "jlpm run clean && jlpm run build", + "eslint": "eslint . --ext .ts,.tsx --fix", + "eslint:check": "eslint . --ext .ts,.tsx", + "watch": "tsc -w" + }, + "dependencies": { + "@jupyterlab/application": "^2.1.2", + "@jupyterlab/apputils": "^2.1.1", + "@jupyterlab/coreutils": "^4.1.0", + "@jupyterlab/logconsole": "^2.1.1", + "@jupyterlab/mainmenu": "^2.1.1", + "@jupyterlab/nbformat": "^2.1.0", + "@jupyterlab/rendermime": "^2.1.1", + "@jupyterlab/ui-components": "^2.1.1", + "@lumino/coreutils": "^1.5.0", + "@lumino/widgets": "^1.13.0" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^2.21.0", + "@typescript-eslint/parser": "^2.21.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-jsdoc": "^22.0.0", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^7.18.3", + "rimraf": "^3.0.0", + "typescript": "~3.7.5" + }, + "sideEffects": [ + "style/*.css" + ], + "jupyterlab": { + "extension": true + } +} diff --git a/log-console/custom-log-console/preview.gif b/log-console/custom-log-console/preview.gif new file mode 100644 index 00000000..b542c0c8 Binary files /dev/null and b/log-console/custom-log-console/preview.gif differ diff --git a/log-console/custom-log-console/src/index.ts b/log-console/custom-log-console/src/index.ts new file mode 100644 index 00000000..7a639737 --- /dev/null +++ b/log-console/custom-log-console/src/index.ts @@ -0,0 +1,214 @@ +import { + JupyterFrontEnd, + JupyterFrontEndPlugin, + ILayoutRestorer +} from '@jupyterlab/application'; +import { + ICommandPalette, + MainAreaWidget, + WidgetTracker, + CommandToolbarButton +} from '@jupyterlab/apputils'; +import { + LoggerRegistry, + LogConsolePanel, + IHtmlLog, + ITextLog, + IOutputLog +} from '@jupyterlab/logconsole'; +import { addIcon, clearIcon, listIcon } from '@jupyterlab/ui-components'; +import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; +import { IMainMenu } from '@jupyterlab/mainmenu'; +import { Menu } from '@lumino/widgets'; + +import * as nbformat from '@jupyterlab/nbformat'; + +import LogLevelSwitcher from './logLevelSwitcher'; + +const extension: JupyterFrontEndPlugin = { + id: 'custom-log-console', + autoStart: true, + requires: [ICommandPalette, IRenderMimeRegistry, IMainMenu, ILayoutRestorer], + optional: [], + activate: ( + app: JupyterFrontEnd, + palette: ICommandPalette, + rendermime: IRenderMimeRegistry, + mainMenu: IMainMenu, + restorer: ILayoutRestorer + ) => { + const { commands } = app; + + let logConsolePanel: LogConsolePanel = null; + let logConsoleWidget: MainAreaWidget = null; + + const tracker = new WidgetTracker>({ + namespace: 'example-custom-log-console' + }); + + restorer.restore(tracker, { + command: 'jlab-examples/custom-log-console:open', + name: () => 'example-custom-log-console' + }); + + commands.addCommand('jlab-examples/custom-log-console:checkpoint', { + execute: () => logConsolePanel?.logger?.checkpoint(), + icon: addIcon, + isEnabled: () => !!logConsolePanel && logConsolePanel.source !== null, + label: 'Add Checkpoint' + }); + commands.addCommand('jlab-examples/custom-log-console:clear', { + execute: () => logConsolePanel?.logger?.clear(), + icon: clearIcon, + isEnabled: () => !!logConsolePanel && logConsolePanel.source !== null, + label: 'Clear Log' + }); + commands.addCommand('jlab-examples/custom-log-console:level', { + execute: (args: any) => { + if (logConsolePanel?.logger) { + logConsolePanel.logger.level = args.level; + } + }, + isEnabled: () => !!logConsolePanel && logConsolePanel.source !== null, + label: args => `Set Log Level to ${args.level as string}` + }); + + const createLogConsoleWidget = (): void => { + logConsolePanel = new LogConsolePanel( + new LoggerRegistry({ + defaultRendermime: rendermime, + maxLength: 1000 + }) + ); + + logConsolePanel.source = 'custom-log-console'; + + logConsoleWidget = new MainAreaWidget({ + content: logConsolePanel + }); + logConsoleWidget.addClass('jp-LogConsole'); + logConsoleWidget.title.label = 'Custom Log console'; + logConsoleWidget.title.icon = listIcon; + + logConsoleWidget.toolbar.addItem( + 'checkpoint', + new CommandToolbarButton({ + commands: app.commands, + id: 'jlab-examples/custom-log-console:checkpoint' + }) + ); + logConsoleWidget.toolbar.addItem( + 'clear', + new CommandToolbarButton({ + commands: app.commands, + id: 'jlab-examples/custom-log-console:clear' + }) + ); + logConsoleWidget.toolbar.addItem( + 'level', + new LogLevelSwitcher(logConsoleWidget.content) + ); + + logConsoleWidget.disposed.connect(() => { + logConsoleWidget = null; + logConsolePanel = null; + commands.notifyCommandChanged(); + }); + + app.shell.add(logConsoleWidget, 'main', { mode: 'split-bottom' }); + tracker.add(logConsoleWidget); + + logConsoleWidget.update(); + commands.notifyCommandChanged(); + }; + + commands.addCommand('jlab-examples/custom-log-console:open', { + label: 'Custom Log Console', + caption: 'Custom log console example.', + isToggled: () => logConsoleWidget !== null, + execute: () => { + if (logConsoleWidget) { + logConsoleWidget.dispose(); + } else { + createLogConsoleWidget(); + } + } + }); + + palette.addItem({ + command: 'jlab-examples/custom-log-console:open', + category: 'Examples' + }); + + commands.addCommand('jlab-examples/custom-log-console:logHTMLMessage', { + label: 'HTML log message', + caption: 'Custom HTML log message example.', + execute: () => { + const msg: IHtmlLog = { + type: 'html', + level: 'debug', + data: '
Hello world HTML!!
' + }; + + logConsolePanel?.logger?.log(msg); + } + }); + + commands.addCommand('jlab-examples/custom-log-console:logTextMessage', { + label: 'Text log message', + caption: 'Custom text log message example.', + execute: () => { + const msg: ITextLog = { + type: 'text', + level: 'info', + data: 'Hello world text!!' + }; + + logConsolePanel?.logger?.log(msg); + } + }); + + commands.addCommand('jlab-examples/custom-log-console:logOutputMessage', { + label: 'Output log message', + caption: 'Custom notebook output log message example.', + execute: () => { + const data: nbformat.IOutput = { + output_type: 'display_data', + data: { + 'text/plain': 'Hello world nbformat!!' + } + }; + + const msg: IOutputLog = { + type: 'output', + level: 'warning', + data + }; + + logConsolePanel?.logger?.log(msg); + } + }); + + // Create a new menu + const menu: Menu = new Menu({ commands }); + menu.title.label = 'Log Console Example'; + mainMenu.addMenu(menu, { rank: 80 }); + + // Button to open custom log console + menu.addItem({ command: 'jlab-examples/custom-log-console:open' }); + menu.addItem({ type: 'separator' }); + + // Buttons for the different examples + menu.addItem({ + command: 'jlab-examples/custom-log-console:logHTMLMessage' + }); + menu.addItem({ + command: 'jlab-examples/custom-log-console:logTextMessage' + }); + menu.addItem({ + command: 'jlab-examples/custom-log-console:logOutputMessage' + }); + } +}; + +export default extension; diff --git a/log-console/custom-log-console/src/logLevelSwitcher.tsx b/log-console/custom-log-console/src/logLevelSwitcher.tsx new file mode 100644 index 00000000..42597be2 --- /dev/null +++ b/log-console/custom-log-console/src/logLevelSwitcher.tsx @@ -0,0 +1,107 @@ +import { ReactWidget } from '@jupyterlab/apputils'; +import { LogConsolePanel, LogLevel } from '@jupyterlab/logconsole'; +import { HTMLSelect } from '@jupyterlab/ui-components'; +import { IChangedArgs } from '@jupyterlab/coreutils'; +import { UUID } from '@lumino/coreutils'; + +import React from 'react'; + +/** + * A toolbar widget that switches log levels. + */ +export default class LogLevelSwitcher extends ReactWidget { + /** + * Construct a new cell type switcher. + * + * @param widget The log console panel + */ + constructor(widget: LogConsolePanel) { + super(); + this.addClass('jp-LogConsole-toolbarLogLevel'); + this._logConsole = widget; + this._logConsole.logger.level = 'debug'; + if (widget.source) { + this.update(); + } + widget.sourceChanged.connect(this._updateSource, this); + } + + private _updateSource( + sender: LogConsolePanel, + { oldValue, newValue }: IChangedArgs + ): void { + // Transfer stateChanged handler to new source logger + if (oldValue !== null) { + const logger = sender.loggerRegistry.getLogger(oldValue); + logger.stateChanged.disconnect(this.update, this); + } + if (newValue !== null) { + const logger = sender.loggerRegistry.getLogger(newValue); + logger.stateChanged.connect(this.update, this); + } + this.update(); + } + + /** + * Handle `change` events for the HTMLSelect component. + * + * @param event The HTML select event. + */ + handleChange = (event: React.ChangeEvent): void => { + if (this._logConsole.logger) { + this._logConsole.logger.level = event.target.value as LogLevel; + } + this.update(); + }; + + /** + * Handle `keydown` events for the HTMLSelect component. + * + * @param event The keyboard event. + */ + handleKeyDown = (event: React.KeyboardEvent): void => { + if (event.keyCode === 13) { + this._logConsole.activate(); + } + }; + + render(): JSX.Element { + const logger = this._logConsole.logger; + return ( + <> + + ({ label, value: label.toLowerCase() })) + } + /> + + ); + } + private _logConsole: LogConsolePanel; + private _id = `level-${UUID.uuid4()}`; +} diff --git a/log-console/custom-log-console/style/index.css b/log-console/custom-log-console/style/index.css new file mode 100644 index 00000000..e69de29b diff --git a/log-console/custom-log-console/tsconfig.json b/log-console/custom-log-console/tsconfig.json new file mode 100644 index 00000000..81139f54 --- /dev/null +++ b/log-console/custom-log-console/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "composite": true, + "declaration": true, + "esModuleInterop": true, + "incremental": true, + "jsx": "react", + "module": "esnext", + "moduleResolution": "node", + "noEmitOnError": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "preserveWatchOutput": true, + "resolveJsonModule": true, + "outDir": "lib", + "rootDir": "src", + "strict": true, + "strictNullChecks": false, + "target": "es2017", + "types": [] + }, + "include": ["src/*"] +} diff --git a/log-console/log-messages/.eslintignore b/log-console/log-messages/.eslintignore new file mode 100644 index 00000000..8d5c8605 --- /dev/null +++ b/log-console/log-messages/.eslintignore @@ -0,0 +1,4 @@ +node_modules +dist +coverage +**/*.d.ts diff --git a/log-console/log-messages/.eslintrc.js b/log-console/log-messages/.eslintrc.js new file mode 100644 index 00000000..652320e0 --- /dev/null +++ b/log-console/log-messages/.eslintrc.js @@ -0,0 +1,47 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:jsdoc/recommended', + 'plugin:prettier/recommended', + 'plugin:react/recommended' + ], + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + sourceType: 'module' + }, + plugins: ['@typescript-eslint', 'jsdoc'], + rules: { + '@typescript-eslint/interface-name-prefix': [ + 'error', + { prefixWithI: 'always' } + ], + '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/quotes': [ + 'error', + 'single', + { avoidEscape: true, allowTemplateLiterals: false } + ], + curly: ['error', 'all'], + eqeqeq: 'error', + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-property-type': 'off', + 'jsdoc/require-returns-type': 'off', + 'jsdoc/no-types': 'warn', + 'prefer-arrow-callback': 'error' + }, + settings: { + jsdoc: { + mode: 'typescript' + }, + react: { + version: 'detect' + } + } +}; diff --git a/log-console/log-messages/.gitignore b/log-console/log-messages/.gitignore new file mode 100644 index 00000000..18cace13 --- /dev/null +++ b/log-console/log-messages/.gitignore @@ -0,0 +1,6 @@ +*.bundle.* +lib/ +node_modules/ +*.egg-info/ +.ipynb_checkpoints +*.tsbuildinfo diff --git a/log-console/log-messages/README.md b/log-console/log-messages/README.md new file mode 100644 index 00000000..7a3d6524 --- /dev/null +++ b/log-console/log-messages/README.md @@ -0,0 +1,74 @@ +# Log message + +> Send a log message to the log console. + +This is a basic example to show how to send different types of log message to the log console from a JupyterLab extension. + +![log message example](preview.gif) + +The default log console extension in JupyterLab obtains log outputs from the kernel context of the current active notebook. That let the log console change the source input once a new notebook is opened. There are different ways to approach the problem: + +1. Obtain the current active notebook and send message to his `Logger` instance (covered in this example). +2. Create your custom log console (see the [custom log console](https://github.com/jupyterlab/extension-examples/tree/master/log-console/custom-log-console) example). + +> It is strongly recommended to read [main-menu](https://github.com/jupyterlab/extension-examples/tree/master/main-menu) example before diving into this one. + +To implement this example you need to install the following packages: + +- `@jupyterlab/logconsole`: Where you will find the classes and interfaces to work with the log console. +- `@jupyterlab/notebook`: Where you will find the different classes and interfaces to work with notebooks. +- `@jupyterlab/nbformat`: Only necessary if you want to use the notebook output format as the type of message. + +First of all, you will start looking into the declaration of the extension: + + +```ts +// src/index.ts#L10-L20 + +const extension: JupyterFrontEndPlugin = { + id: 'log-messages', + autoStart: true, + requires: [IMainMenu, ILoggerRegistry, INotebookTracker], + optional: [], + activate: ( + app: JupyterFrontEnd, + mainMenu: IMainMenu, + loggerRegistry: ILoggerRegistry, + nbtracker: INotebookTracker + ) => { +``` + + +For this extension, you need to require `ILoggerRegister` to search for the logger of the active notebook and `INotebookTracker` to obtain the active notebook. + +The first step is to obtain the logger of the active notebook. You can use `loggerRegistry.getLogger()` which needs a `source`. The `source` refers to the context's path of the active notebook. You can obtain it using the notebook tracker referenced as `nbtracker` here. + + +```ts +// src/index.ts#L27-L29 + +const logger = loggerRegistry.getLogger( + nbtracker.currentWidget?.context.path +); +``` + + +Finally, you can send log messages by calling the `log` method of the `logger` object. This method lets you send different types of logs like `IHtmlLog`, `ITextLog` and `IOutputLog`. + + +```ts +// src/index.ts#L32-L38 + +const msg: ITextLog = { + type: 'text', + level: 'info', + data: 'Hello world text!!' +}; + +logger?.log(msg); +``` + + +It is worth noting that with this approximation you will only be able to send messages to the log console if you have a notebook opened. If you have more than one notebook opened, the messages will be sent to the active notebook or the most recently focused notebook. It means that if you are changing from one notebook to another, every message will be sent to a different source and will be shown when the notebook gets the focus. + +Examples of other types of messages can be seen in the [custom log console](https://github.com/jupyterlab/extension-examples/tree/master/log-console/custom-log-console) example. diff --git a/log-console/log-messages/package.json b/log-console/log-messages/package.json new file mode 100644 index 00000000..8784c75c --- /dev/null +++ b/log-console/log-messages/package.json @@ -0,0 +1,66 @@ +{ + "name": "@jupyterlab-examples/log-messages", + "version": "0.1.0", + "description": "A minimal JupyterLab example to develop a custom log-messages.", + "keywords": [ + "jupyter", + "jupyterlab", + "jupyterlab-extension" + ], + "homepage": "https://github.com/jupyterlab/extension-examples", + "bugs": { + "url": "https://github.com/jupyterlab/extension-examples/issues" + }, + "license": "BSD-3-Clause", + "author": "Project Jupyter Contributors", + "files": [ + "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", + "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" + ], + "main": "lib/index.js", + "types": "lib/index.d.ts", + "style": "style/index.css", + "repository": { + "type": "git", + "url": "https://github.com/jupyterlab/extension-examples.git" + }, + "scripts": { + "build": "tsc", + "clean": "rimraf lib tsconfig.tsbuildinfo", + "link": "jupyter labextension link . --no-build", + "prepare": "jlpm run clean && jlpm run build", + "eslint": "eslint . --ext .ts,.tsx --fix", + "eslint:check": "eslint . --ext .ts,.tsx", + "watch": "tsc -w" + }, + "dependencies": { + "@jupyterlab/application": "^2.1.2", + "@jupyterlab/apputils": "^2.1.1", + "@jupyterlab/coreutils": "^4.1.0", + "@jupyterlab/logconsole": "^2.1.1", + "@jupyterlab/mainmenu": "^2.1.1", + "@jupyterlab/nbformat": "^2.1.0", + "@jupyterlab/notebook": "^2.1.2", + "@jupyterlab/rendermime": "^2.1.1", + "@jupyterlab/ui-components": "^2.1.1", + "@lumino/coreutils": "^1.5.0", + "@lumino/widgets": "^1.13.0" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^2.21.0", + "@typescript-eslint/parser": "^2.21.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-jsdoc": "^22.0.0", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^7.18.3", + "rimraf": "^3.0.0", + "typescript": "~3.7.5" + }, + "sideEffects": [ + "style/*.css" + ], + "jupyterlab": { + "extension": true + } +} diff --git a/log-console/log-messages/preview.gif b/log-console/log-messages/preview.gif new file mode 100644 index 00000000..1d721fa4 Binary files /dev/null and b/log-console/log-messages/preview.gif differ diff --git a/log-console/log-messages/src/index.ts b/log-console/log-messages/src/index.ts new file mode 100644 index 00000000..a02474d0 --- /dev/null +++ b/log-console/log-messages/src/index.ts @@ -0,0 +1,52 @@ +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; +import { ILoggerRegistry, ITextLog } from '@jupyterlab/logconsole'; +import { INotebookTracker } from '@jupyterlab/notebook'; +import { IMainMenu } from '@jupyterlab/mainmenu'; +import { Menu } from '@lumino/widgets'; + +const extension: JupyterFrontEndPlugin = { + id: 'log-messages', + autoStart: true, + requires: [IMainMenu, ILoggerRegistry, INotebookTracker], + optional: [], + activate: ( + app: JupyterFrontEnd, + mainMenu: IMainMenu, + loggerRegistry: ILoggerRegistry, + nbtracker: INotebookTracker + ) => { + const { commands } = app; + + commands.addCommand('jlab-examples/log-messages:logTextMessage', { + label: 'Text log message', + caption: 'Custom text log message example.', + execute: () => { + const logger = loggerRegistry.getLogger( + nbtracker.currentWidget?.context.path + ); + console.log(logger); + + const msg: ITextLog = { + type: 'text', + level: 'info', + data: 'Hello world text!!' + }; + + logger?.log(msg); + } + }); + + // Create a new menu + const menu: Menu = new Menu({ commands }); + menu.title.label = 'Log Messages Example'; + console.log(mainMenu); + mainMenu.addMenu(menu, { rank: 80 }); + + menu.addItem({ command: 'jlab-examples/log-messages:logTextMessage' }); + } +}; + +export default extension; diff --git a/log-console/log-messages/style/index.css b/log-console/log-messages/style/index.css new file mode 100644 index 00000000..e69de29b diff --git a/log-console/log-messages/tsconfig.json b/log-console/log-messages/tsconfig.json new file mode 100644 index 00000000..81139f54 --- /dev/null +++ b/log-console/log-messages/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "composite": true, + "declaration": true, + "esModuleInterop": true, + "incremental": true, + "jsx": "react", + "module": "esnext", + "moduleResolution": "node", + "noEmitOnError": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "preserveWatchOutput": true, + "resolveJsonModule": true, + "outDir": "lib", + "rootDir": "src", + "strict": true, + "strictNullChecks": false, + "target": "es2017", + "types": [] + }, + "include": ["src/*"] +} diff --git a/main-menu/.eslintrc.js b/main-menu/.eslintrc.js index 13c63682..652320e0 100644 --- a/main-menu/.eslintrc.js +++ b/main-menu/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/package.json b/package.json index 40f85a5e..7a49518b 100644 --- a/package.json +++ b/package.json @@ -23,17 +23,15 @@ "packages": [ "advanced/*", "basics/*", - "commands", "command-palette", - "keyboard-shortcuts/*", + "commands", + "context-menu", "launcher", - "left-right-areas/*", + "log-console/*", "main-menu", "react/*", - "state", - "status-bar/*", "settings", - "copy-shareable-link/*", + "state", "widget-tracker/*" ] }, diff --git a/react/react-widget/.eslintrc.js b/react/react-widget/.eslintrc.js index 13c63682..652320e0 100644 --- a/react/react-widget/.eslintrc.js +++ b/react/react-widget/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/settings/.eslintrc.js b/settings/.eslintrc.js index 13c63682..652320e0 100644 --- a/settings/.eslintrc.js +++ b/settings/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/state/.eslintrc.js b/state/.eslintrc.js index 13c63682..652320e0 100644 --- a/state/.eslintrc.js +++ b/state/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [ diff --git a/widget-tracker/widgets/.eslintrc.js b/widget-tracker/widgets/.eslintrc.js index 13c63682..652320e0 100644 --- a/widget-tracker/widgets/.eslintrc.js +++ b/widget-tracker/widgets/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { ], '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/camelcase': 'warn', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/quotes': [