From 46cffb2b6d8a6f5e7944e3527ad736267b8a0c64 Mon Sep 17 00:00:00 2001 From: yacchin1205 <968739+yacchin1205@users.noreply.github.com> Date: Sat, 11 May 2024 10:06:23 +0900 Subject: [PATCH 1/2] Improve stylesheet --- src/OutputTabsWidget.tsx | 1 + style/base.css | 56 ++++++++++++++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/OutputTabsWidget.tsx b/src/OutputTabsWidget.tsx index dd3d157..6fb19a0 100644 --- a/src/OutputTabsWidget.tsx +++ b/src/OutputTabsWidget.tsx @@ -17,6 +17,7 @@ import $ from 'jquery'; export class OutputTabsWidget extends ReactWidget { constructor(private cell: CodeCell) { super(); + this.addClass('multi-output-widget'); } render(): JSX.Element { diff --git a/style/base.css b/style/base.css index 6ace54a..f2f533a 100644 --- a/style/base.css +++ b/style/base.css @@ -4,6 +4,14 @@ https://jupyterlab.readthedocs.io/en/stable/developer/css.html */ +.multi-output-widget .jp-Cell-outputWrapper { + margin-top: 0 !important; +} + +.multi-output-wrapper { + padding: 0 !important; +} + .output_wrapper .out_prompt_bg { padding: 0px 0.4em; position: absolute; @@ -11,6 +19,15 @@ z-index: -1; } +.multi-output-tabs { + display: flex; +} + +.multi-output-tabs .out_prompt_bg { + width: calc(var(--jp-cell-prompt-width) + 8px); + flex: 0 0 calc(var(--jp-cell-prompt-width) + 8px); +} + .multi-output-tabs ul.nav-tabs { overflow-x: auto; overflow-y: hidden; @@ -21,6 +38,15 @@ flex: 1; } +.multi-output-widget .jp-Collapser { + display: none; +} + +.multi-output-widget .jp-OutputPrompt { + width: calc(var(--jp-cell-prompt-width) + 8px); + flex: 0 0 calc(var(--jp-cell-prompt-width) + 8px); +} + .multi-outputs-ui, .multi-outputs-diff-ui { text-align: left; @@ -59,23 +85,37 @@ font-size: 14px; } -.cell-status-success .jp-InputPrompt, -.cell-status-success .jp-OutputPrompt { +.jp-CodeCell.cell-status-success .jp-InputPrompt, +.jp-CodeCell.cell-status-success .jp-OutputPrompt, +.jp-CodeCell.cell-status-success:not(.jp-mod-active) .jp-Collapser, +.jp-CodeCell.cell-status-success .out_prompt_bg { background-color: #bcffbc; } -.cell-status-error .jp-InputPrompt, -.cell-status-error .jp-OutputPrompt { +.jp-CodeCell.cell-status-error .jp-InputPrompt, +.jp-CodeCell.cell-status-error .jp-OutputPrompt, +.jp-CodeCell.cell-status-error:not(.jp-mod-active) .jp-Collapser, +.jp-CodeCell.cell-status-error .out_prompt_bg { background-color: #ffc0cb; } -.cell-status-inqueue .jp-InputPrompt, -.cell-status-inqueue .jp-OutputPrompt { +.jp-CodeCell.cell-status-inqueue .jp-InputPrompt, +.jp-CodeCell.cell-status-inqueue .jp-OutputPrompt, +.jp-CodeCell.cell-status-inqueue:not(.jp-mod-active) .jp-Collapser, +.jp-CodeCell.cell-status-inqueue .out_prompt_bg { background-color: #e0ffff; } +.jp-Notebook .jp-CodeCell:not(.jp-mod-active) .jp-Collapser { + opacity: var(--jp-cell-prompt-not-active-opacity); +} + +.jp-Notebook .jp-CodeCell:not(.jp-mod-active) .out_prompt_bg { + opacity: var(--jp-cell-prompt-not-active-opacity); +} + .multi-output-container.ui-tabs { - border: none; + border: none !important; padding: 0px; } @@ -84,7 +124,7 @@ } .multi-output-tabs .ui-tabs-anchor { - font-size: 0.6em; + font-size: calc(var(--jp-code-font-size) * 0.8); } .multi-output-tabs .ui-tabs-nav { From 505c9cd0f8248f29f36cf1d788b9dc7964375774 Mon Sep 17 00:00:00 2001 From: yacchin1205 <968739+yacchin1205@users.noreply.github.com> Date: Sat, 11 May 2024 10:50:20 +0900 Subject: [PATCH 2/2] Support i18n --- .gitignore | 4 - .../ja_JP/LC_MESSAGES/lc_multi_outputs.json | 44 ++++++++++ .../ja_JP/LC_MESSAGES/lc_multi_outputs.mo | Bin 0 -> 1285 bytes .../ja_JP/LC_MESSAGES/lc_multi_outputs.po | 69 +++++++++++++++ lc_multi_outputs/locale/lc_multi_outputs.pot | 61 +++++++++++++ package.json | 7 +- pyproject.toml | 3 + src/MergeWidget.tsx | 16 ++-- src/OutputTabExtension.ts | 15 ++-- src/OutputTabsWidget.tsx | 17 +++- src/commands.ts | 47 ++++++---- src/components/SearchBar.tsx | 16 ++-- src/index.ts | 14 ++- src/showOutputAreaDiffDialog.ts | 8 +- src/toolbar.ts | 17 ++-- ui-tests/tests/lc_multi_outputs.spec.ts | 81 +++++++++++++----- yarn.lock | 1 + 17 files changed, 337 insertions(+), 83 deletions(-) create mode 100644 lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.json create mode 100644 lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.mo create mode 100644 lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.po create mode 100644 lc_multi_outputs/locale/lc_multi_outputs.pot diff --git a/.gitignore b/.gitignore index 3e03349..2945c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -71,10 +71,6 @@ coverage.xml .hypothesis/ .pytest_cache/ -# Translations -*.mo -*.pot - # Scrapy stuff: .scrapy diff --git a/lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.json b/lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.json new file mode 100644 index 0000000..92625fd --- /dev/null +++ b/lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.json @@ -0,0 +1,44 @@ +{ + "": { + "domain": "lc_multi_outputs", + "language": "ja-JP", + "plural_forms": "nplurals=1; plural=0;", + "version": "2.2.0.test20" + }, + "Close": [ + "\u9589\u3058\u308b" + ], + "Pin Outputs": [ + "\u51fa\u529b\u3092\u30d4\u30f3\u7559\u3081" + ], + "Pin Outputs In Below All": [ + "\u4ee5\u4e0b\u306e\u3059\u3079\u3066\u306e\u51fa\u529b\u3092\u30d4\u30f3\u7559\u3081" + ], + "Pin Outputs In Below Section": [ + "\u4ee5\u4e0b\u306e\u30bb\u30af\u30b7\u30e7\u30f3\u306e\u51fa\u529b\u3092\u30d4\u30f3\u7559\u3081" + ], + "Pin Outputs On Selection": [ + "\u9078\u629e\u3057\u305f\u51fa\u529b\u3092\u30d4\u30f3\u7559\u3081" + ], + "Remove Outputs In Below All": [ + "\u4ee5\u4e0b\u306e\u3059\u3079\u3066\u306e\u51fa\u529b\u3092\u524a\u9664" + ], + "Remove Outputs In Below Section": [ + "\u4ee5\u4e0b\u306e\u30bb\u30af\u30b7\u30e7\u30f3\u306e\u51fa\u529b\u3092\u524a\u9664" + ], + "Remove Outputs On Selection": [ + "\u9078\u629e\u3057\u305f\u51fa\u529b\u3092\u524a\u9664" + ], + "Remove Pinned Outputs Leaving One": [ + "1\u3064\u3092\u6b8b\u3057\u3066\u30d4\u30f3\u7559\u3081\u3055\u308c\u305f\u51fa\u529b\u3092\u524a\u9664" + ], + "Search": [ + "\u691c\u7d22" + ], + "Search: ": [ + "\u691c\u7d22: " + ], + "settings\u0004Max Pinned Outputs": [ + "\u30d4\u30f3\u7559\u3081\u3067\u304d\u308b\u6700\u5927\u51fa\u529b\u6570" + ] +} \ No newline at end of file diff --git a/lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.mo b/lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.mo new file mode 100644 index 0000000000000000000000000000000000000000..ddd99788aa6d4d9e3ee237a368cfa3ce9ec55155 GIT binary patch literal 1285 zcmaiz?@JSL9LF!RsD)30Kq%-d_^7yfn-lERaO#Z3&5dpKRIcsoJh|PCyYEaN)y*KB z^`K~#C6UoU~7vYbby_p>raAiof}{PTm;=dpFp?IXRrhO4hloM zX;UGl86=u#xjeJ?zDd|1gH$)Klc26QcO+_}L7Q<=rDz$8E zBojR~md)!-8#8N{A%3slD|s2UnP2k6Y0k8m7_l>2S{%%0Y%yudBoqmTm4V>!a6FVq zcw*6{IAl@Py^uJpGAa|lk2#E_9P(>S$E zdah~A6VvmSs*5A0m9=GJzz-M1?mKJ!y5jNJJZ>-I8XO%k(+z%| pO1yAq@0j(ZYI$k%%`>3}{0EImE8Mw@(^i*mR>~{24XdS>!e6iJ>dycG literal 0 HcmV?d00001 diff --git a/lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.po b/lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.po new file mode 100644 index 0000000..d2dda09 --- /dev/null +++ b/lc_multi_outputs/locale/ja_JP/LC_MESSAGES/lc_multi_outputs.po @@ -0,0 +1,69 @@ +# +msgid "" +msgstr "" +"Project-Id-Version: lc_multi_outputs 2.2.0.test20\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-05-11 11:43+0900\n" +"PO-Revision-Date: 2024-05-11 10:35+0900\n" +"Last-Translator: FULL NAME \n" +"Language-Team: ja_JP \n" +"Language: ja_JP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.14.0\n" + +msgctxt "settings" +msgid "Max Pinned Outputs" +msgstr "ピン留めできる最大出力数" + +msgctxt "schema" +msgid "LC Multi Outputs" +msgstr "" + +#: src/commands.ts:114 src/commands.ts:115 +msgid "Remove Outputs In Below Section" +msgstr "以下のセクションの出力を削除" + +#: src/commands.ts:134 src/commands.ts:135 +msgid "Pin Outputs In Below All" +msgstr "以下のすべての出力をピン留め" + +#: src/commands.ts:154 src/commands.ts:155 +msgid "Remove Outputs In Below All" +msgstr "以下のすべての出力を削除" + +#: src/commands.ts:54 src/commands.ts:55 +msgid "Pin Outputs On Selection" +msgstr "選択した出力をピン留め" + +#: src/commands.ts:74 src/commands.ts:75 +msgid "Remove Outputs On Selection" +msgstr "選択した出力を削除" + +#: src/commands.ts:94 src/commands.ts:95 +msgid "Pin Outputs In Below Section" +msgstr "以下のセクションの出力をピン留め" + +#: src/components/SearchBar.tsx:20 +msgid "Search: " +msgstr "検索: " + +#: src/components/SearchBar.tsx:22 +msgid "Search" +msgstr "検索" + +#: src/showOutputAreaDiffDialog.ts:39 +#: ui-tests/node_modules/@jupyterlab/docmanager/src/widgetmanager.ts:397 +#: ui-tests/node_modules/@jupyterlab/filebrowser/src/listing.ts:1415 +msgid "Close" +msgstr "閉じる" + +#: src/toolbar.ts:26 +msgid "Pin Outputs" +msgstr "出力をピン留め" + +#: src/toolbar.ts:41 +msgid "Remove Pinned Outputs Leaving One" +msgstr "1つを残してピン留めされた出力を削除" diff --git a/lc_multi_outputs/locale/lc_multi_outputs.pot b/lc_multi_outputs/locale/lc_multi_outputs.pot new file mode 100644 index 0000000..d163e20 --- /dev/null +++ b/lc_multi_outputs/locale/lc_multi_outputs.pot @@ -0,0 +1,61 @@ +# +msgid "" +msgstr "" +"Project-Id-Version: lc_multi_outputs 2.2.0.test20\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: /schema/plugin.json:/properties/maxPinnedOutputs/title +msgctxt "settings" +msgid "Max Pinned Outputs" +msgstr "" + +#: /schema/plugin.json:/title +msgctxt "schema" +msgid "LC Multi Outputs" +msgstr "" + +#: src/commands.ts:114 src/commands.ts:115 +msgid "Remove Outputs In Below Section" +msgstr "" + +#: src/commands.ts:134 src/commands.ts:135 +msgid "Pin Outputs In Below All" +msgstr "" + +#: src/commands.ts:154 src/commands.ts:155 +msgid "Remove Outputs In Below All" +msgstr "" + +#: src/commands.ts:54 src/commands.ts:55 +msgid "Pin Outputs On Selection" +msgstr "" + +#: src/commands.ts:74 src/commands.ts:75 +msgid "Remove Outputs On Selection" +msgstr "" + +#: src/commands.ts:94 src/commands.ts:95 +msgid "Pin Outputs In Below Section" +msgstr "" + +#: src/components/SearchBar.tsx:20 +msgid "Search: " +msgstr "" + +#: src/components/SearchBar.tsx:22 +msgid "Search" +msgstr "" + +#: src/showOutputAreaDiffDialog.ts:39 ui-tests/node_modules/@jupyterlab/docmanager/src/widgetmanager.ts:397 ui-tests/node_modules/@jupyterlab/filebrowser/src/listing.ts:1415 +msgid "Close" +msgstr "" + +#: src/toolbar.ts:26 +msgid "Pin Outputs" +msgstr "" + +#: src/toolbar.ts:41 +msgid "Remove Pinned Outputs Leaving One" +msgstr "" diff --git a/package.json b/package.json index 8ede540..c55e65c 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,11 @@ "stylelint:check": "stylelint --cache \"style/**/*.css\"", "watch": "run-p watch:src watch:labextension", "watch:labextension": "jupyter labextension watch .", - "watch:src": "tsc -w" + "watch:src": "tsc -w", + "prepare:translations": "pip install jupyterlab-translate && jlpm extract:translations && jlpm update:translations && jlpm compile:translations", + "extract:translations": "jupyterlab-translate extract . lc_multi_outputs", + "update:translations": "jupyterlab-translate update . lc_multi_outputs -l ja_JP", + "compile:translations": "jupyterlab-translate compile . lc_multi_outputs -l ja_JP" }, "dependencies": { "@jupyterlab/application": "^4.0.3", @@ -69,6 +73,7 @@ "@jupyterlab/outputarea": "^4.0.3", "@jupyterlab/rendermime": "^4.0.3", "@jupyterlab/settingregistry": "^4.0.3", + "@jupyterlab/translation": "^4.0.3", "@jupyterlab/ui-components": "^4.0.3", "@lumino/algorithm": "^2.0.0", "@lumino/commands": "^2.0.1", diff --git a/pyproject.toml b/pyproject.toml index 9d16341..49018c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,9 @@ dependencies = [ ] dynamic = ["version", "description", "authors", "urls", "keywords"] +[project.entry-points."jupyterlab.locale"] +lc_multi_outputs = "lc_multi_outputs" + [tool.hatch.version] source = "nodejs" diff --git a/src/MergeWidget.tsx b/src/MergeWidget.tsx index fbdeda8..dd7fc12 100644 --- a/src/MergeWidget.tsx +++ b/src/MergeWidget.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useRef } from 'react'; import { ReactWidget } from '@jupyterlab/apputils'; +import { TranslationBundle } from '@jupyterlab/translation'; import { Editor, MergeView } from 'codemirror'; import 'codemirror/lib/codemirror.css'; @@ -12,6 +13,7 @@ import { SearchBar } from './components/SearchBar'; export class MergeWidget extends ReactWidget { constructor( + private trans: TranslationBundle, private value: string, private orig: string ) { @@ -19,22 +21,22 @@ export class MergeWidget extends ReactWidget { } render(): JSX.Element { - return ; + return ; } } -export function MergePage({ - value, - orig -}: { +export type MergePageProps = { + trans: TranslationBundle; value: string; orig: string; -}): JSX.Element { +}; + +export function MergePage({ trans, value, orig }: MergePageProps): JSX.Element { const [search, setSearch] = React.useState(''); return (
- setSearch(data.keyword)} /> + setSearch(data.keyword)} />
); } diff --git a/src/OutputTabExtension.ts b/src/OutputTabExtension.ts index 979f132..459e457 100644 --- a/src/OutputTabExtension.ts +++ b/src/OutputTabExtension.ts @@ -3,6 +3,7 @@ import { INotebookModel, Notebook, NotebookPanel } from '@jupyterlab/notebook'; import { DocumentRegistry } from '@jupyterlab/docregistry'; import { IDisposable, DisposableDelegate } from '@lumino/disposable'; import { CodeCell, isCodeCellModel } from '@jupyterlab/cells'; +import { TranslationBundle } from '@jupyterlab/translation'; import { outputAreaWithPinButton, OutputTabsWidget } from './OutputTabsWidget'; import { pinOutput } from './pinOutput'; @@ -11,6 +12,8 @@ import { registerToolbarButtons } from './toolbar'; export class OutputTabExtension implements DocumentRegistry.IWidgetExtension { + constructor(private trans: TranslationBundle) {} + createNew( panel: NotebookPanel, context: DocumentRegistry.IContext @@ -22,7 +25,7 @@ export class OutputTabExtension if (cell && isCodeCellModel(cellModel)) { cell.inViewportChanged.connect((_, isAttached) => { if (isAttached) { - initCell(cell as CodeCell); + initCell(this.trans, cell as CodeCell); } }); } @@ -30,7 +33,7 @@ export class OutputTabExtension } }); - const toolbarButtons = registerToolbarButtons(panel); + const toolbarButtons = registerToolbarButtons(this.trans, panel); return new DisposableDelegate(() => { toolbarButtons.forEach(b => b.dispose()); @@ -38,8 +41,8 @@ export class OutputTabExtension } } -function initCell(cell: CodeCell) { - initOutputTabs(cell); +function initCell(trans: TranslationBundle, cell: CodeCell) { + initOutputTabs(trans, cell); // アウトプットの初期化 outputAreaWithPinButton(cell.outputArea, () => { pinOutput(cell); @@ -54,8 +57,8 @@ function initCell(cell: CodeCell) { }); } -function initOutputTabs(cell: CodeCell) { - Widget.attach(new OutputTabsWidget(cell), cell.node); +function initOutputTabs(trans: TranslationBundle, cell: CodeCell) { + Widget.attach(new OutputTabsWidget(trans, cell), cell.node); } function getCellByModelId(notebook: Notebook, cellModelId: string) { diff --git a/src/OutputTabsWidget.tsx b/src/OutputTabsWidget.tsx index 6fb19a0..79cda7b 100644 --- a/src/OutputTabsWidget.tsx +++ b/src/OutputTabsWidget.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { ReactWidget, UseSignal } from '@jupyterlab/apputils'; import { OutputTabs } from './components/OutputTabs'; import { CodeCell } from '@jupyterlab/cells'; +import { TranslationBundle } from '@jupyterlab/translation'; import { getOutputTabIndex, selectCurrentOutputTab, @@ -15,7 +16,10 @@ import { showOutputAreaDiffDialog } from './showOutputAreaDiffDialog'; import $ from 'jquery'; export class OutputTabsWidget extends ReactWidget { - constructor(private cell: CodeCell) { + constructor( + private trans: TranslationBundle, + private cell: CodeCell + ) { super(); this.addClass('multi-output-widget'); } @@ -24,7 +28,7 @@ export class OutputTabsWidget extends ReactWidget { return ( {(_, args) => { - const tabs = createTabs(this.cell); + const tabs = createTabs(this.trans, this.cell); if (args && args.key === 'scrolled') { // スクロールの変更の場合は最初のタブを選択状態にする selectCurrentOutputTab(this.cell.model); @@ -133,7 +137,7 @@ function outputAreaWithMergeButton( return outputArea; } -function createTabs(cell: CodeCell) { +function createTabs(trans: TranslationBundle, cell: CodeCell) { return [ { name: 'output-current', @@ -153,7 +157,12 @@ function createTabs(cell: CodeCell) { name: `output-${output.execution_count}`, label: `Out [${output.execution_count}]`, outputNode: outputAreaWithMergeButton(outputArea, () => { - showOutputAreaDiffDialog(cell, output.execution_count, outputArea); + showOutputAreaDiffDialog( + trans, + cell, + output.execution_count, + outputArea + ); }).node, onClose: () => { removePinnedOutput(cell, output.execution_count); diff --git a/src/commands.ts b/src/commands.ts index 2ccad04..40f564d 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -2,6 +2,7 @@ import { extensionId } from './plugin'; import { ICommandPalette } from '@jupyterlab/apputils'; import { INotebookTracker } from '@jupyterlab/notebook'; import { JupyterFrontEnd } from '@jupyterlab/application'; +import { TranslationBundle } from '@jupyterlab/translation'; import { CommandRegistry } from '@lumino/commands'; import { pinOutputsInBelowSection, @@ -14,12 +15,13 @@ import { export function registerCommands( app: JupyterFrontEnd, + trans: TranslationBundle, notebooks: INotebookTracker, commandPalette: ICommandPalette ): void { const category = 'Notebook'; - createCommands(app, notebooks).forEach(([id, options]) => { + createCommands(app, trans, notebooks).forEach(([id, options]) => { const command = extensionId + ':' + id; app.commands.addCommand(command, options); commandPalette.addItem({ command, category }); @@ -28,27 +30,29 @@ export function registerCommands( function createCommands( app: JupyterFrontEnd, + trans: TranslationBundle, notebooks: INotebookTracker ): [string, CommandRegistry.ICommandOptions][] { return [ - createPinOutputOnSelectionCommand(app, notebooks), - createRemoveOutputOnSelectionCommand(app, notebooks), - createPinOutputInBelowSectionCommand(app, notebooks), - createRemoveOutputInBelowSectionCommand(app, notebooks), - createPinOutputInBelowAllCommand(app, notebooks), - createRemoveOutputInBelowAllCommand(app, notebooks) + createPinOutputOnSelectionCommand(app, trans, notebooks), + createRemoveOutputOnSelectionCommand(app, trans, notebooks), + createPinOutputInBelowSectionCommand(app, trans, notebooks), + createRemoveOutputInBelowSectionCommand(app, trans, notebooks), + createPinOutputInBelowAllCommand(app, trans, notebooks), + createRemoveOutputInBelowAllCommand(app, trans, notebooks) ]; } function createPinOutputOnSelectionCommand( app: JupyterFrontEnd, + trans: TranslationBundle, notebooks: INotebookTracker ): [string, CommandRegistry.ICommandOptions] { return [ 'pin-outputs-on-selection', { - label: 'Pin Outputs On Selection', - caption: 'Pin Outputs On Selection', + label: trans.__('Pin Outputs On Selection'), + caption: trans.__('Pin Outputs On Selection'), execute: () => { const current = getCurrentNotebookPanel(app, notebooks); if (current) { @@ -61,13 +65,14 @@ function createPinOutputOnSelectionCommand( function createRemoveOutputOnSelectionCommand( app: JupyterFrontEnd, + trans: TranslationBundle, notebooks: INotebookTracker ): [string, CommandRegistry.ICommandOptions] { return [ 'remove-outputs-on-selection', { - label: 'Remove Outputs On Selection', - caption: 'Remove Outputs On Selection', + label: trans.__('Remove Outputs On Selection'), + caption: trans.__('Remove Outputs On Selection'), execute: () => { const current = getCurrentNotebookPanel(app, notebooks); if (current) { @@ -80,13 +85,14 @@ function createRemoveOutputOnSelectionCommand( function createPinOutputInBelowSectionCommand( app: JupyterFrontEnd, + trans: TranslationBundle, notebooks: INotebookTracker ): [string, CommandRegistry.ICommandOptions] { return [ 'pin-outputs-in-below-section', { - label: 'Pin Outputs In Below Section', - caption: 'Pin Outputs In Below Section', + label: trans.__('Pin Outputs In Below Section'), + caption: trans.__('Pin Outputs In Below Section'), execute: () => { const current = getCurrentNotebookPanel(app, notebooks); if (current) { @@ -99,13 +105,14 @@ function createPinOutputInBelowSectionCommand( function createRemoveOutputInBelowSectionCommand( app: JupyterFrontEnd, + trans: TranslationBundle, notebooks: INotebookTracker ): [string, CommandRegistry.ICommandOptions] { return [ 'remove-outputs-in-below-section', { - label: 'Remove Outputs In Below Section', - caption: 'Remove Outputs In Below Section', + label: trans.__('Remove Outputs In Below Section'), + caption: trans.__('Remove Outputs In Below Section'), execute: () => { const current = getCurrentNotebookPanel(app, notebooks); if (current) { @@ -118,13 +125,14 @@ function createRemoveOutputInBelowSectionCommand( function createPinOutputInBelowAllCommand( app: JupyterFrontEnd, + trans: TranslationBundle, notebooks: INotebookTracker ): [string, CommandRegistry.ICommandOptions] { return [ 'pin-outputs-in-below-all', { - label: 'Pin Outputs In Below All', - caption: 'Pin Outputs In Below All', + label: trans.__('Pin Outputs In Below All'), + caption: trans.__('Pin Outputs In Below All'), execute: () => { const current = getCurrentNotebookPanel(app, notebooks); if (current) { @@ -137,13 +145,14 @@ function createPinOutputInBelowAllCommand( function createRemoveOutputInBelowAllCommand( app: JupyterFrontEnd, + trans: TranslationBundle, notebooks: INotebookTracker ): [string, CommandRegistry.ICommandOptions] { return [ 'remove-outputs-in-below-all', { - label: 'Remove Outputs In Below All', - caption: 'Remove Outputs In Below All', + label: trans.__('Remove Outputs In Below All'), + caption: trans.__('Remove Outputs In Below All'), execute: () => { const current = getCurrentNotebookPanel(app, notebooks); if (current) { diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index 8a57e57..d6366a2 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -1,10 +1,12 @@ import React, { FormEvent } from 'react'; +import { TranslationBundle } from '@jupyterlab/translation'; -export function SearchBar({ - onSubmit -}: { - onSubmit: (data: { keyword: string }) => unknown; -}): JSX.Element { +export type SearchBarProps = { + trans: TranslationBundle; + onSubmit: (data: { keyword: string }) => void; +}; + +export function SearchBar({ trans, onSubmit }: SearchBarProps): JSX.Element { const handleSubmit = (e: FormEvent) => { e.preventDefault(); const data = new FormData(e.currentTarget); @@ -15,9 +17,9 @@ export function SearchBar({ return (
- + - +
); } diff --git a/src/index.ts b/src/index.ts index 3e4a4a1..831a493 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application'; +import { ITranslator } from '@jupyterlab/translation'; import 'jquery-ui/ui/widgets/tabs'; import 'jquery-ui/ui/widgets/button'; @@ -26,21 +27,26 @@ Object.keys(diff).forEach(key => { const plugin: JupyterFrontEndPlugin = { id: pluginId, autoStart: true, - requires: [ICommandPalette, INotebookTracker, ISettingRegistry], + requires: [ICommandPalette, INotebookTracker, ISettingRegistry, ITranslator], activate: ( app: JupyterFrontEnd, palette: ICommandPalette, notebooks: INotebookTracker, - settings: ISettingRegistry + settings: ISettingRegistry, + translator: ITranslator ) => { console.debug('JupyterLab extension lc_multi_outputs is activated!'); + const trans = translator.load('lc_multi_outputs'); useSettings(settings); useColorPrompt(); useOutputTabs(); - registerCommands(app, notebooks, palette); + registerCommands(app, trans, notebooks, palette); - app.docRegistry.addWidgetExtension('Notebook', new OutputTabExtension()); + app.docRegistry.addWidgetExtension( + 'Notebook', + new OutputTabExtension(trans) + ); } }; diff --git a/src/showOutputAreaDiffDialog.ts b/src/showOutputAreaDiffDialog.ts index 04bfcf5..8f0a8c4 100644 --- a/src/showOutputAreaDiffDialog.ts +++ b/src/showOutputAreaDiffDialog.ts @@ -1,10 +1,12 @@ import { Dialog, showDialog } from '@jupyterlab/apputils'; import { CodeCell } from '@jupyterlab/cells'; import { OutputArea } from '@jupyterlab/outputarea'; +import { TranslationBundle } from '@jupyterlab/translation'; import { MergeWidget } from './MergeWidget'; import { getOutputText } from './getOutputText'; export function showOutputAreaDiffDialog( + trans: TranslationBundle, cell: CodeCell, pinnedExecutionCount: number, pinnedOutputArea: OutputArea @@ -12,6 +14,7 @@ export function showOutputAreaDiffDialog( const value = getOutputText(cell.outputArea); const orig = getOutputText(pinnedOutputArea); showDiffDialog( + trans, value, orig, String(cell.model.executionCount || '*'), @@ -20,6 +23,7 @@ export function showOutputAreaDiffDialog( } function showDiffDialog( + trans: TranslationBundle, value: string, other: string, label: string, @@ -31,7 +35,7 @@ function showDiffDialog( } showDialog({ title: `Diff: Out[${label}] <- Out[${otherLabel}]`, - body: new MergeWidget(value, other), - buttons: [Dialog.cancelButton({ label: '閉じる' })] + body: new MergeWidget(trans, value, other), + buttons: [Dialog.cancelButton({ label: trans.__('Close') })] }); } diff --git a/src/toolbar.ts b/src/toolbar.ts index 06cabfe..3af69ad 100644 --- a/src/toolbar.ts +++ b/src/toolbar.ts @@ -1,12 +1,14 @@ import { ToolbarButton } from '@jupyterlab/apputils'; import { Notebook, NotebookPanel } from '@jupyterlab/notebook'; +import { TranslationBundle } from '@jupyterlab/translation'; import { pinOutputOnSelection, removeOutputOnSelection } from './actions'; export function registerToolbarButtons( + trans: TranslationBundle, panel: NotebookPanel ): [ToolbarButton, ToolbarButton] { - const pinButton = createPinOutputButton(panel.content); - const removeButton = createRemoveOutputButton(panel.content); + const pinButton = createPinOutputButton(trans, panel.content); + const removeButton = createRemoveOutputButton(trans, panel.content); panel.toolbar.insertItem( 10, 'removePinnedOutputsLeavingOneOnSelection', @@ -16,24 +18,27 @@ export function registerToolbarButtons( return [pinButton, removeButton]; } -function createPinOutputButton(notebook: Notebook) { +function createPinOutputButton(trans: TranslationBundle, notebook: Notebook) { return new ToolbarButton({ className: 'lc-toolbar-button', label: '+', iconClass: 'fa fa-thumb-tack', - tooltip: 'Pin Outputs', + tooltip: trans.__('Pin Outputs'), onClick: () => { pinOutputOnSelection(notebook); } }); } -function createRemoveOutputButton(notebook: Notebook) { +function createRemoveOutputButton( + trans: TranslationBundle, + notebook: Notebook +) { return new ToolbarButton({ className: 'lc-toolbar-button', label: '1', iconClass: 'fa fa-thumb-tack', - tooltip: 'Remove Pinned Outputs Leaving One', + tooltip: trans.__('Remove Pinned Outputs Leaving One'), onClick: () => { removeOutputOnSelection(notebook); } diff --git a/ui-tests/tests/lc_multi_outputs.spec.ts b/ui-tests/tests/lc_multi_outputs.spec.ts index 8af2c6c..356d70c 100644 --- a/ui-tests/tests/lc_multi_outputs.spec.ts +++ b/ui-tests/tests/lc_multi_outputs.spec.ts @@ -1,15 +1,30 @@ -import { expect, test } from '@jupyterlab/galata'; +import { IJupyterLabPage, expect, test } from '@jupyterlab/galata'; +import { Page } from '@playwright/test'; function delay(ms: number) { // https://stackoverflow.com/questions/37764665/how-to-implement-sleep-function-in-typescript - return new Promise( resolve => setTimeout(resolve, ms) ); + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// https://github.com/jupyterlab/jupyterlab/blob/9844a6fdb680aeae28a4d6238433f751ce5a6204/galata/src/fixtures.ts#L319-L336 +async function myWaitForApplication({ baseURL }, use) { + const waitIsReady = async ( + page: Page, + helpers: IJupyterLabPage + ): Promise => { + await page.waitForSelector('#jupyterlab-splash', { + state: 'detached' + }); + //! not wait for launcher tab. + }; + await use(waitIsReady); } /** * Don't load JupyterLab webpage before running the tests. * This is required to ensure we capture all log messages. */ -test.use({ autoGoto: false }); +test.use({ autoGoto: false, waitForApplication: myWaitForApplication }); test('should emit an activation console message', async ({ page }) => { const logs: string[] = []; @@ -20,52 +35,72 @@ test('should emit an activation console message', async ({ page }) => { await page.goto(); expect( - logs.filter(s => s === 'JupyterLab extension lc_multi_outputs is activated!') + logs.filter( + s => s === 'JupyterLab extension lc_multi_outputs is activated!' + ) ).toHaveLength(1); }); -test.use({ autoGoto: true }); -test('should save current outputs on clicking the pin button', async ({ page }) => { +test.use({ autoGoto: true, waitForApplication: myWaitForApplication }); +test('should save current outputs on clicking the pin button', async ({ + page +}) => { // create new notebook - const fileName = "multi_outputs_test.ipynb"; - await page.notebook.createNew(fileName); - await page.waitForSelector(`[role="main"] >> text=${fileName}`); + await page.notebook.createNew(); // edit await page.notebook.setCell(0, 'code', 'print("test")'); // run await page.notebook.run(); // click pin button on cell - await page.locator(".multi-outputs-ui").first().locator("button").click(); + await page.locator('.multi-outputs-ui').first().locator('button').click(); // has tabs - let multi_outputs_tabs_container = page.locator(".multi-output-container").first(); + let multi_outputs_tabs_container = page + .locator('.multi-output-container') + .first(); await expect(multi_outputs_tabs_container).toBeVisible(); - await expect(multi_outputs_tabs_container.locator('li[id="tab-output-current"]')).toBeVisible(); - await expect(multi_outputs_tabs_container.locator('li[id="tab-output-1"]')).toBeVisible(); + await expect( + multi_outputs_tabs_container.locator('li[id="tab-output-current"]') + ).toBeVisible(); + await expect( + multi_outputs_tabs_container.locator('li[id="tab-output-1"]') + ).toBeVisible(); // edit await page.notebook.setCell(0, 'code', 'print("test2")\n'); // run await page.notebook.run(); // click add pin button on toolbar await page.notebook.selectCells(0, 0); - await page.locator('button[title="Pin Outputs"]').click(); + await page.locator('jp-button[title="Pin Outputs"]').click(); // has tabs - await expect(multi_outputs_tabs_container.locator('li[id="tab-output-2"]')).toBeVisible(); + await expect( + multi_outputs_tabs_container.locator('li[id="tab-output-2"]') + ).toBeVisible(); // click output-1 tab await multi_outputs_tabs_container.locator('li[id="tab-output-1"]').click(); // click diff button - await page.locator('#output-1').locator(".multi-outputs-diff-ui").locator("button").click(); + await page + .locator('#output-1') + .locator('.multi-outputs-diff-ui') + .locator('button') + .click(); await delay(1000); // has dialog - await expect(page.locator("dialog").first().getByText("Diff: Out[2] <- Out[1]")).toBeVisible(); + await expect( + page.locator('dialog').first().getByText('Diff: Out[2] <- Out[1]') + ).toBeVisible(); // close dialog - await page.locator("dialog").first().getByText("閉じる").click(); + await page.locator('dialog').first().getByText('Close').click(); await delay(1000); // click remove pin button on toolbar - await page.locator('button[title="Remove Pinned Outputs Leaving One"]').click(); + await page + .locator('jp-button[title="Remove Pinned Outputs Leaving One"]') + .click(); // not has output-1 tab - await expect(multi_outputs_tabs_container.locator('li[id="tab-output-1"]')).not.toBeVisible(); + await expect( + multi_outputs_tabs_container.locator('li[id="tab-output-1"]') + ).not.toBeVisible(); // click close button on output-2 tab - await page.locator('li[id="tab-output-2"]').locator("button").click(); + await page.locator('li[id="tab-output-2"]').locator('button').click(); // has no tabs - await expect(page.locator(".multi-output-container")).not.toBeVisible(); -}); \ No newline at end of file + await expect(page.locator('.multi-output-container')).not.toBeVisible(); +}); diff --git a/yarn.lock b/yarn.lock index ee61925..7c81163 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5682,6 +5682,7 @@ __metadata: "@jupyterlab/outputarea": ^4.0.3 "@jupyterlab/rendermime": ^4.0.3 "@jupyterlab/settingregistry": ^4.0.3 + "@jupyterlab/translation": ^4.0.3 "@jupyterlab/ui-components": ^4.0.3 "@lumino/algorithm": ^2.0.0 "@lumino/commands": ^2.0.1