about_diff.md 185 KB

二次开发代码改动说明

二次开发的代码修改基于 JupyterLab v3.4.3 版本进行. 这里主要针对代码部分的 diff 进行说明, 省略了 eslint 等的配置改动, 以及文档相关改动. 使用如下命令查看改动:

git diff v3.4.3 HEAD packages
  • 替换 JupyterLab 自带的 Logo 为自定义 Logo
diff --git a/packages/application-extension/src/index.tsx b/packages/application-extension/src/index.tsx
index 5da68f494d..403607bd1a 100644
--- a/packages/application-extension/src/index.tsx
+++ b/packages/application-extension/src/index.tsx
@@ -40,7 +40,8 @@ import { ITranslator, TranslationBundle } from '@jupyterlab/translation';
 import {
   buildIcon,
   ContextMenuSvg,
-  jupyterIcon,
+  // jupyterIcon,
+  LabIcon,
   RankedMenu
 } from '@jupyterlab/ui-components';
 import { each, iter, toArray } from '@lumino/algorithm';
@@ -48,6 +49,12 @@ import { JSONExt, PromiseDelegate } from '@lumino/coreutils';
 import { DisposableDelegate, DisposableSet } from '@lumino/disposable';
 import { DockLayout, DockPanel, Widget } from '@lumino/widgets';
 import * as React from 'react';
+import yiliLogoSvgStr from '../style/icons/YiliLogo.svg';
+
+const yiliLogoIcon = new LabIcon({
+  name: 'yili-logo',
+  svgstr: yiliLogoSvgStr
+});

 /**
  * Default context menu item rank
@@ -941,14 +948,15 @@ const JupyterLogo: JupyterFrontEndPlugin<void> = {
   requires: [ILabShell],
   activate: (app: JupyterFrontEnd, shell: ILabShell) => {
     const logo = new Widget();
-    jupyterIcon.element({
+    yiliLogoIcon.element({
       container: logo.node,
       elementPosition: 'center',
-      margin: '2px 2px 2px 8px',
+      marginRight: '10px',
+      marginLeft: '10px',
       height: 'auto',
-      width: '16px'
+      width: '54px'
     });
-    logo.id = 'jp-MainLogo';
+    logo.id = 'jp-YiliLogo';
     shell.add(logo, 'top', { rank: 0 });
   }
 };
  • 修改 JupyterLab 主界面的布局, 隐藏部分面板
diff --git a/packages/application/src/shell.ts b/packages/application/src/shell.ts
index c87c901fb3..931ccd369a 100644
--- a/packages/application/src/shell.ts
+++ b/packages/application/src/shell.ts
@@ -306,7 +306,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {

     dockPanel.node.setAttribute('role', 'main');

-    hboxPanel.spacing = 0;
+    hboxPanel.spacing = 2;
     vsplitPanel.spacing = 1;
     dockPanel.spacing = 5;
     hsplitPanel.spacing = 1;
@@ -330,14 +330,14 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {

     hsplitPanel.addWidget(leftHandler.stackedPanel);
     hsplitPanel.addWidget(dockPanel);
-    hsplitPanel.addWidget(rightHandler.stackedPanel);
+    // hsplitPanel.addWidget(rightHandler.stackedPanel);

     vsplitPanel.addWidget(hsplitPanel);
-    vsplitPanel.addWidget(downPanel);
+    // vsplitPanel.addWidget(downPanel);

     hboxPanel.addWidget(leftHandler.sideBar);
     hboxPanel.addWidget(vsplitPanel);
-    hboxPanel.addWidget(rightHandler.sideBar);
+    // hboxPanel.addWidget(rightHandler.sideBar);

     rootLayout.direction = 'top-to-bottom';
     rootLayout.spacing = 0; // TODO make this configurable?
@@ -353,10 +353,12 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
     BoxLayout.setStretch(hboxPanel, 1);
     BoxLayout.setStretch(bottomPanel, 0);

+    BoxLayout.setSizeBasis(topHandler.panel, 60);
+
     rootLayout.addWidget(headerPanel);
     rootLayout.addWidget(topHandler.panel);
     rootLayout.addWidget(hboxPanel);
-    rootLayout.addWidget(bottomPanel);
+    // rootLayout.addWidget(bottomPanel);

     // initially hiding header and bottom panel when no elements inside,
     this._headerPanel.hide();
@@ -1498,7 +1500,7 @@ namespace Private {
     /**
      * Get the panel managed by the handler.
      */
-    get panel() {
+    get panel(): Panel {
       return this._panel;
     }

@@ -1653,12 +1655,12 @@ namespace Private {

       if (title.icon instanceof LabIcon) {
         // bind an appropriate style to the icon
-        title.icon = title.icon.bindprops({
-          stylesheet: 'sideBar'
-        });
+        // title.icon = title.icon.bindprops({
+        //   stylesheet: 'sideBar'
+        // });
       } else if (typeof title.icon === 'string' || !title.icon) {
         // add some classes to help with displaying css background imgs
-        title.iconClass = classes(title.iconClass, 'jp-Icon', 'jp-Icon-20');
+        // title.iconClass = classes(title.iconClass, 'jp-Icon', 'jp-Icon-20');
       }

       this._refreshVisibility();
  • 修改主界面 css 样式, 匹配前端原型
diff --git a/packages/application/style/base.css b/packages/application/style/base.css
index 25daa9c913..3c937aa43a 100644
--- a/packages/application/style/base.css
+++ b/packages/application/style/base.css
@@ -13,7 +13,7 @@

 body {
   font-family: var(--jp-ui-font-family);
-  background: var(--jp-layout-color3);
+  background: #f0f2f5;
   margin: 0;
   padding: 0;
   overflow: hidden;
@@ -31,8 +31,13 @@ body {
   border-top: 4px solid red;
 }

+#jp-main-vsplit-panel,
+#jp-main-split-panel {
+  overflow: visible;
+}
+
 #jp-main-dock-panel {
-  padding: 5px;
+  padding: 10px;
 }

 #jp-main-dock-panel[data-mode='single-document'] {
@@ -44,8 +49,7 @@ body {
 }

 #jp-top-panel {
-  border-bottom: var(--jp-border-width) solid var(--jp-border-color0);
-  background: var(--jp-layout-color1);
+  background-color: #222222;
   display: flex;
   min-height: var(--jp-private-menubar-height);
   overflow: visible;
@@ -53,7 +57,23 @@ body {

 #jp-menu-panel {
   min-height: var(--jp-private-menu-panel-height);
-  background: var(--jp-layout-color1);
+  align-self: center;
+}
+
+#jp-MainMenu {
+  background-color: #222222;
+  color: #afafaf;
+}
+
+#jp-MainMenu .lm-MenuBar-item {
+  padding: 0 20px;
+  border: none;
+}
+
+#jp-MainMenu .lm-MenuBar-item.lm-mod-active {
+  background-color: #333333;
+  border: none;
+  box-shadow: none;
 }

 #jp-down-stack {
  • 重新定义 JupyterLab 对话框按钮样式, 匹配前端原型
diff --git a/packages/application/style/buttons.css b/packages/application/style/buttons.css
index 0c25eb4b3e..2e90a454e5 100644
--- a/packages/application/style/buttons.css
+++ b/packages/application/style/buttons.css
@@ -13,52 +13,32 @@
 |----------------------------------------------------------------------------*/

 button {
-  border-radius: var(--jp-border-radius);
-}
-
-button:focus-visible {
-  border: 1px solid var(--md-blue-900);
+  border-radius: 2px;
 }

 button.jp-mod-styled.jp-mod-accept {
-  background: var(--md-blue-700);
-  border: 0;
+  background: #4883fb;
+  border: 1px solid #4883fb;
   color: white;
 }

 button.jp-mod-styled.jp-mod-accept:hover {
-  background: var(--md-blue-800);
-}
-
-button.jp-mod-styled.jp-mod-accept:active {
-  background: var(--md-blue-900);
-}
-
-button.jp-mod-styled.jp-mod-accept:focus-visible {
-  border: 1px solid var(--md-blue-900);
+  background: #276fff;
 }

 button.jp-mod-styled.jp-mod-reject {
-  background: var(--md-grey-600);
-  border: 0;
-  color: white;
+  background-color: white;
+  border: 1px solid #dddddd;
+  color: #7d7d7d;
 }

 button.jp-mod-styled.jp-mod-reject:hover {
-  background: var(--md-grey-700);
-}
-
-button.jp-mod-styled.jp-mod-reject:active {
-  background: var(--md-grey-800);
-}
-
-button.jp-mod-styled.jp-mod-reject:focus-visible {
-  border: 1px solid var(--md-grey-800);
+  background: #eeeeee;
 }

 button.jp-mod-styled.jp-mod-warn {
   background: var(--md-red-700);
-  border: 0;
+  border: 1px solid var(--md-red-700);
   color: white;
 }

@@ -66,14 +46,6 @@ button.jp-mod-styled.jp-mod-warn:hover {
   background: var(--md-red-800);
 }

-button.jp-mod-styled.jp-mod-warn:active {
-  background: var(--md-red-900);
-}
-
-button.jp-mod-styled.jp-mod-warn:focus-visible {
-  border: 1px solid var(--md-red-900);
-}
-
 .jp-Button-flat {
   text-decoration: none;
   padding: var(--jp-flat-button-padding);
  • 修改主面板样式
diff --git a/packages/application/style/dockpanel.css b/packages/application/style/dockpanel.css
index 827eccb72c..a189fb6727 100644
--- a/packages/application/style/dockpanel.css
+++ b/packages/application/style/dockpanel.css
@@ -14,9 +14,7 @@
 .lm-DockPanel-widget,
 .lm-TabPanel-stackedPanel {
   background: var(--jp-layout-color0);
-  border-left: var(--jp-border-width) solid var(--jp-border-color1);
-  border-right: var(--jp-border-width) solid var(--jp-border-color1);
-  border-bottom: var(--jp-border-width) solid var(--jp-border-color1);
+  box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.1);
 }

 .lm-DockPanel-overlay {
@@ -34,3 +32,11 @@
 .lm-DockPanel-overlay.lm-mod-root-center {
   border-width: 2px;
 }
+
+#jp-main-dock-panel .lm-DockPanel-widget {
+  z-index: 2;
+}
+
+#jp-main-dock-panel .lm-DockPanel-tabBar {
+  z-index: unset;
+}

diff --git a/packages/application/style/menus.css b/packages/application/style/menus.css
index 36535b8571..97241b8730 100644
--- a/packages/application/style/menus.css
+++ b/packages/application/style/menus.css
@@ -26,10 +26,6 @@
   overflow-x: auto;
 }

-.lm-MenuBar-menu {
-  transform: translateY(calc(-2 * var(--jp-border-width)));
-}
-
 .lm-MenuBar-item {
   padding: 0px 8px;
   border-left: var(--jp-border-width) solid transparent;
  • 修改侧边栏样式
diff --git a/packages/application/style/sidepanel.css b/packages/application/style/sidepanel.css
index d19f2d2841..ed7ff3b8bf 100644
--- a/packages/application/style/sidepanel.css
+++ b/packages/application/style/sidepanel.css
@@ -21,18 +21,14 @@
   font-size: var(--jp-ui-font-size1);
 }

-.jp-SideBar.lm-TabBar,
-#jp-down-stack .lm-TabBar {
-  color: var(--jp-ui-font-color1);
-  background: var(--jp-layout-color2);
-  font-size: var(--jp-ui-font-size1);
-  overflow: visible;
-}
-
 .jp-SideBar.lm-TabBar {
-  min-width: calc(var(--jp-private-sidebar-tab-width) + var(--jp-border-width));
-  max-width: calc(var(--jp-private-sidebar-tab-width) + var(--jp-border-width));
+  color: #4a4a4a;
+  background-color: white;
+  font-size: var(--jp-ui-font-size1);
+  min-width: 180px;
+  max-width: 180px;
   display: block;
+  height: 100%;
 }

 .jp-SideBar .lm-TabBar-content {
@@ -41,103 +37,44 @@
   display: flex;
   align-items: stretch;
   list-style-type: none;
-  height: var(--jp-private-sidebar-tab-width);
-  transform-origin: 0 0 0;
 }

 .jp-SideBar .lm-TabBar-tab {
-  padding: 0 16px;
+  display: flex;
+  align-items: center;
+  height: 50px;
+  padding: 0 25px;
   border: none;
-  overflow: visible;
+  color: #4a4a4a;
 }

 .jp-SideBar .lm-TabBar-tab.lm-mod-current {
-  min-height: calc(
-    var(--jp-private-sidebar-tab-width) + var(--jp-border-width)
-  );
-  max-height: calc(
-    var(--jp-private-sidebar-tab-width) + var(--jp-border-width)
-  );
-  /* transform: translateY(var(--jp-border-width)); */
-}
-
-.jp-SideBar .lm-TabBar-tab:not(.lm-mod-current),
-#jp-down-stack .lm-TabBar-tab:not(.lm-mod-current) {
-  background: var(--jp-layout-color2);
-}
-
-.jp-SideBar .lm-TabBar-tabIcon.jp-SideBar-tabIcon {
-  min-width: 20px;
-  min-height: 20px;
-  background-size: 20px;
-  display: inline-block;
-  vertical-align: middle;
-  background-repeat: no-repeat;
-  background-position: center;
-}
-
-.jp-SideBar .lm-TabBar-tabLabel {
-  line-height: var(--jp-private-sidebar-tab-width);
-}
-
-.jp-SideBar .lm-TabBar-tab:hover:not(.lm-mod-current),
-#jp-down-stack .lm-TabBar-tab:hover:not(.lm-mod-current) {
-  background: var(--jp-layout-color1);
-}
-
-/* Left */
-
-/* Borders */
-
-.jp-SideBar.lm-TabBar.jp-mod-left {
-  border-right: var(--jp-border-width) solid var(--jp-border-color0);
-}
-
-.jp-SideBar.lm-TabBar.jp-mod-left .lm-TabBar-tab + .lm-TabBar-tab {
-  border-right: var(--jp-border-width) solid var(--jp-layout-color2);
+  background-color: #147bd1;
+  color: white;
 }

-.jp-SideBar.lm-TabBar.jp-mod-left
-  .lm-TabBar-tab.lm-mod-current
-  + .lm-TabBar-tab {
-  border-right: var(--jp-border-width) solid var(--jp-border-color0);
+.jp-SideBar .lm-TabBar-tab .jp-icon3[fill] {
+  fill: #4a4a4a;
 }

-.jp-SideBar.lm-TabBar.jp-mod-left
-  .lm-TabBar-tab
-  + .lm-TabBar-tab.lm-mod-current {
-  border-right: var(--jp-border-width) solid var(--jp-border-color0);
+.jp-SideBar .lm-TabBar-tab.lm-mod-current .jp-icon3[fill] {
+  fill: white;
 }

-.jp-SideBar.lm-TabBar.jp-mod-left .lm-TabBar-tab.lm-mod-current:last-child {
-  border-left: var(--jp-border-width) solid var(--jp-border-color0);
-}
-
-/* Transforms */
-
-.jp-SideBar.lm-TabBar.jp-mod-left .lm-TabBar-content {
-  flex-direction: row-reverse;
-  transform: rotate(-90deg) translateX(-100%);
+.jp-SideBar .lm-TabBar-tabIcon svg {
+  width: 16px;
+  height: 16px;
+  display: block;
 }

-.jp-SideBar.lm-TabBar.jp-mod-left
-  .lm-TabBar-tab:not(.lm-mod-current)
-  .lm-TabBar-tabIcon {
-  transform: rotate(90deg);
+.jp-SideBar .lm-TabBar-tabIcon {
+  margin-right: 16px;
 }

-.jp-SideBar.lm-TabBar.jp-mod-left
-  .lm-TabBar-tab.lm-mod-current
-  .lm-TabBar-tabIcon {
-  transform: rotate(90deg)
-    translate(
-      calc(-0.5 * var(--jp-border-width)),
-      calc(-0.5 * var(--jp-border-width))
-    );
+.jp-SideBar .lm-TabBar-tab:hover:not(.lm-mod-current) {
+  background-color: #eeeeee;
 }

-/* Right */
-
 /* Borders */

 .jp-SideBar.lm-TabBar.jp-mod-right {
@@ -217,6 +154,7 @@
 #jp-left-stack > .lm-Widget,
 #jp-right-stack > .lm-Widget {
   min-width: var(--jp-sidebar-min-width);
+  background-color: #f8f8f9;
 }

 #jp-right-stack {
@@ -224,7 +162,7 @@
 }

 #jp-left-stack {
-  border-right: var(--jp-border-width) solid var(--jp-border-color1);
+  box-shadow: 1px 0px 6px 0px rgba(0, 0, 0, 0.12);
 }

 #jp-down-stack > .lm-TabPanel-stackedPanel {
  • 重新设置 Tab 的样式, 将原来的长方形改为梯形
diff --git a/packages/application/style/tabs.css b/packages/application/style/tabs.css
index bb23cb958f..171e3d34ef 100644
--- a/packages/application/style/tabs.css
+++ b/packages/application/style/tabs.css
@@ -20,7 +20,6 @@

 .lm-DockPanel-tabBar,
 .lm-TabPanel-tabBar {
-  border-bottom: var(--jp-border-width) solid var(--jp-border-color1);
   overflow: visible;
   color: var(--jp-ui-font-color1);
   font-size: var(--jp-ui-font-size1);
@@ -52,18 +51,38 @@
     var(--jp-private-horizontal-tab-height) + var(--jp-border-width)
   );
   min-width: 0px;
-  margin-left: calc(-1 * var(--jp-border-width));
+  margin-left: -10px;
+  margin-right: 25px;
   line-height: var(--jp-private-horizontal-tab-height);
   padding: 0px 8px;
-  background: var(--jp-layout-color2);
-  border: var(--jp-border-width) solid var(--jp-border-color1);
-  border-bottom: none;
+  background: #eeeeee;
+  border: none;
   position: relative;
+  overflow: visible;
+  color: #4a4a4a;
+  box-shadow: -2px -2px 4px -2px rgba(0, 0, 0, 0.1);
+}
+
+.lm-DockPanel-tabBar .lm-TabBar-tab::after {
+  content: ' ';
+  position: absolute;
+  top: 0;
+  height: 100%;
+  background-color: inherit;
+  z-index: 1;
+  width: 22px;
+  right: -12px;
+  transform: skew(40deg);
+  box-shadow: 4px 0px 4px -4px rgba(0, 0, 0, 0.1);
+}
+
+.lm-DockPanel-tabBar .lm-TabBar-tab .lm-TabBar-tabCloseIcon {
+  z-index: 2;
 }

 .lm-DockPanel-tabBar .lm-TabBar-tab:hover:not(.lm-mod-current),
 .lm-TabPanel-tabBar .lm-TabBar-tab:hover:not(.lm-mod-current) {
-  background: var(--jp-layout-color1);
+  background-color: #f5f5f5;
 }

 .lm-DockPanel-tabBar .lm-TabBar-tab:first-child,
@@ -74,11 +93,10 @@
 /* This is a current tab of a tab bar in the dock panel: each tab bar has 1. */
 .lm-DockPanel-tabBar .lm-TabBar-tab.lm-mod-current {
   background: var(--jp-layout-color1);
-  color: var(--jp-ui-font-color0);
+  color: #4a4a4a;
   min-height: calc(
     var(--jp-private-horizontal-tab-height) + 2 * var(--jp-border-width)
   );
-  transform: translateY(var(--jp-border-width));
 }

 .lm-TabPanel-tabBar .lm-TabBar-tab.lm-mod-current {
@@ -86,17 +104,6 @@
   color: var(--jp-ui-font-color0);
 }

-/* This is the main application level current tab: only 1 exists. */
-.lm-DockPanel-tabBar .lm-TabBar-tab.jp-mod-current:before {
-  position: absolute;
-  top: calc(-1 * var(--jp-border-width) + 1px);
-  left: calc(-1 * var(--jp-border-width));
-  content: '';
-  height: var(--jp-private-horizontal-tab-active-top-border);
-  width: calc(100% + 2 * var(--jp-border-width));
-  background: var(--jp-brand-color1);
-}
-
 /* This is the left tab bar current tab: only 1 exists. */
 .lm-TabBar-tab.lm-mod-current {
   color: var(--jp-ui-font-color0);
  • 去除一些可能导致 Notebook 被下载的插件
diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts
index fe5cee38f2..cdf726aa6e 100644
--- a/packages/apputils-extension/src/index.ts
+++ b/packages/apputils-extension/src/index.ts
@@ -22,7 +22,7 @@ import {
   ISplashScreen,
   IWindowResolver,
   MainAreaWidget,
-  Printing,
+  // Printing,
   sessionContextDialogs,
   WindowResolver
 } from '@jupyterlab/apputils';
@@ -268,28 +268,28 @@ Would you like to clear the workspace or keep waiting?`),
   }
 };

-const print: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/apputils-extension:print',
-  autoStart: true,
-  requires: [ITranslator],
-  activate: (app: JupyterFrontEnd, translator: ITranslator) => {
-    const trans = translator.load('jupyterlab');
-    app.commands.addCommand(CommandIDs.print, {
-      label: trans.__('Print…'),
-      isEnabled: () => {
-        const widget = app.shell.currentWidget;
-        return Printing.getPrintFunction(widget) !== null;
-      },
-      execute: async () => {
-        const widget = app.shell.currentWidget;
-        const printFunction = Printing.getPrintFunction(widget);
-        if (printFunction) {
-          await printFunction();
-        }
-      }
-    });
-  }
-};
+// const print: JupyterFrontEndPlugin<void> = {
+//   id: '@jupyterlab/apputils-extension:print',
+//   autoStart: true,
+//   requires: [ITranslator],
+//   activate: (app: JupyterFrontEnd, translator: ITranslator) => {
+//     const trans = translator.load('jupyterlab');
+//     app.commands.addCommand(CommandIDs.print, {
+//       label: trans.__('Print…'),
+//       isEnabled: () => {
+//         const widget = app.shell.currentWidget;
+//         return Printing.getPrintFunction(widget) !== null;
+//       },
+//       execute: async () => {
+//         const widget = app.shell.currentWidget;
+//         const printFunction = Printing.getPrintFunction(widget);
+//         if (printFunction) {
+//           await printFunction();
+//         }
+//       }
+//     });
+//   }
+// };

 export const toggleHeader: JupyterFrontEndPlugin<void> = {
   id: '@jupyterlab/apputils-extension:toggle-header',
@@ -620,7 +620,7 @@ const sanitizer: JupyterFrontEndPlugin<ISanitizer> = {
 const plugins: JupyterFrontEndPlugin<any>[] = [
   palette,
   paletteRestorer,
-  print,
+  // print,
   resolver,
   sanitizer,
   settingsPlugin,
diff --git a/packages/apputils-extension/src/workspacesplugin.ts b/packages/apputils-extension/src/workspacesplugin.ts
index ffcefb6ded..bf8b93a806 100644
--- a/packages/apputils-extension/src/workspacesplugin.ts
+++ b/packages/apputils-extension/src/workspacesplugin.ts
@@ -24,11 +24,11 @@ import { IStateDB } from '@jupyterlab/statedb';
 import { ITranslator, nullTranslator } from '@jupyterlab/translation';
 import { Widget } from '@lumino/widgets';

-namespace CommandIDs {
-  export const saveWorkspace = 'workspace-ui:save';
+// namespace CommandIDs {
+//   export const saveWorkspace = 'workspace-ui:save';

-  export const saveWorkspaceAs = 'workspace-ui:save-as';
-}
+//   export const saveWorkspaceAs = 'workspace-ui:save-as';
+// }

 const WORKSPACE_NAME = 'jupyterlab-workspace';
 const WORKSPACE_EXT = '.' + WORKSPACE_NAME;
@@ -78,39 +78,39 @@ export const workspacesPlugin: JupyterFrontEndPlugin<void> = {
       iconClass: ICON_NAME
     });
     app.docRegistry.addWidgetFactory(factory);
-    app.commands.addCommand(CommandIDs.saveWorkspaceAs, {
-      label: trans.__('Save Current Workspace As…'),
-      execute: async () => {
-        const data = app.serviceManager.workspaces.fetch(resolver.name);
-        await Private.saveAs(
-          fbf.defaultBrowser,
-          app.serviceManager.contents,
-          data,
-          state,
-          translator
-        );
-      }
-    });
+    // app.commands.addCommand(CommandIDs.saveWorkspaceAs, {
+    //   label: trans.__('Save Current Workspace As…'),
+    //   execute: async () => {
+    //     const data = app.serviceManager.workspaces.fetch(resolver.name);
+    //     await Private.saveAs(
+    //       fbf.defaultBrowser,
+    //       app.serviceManager.contents,
+    //       data,
+    //       state,
+    //       translator
+    //     );
+    //   }
+    // });

-    app.commands.addCommand(CommandIDs.saveWorkspace, {
-      label: trans.__('Save Current Workspace'),
-      execute: async () => {
-        const { contents } = app.serviceManager;
-        const data = app.serviceManager.workspaces.fetch(resolver.name);
-        const lastSave = (await state.fetch(LAST_SAVE_ID)) as string;
-        if (lastSave === undefined) {
-          await Private.saveAs(
-            fbf.defaultBrowser,
-            contents,
-            data,
-            state,
-            translator
-          );
-        } else {
-          await Private.save(lastSave, contents, data, state);
-        }
-      }
-    });
+    // app.commands.addCommand(CommandIDs.saveWorkspace, {
+    //   label: trans.__('Save Current Workspace'),
+    //   execute: async () => {
+    //     const { contents } = app.serviceManager;
+    //     const data = app.serviceManager.workspaces.fetch(resolver.name);
+    //     const lastSave = (await state.fetch(LAST_SAVE_ID)) as string;
+    //     if (lastSave === undefined) {
+    //       await Private.saveAs(
+    //         fbf.defaultBrowser,
+    //         contents,
+    //         data,
+    //         state,
+    //         translator
+    //       );
+    //     } else {
+    //       await Private.save(lastSave, contents, data, state);
+    //     }
+    //   }
+    // });
   }
 };
  • 优化 JupyterLab 对话框的代码
diff --git a/packages/apputils/src/dialog.tsx b/packages/apputils/src/dialog.tsx
index 2003f99c5a..9f3854c394 100644
--- a/packages/apputils/src/dialog.tsx
+++ b/packages/apputils/src/dialog.tsx
@@ -33,9 +33,9 @@ export function showDialog<T>(
  * @param error - the error to show in the dialog body (either a string
  *   or an object with a string `message` property).
  */
-export function showErrorMessage(
+export function showErrorMessage<E extends { message: string | JSX.Element }>(
   title: string,
-  error: any,
+  error: string | E,
   buttons: ReadonlyArray<Dialog.IButton> = [
     Dialog.okButton({ label: 'Dismiss' })
   ]
@@ -415,8 +415,6 @@ export class Dialog<T> extends Widget {
       this.dispose();
       return;
     }
-    this._promise = null;
-    ArrayExt.removeFirstOf(Private.launchQueue, promise.promise);
     const body = this._body;
     let value: T | null = null;
     if (
@@ -426,6 +424,8 @@ export class Dialog<T> extends Widget {
     ) {
       value = body.getValue();
     }
+    this._promise = null;
+    ArrayExt.removeFirstOf(Private.launchQueue, promise.promise);
     this.dispose();
     promise.resolve({ button, value });
   }
@@ -693,7 +693,7 @@ export namespace Dialog {
   /**
    * The default implementation of a dialog renderer.
    */
-  export class Renderer {
+  export class Renderer implements Dialog.IRenderer {
     /**
      * Create the header of the dialog.
      *

diff --git a/packages/apputils/src/mainareawidget.ts b/packages/apputils/src/mainareawidget.ts
index baa4eaa21f..f01dbd18f1 100644
--- a/packages/apputils/src/mainareawidget.ts
+++ b/packages/apputils/src/mainareawidget.ts
@@ -60,8 +60,8 @@ export class MainAreaWidget<T extends Widget = Widget>
     BoxLayout.setStretch(toolbar, 0);
     BoxLayout.setStretch(contentHeader, 0);
     BoxLayout.setStretch(content, 1);
-    layout.addWidget(toolbar);
     layout.addWidget(contentHeader);
+    layout.addWidget(toolbar);
     layout.addWidget(content);

     if (!content.id) {
  • 更新对话框样式, 匹配前端原型
diff --git a/packages/apputils/style/dialog.css b/packages/apputils/style/dialog.css
index 7994fb4b59..17dfd7307f 100644
--- a/packages/apputils/style/dialog.css
+++ b/packages/apputils/style/dialog.css
@@ -6,7 +6,7 @@

 .jp-Dialog {
   position: absolute;
-  z-index: 10000;
+  z-index: 1000;
   display: flex;
   flex-direction: column;
   align-items: center;
@@ -17,7 +17,7 @@
   padding: 0;
   width: 100%;
   height: 100%;
-  background: var(--jp-dialog-background);
+  background: rgba(0, 0, 0, 0.4);
 }

 .jp-Dialog-content {
@@ -26,20 +26,17 @@
   margin-left: auto;
   margin-right: auto;
   background: var(--jp-layout-color1);
-  padding: 24px 24px 12px 24px;
   min-width: 300px;
   min-height: 150px;
   max-width: 1000px;
-  max-height: 500px;
   box-sizing: border-box;
-  box-shadow: var(--jp-elevation-z20);
+  box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.33);
   word-wrap: break-word;
   border-radius: var(--jp-border-radius);
   /* This is needed so that all font sizing of children done in ems is
    * relative to this base size */
   font-size: var(--jp-ui-font-size1);
   color: var(--jp-ui-font-color1);
-  resize: both;
 }

 .jp-Dialog-content.jp-Dialog-content-small {
@@ -50,50 +47,32 @@
   overflow: visible;
 }

-button.jp-Dialog-button:focus {
-  outline: 1px solid var(--jp-brand-color1);
-  outline-offset: 4px;
-  -moz-outline-radius: 0px;
-}
-
 button.jp-Dialog-button:focus::-moz-focus-inner {
   border: 0;
 }

-button.jp-Dialog-button.jp-mod-styled.jp-mod-accept:focus,
-button.jp-Dialog-button.jp-mod-styled.jp-mod-warn:focus,
-button.jp-Dialog-button.jp-mod-styled.jp-mod-reject:focus {
-  outline-offset: 4px;
-  -moz-outline-radius: 0px;
-}
-
-button.jp-Dialog-button.jp-mod-styled.jp-mod-accept:focus {
-  outline: 1px solid var(--md-blue-700);
-}
-
-button.jp-Dialog-button.jp-mod-styled.jp-mod-warn:focus {
-  outline: 1px solid var(--md-red-600);
-}
-
-button.jp-Dialog-button.jp-mod-styled.jp-mod-reject:focus {
-  outline: 1px solid var(--md-grey-700);
-}
-
 button.jp-Dialog-close-button {
   padding: 0;
-  height: 100%;
+  height: auto;
   min-width: unset;
   min-height: unset;
+  transform: translateY(2px);
+}
+
+button.jp-Dialog-close-button svg {
+  width: 22px;
 }

 .jp-Dialog-header {
   display: flex;
   justify-content: space-between;
   flex: 0 0 auto;
-  padding-bottom: 12px;
-  font-size: var(--jp-ui-font-size3);
-  font-weight: 400;
-  color: var(--jp-ui-font-color0);
+  align-items: center;
+  height: 48px;
+  padding: 0 30px;
+  font-weight: bold;
+  color: #4a4a4a;
+  background-color: #edf2f8;
 }

 .jp-Dialog-body {
@@ -103,16 +82,15 @@ button.jp-Dialog-close-button {
   font-size: var(--jp-ui-font-size1);
   background: var(--jp-layout-color1);
   overflow: auto;
+  padding: 40px;
 }

 .jp-Dialog-footer {
   display: flex;
   flex-direction: row;
-  justify-content: flex-end;
+  justify-content: center;
   flex: 0 0 auto;
-  margin-left: -12px;
-  margin-right: -12px;
-  padding: 12px;
+  padding: 10px 30px 30px;
 }

 .jp-Dialog-title {
@@ -130,10 +108,10 @@ button.jp-Dialog-close-button {
 }

 .jp-Dialog-body > label {
-  line-height: 1.4;
-  color: var(--jp-ui-font-color0);
+  color: #4a4a4a;
+  margin-bottom: 5px;
 }

 .jp-Dialog-button.jp-mod-styled:not(:last-child) {
-  margin-right: 12px;
+  margin-right: 50px;
 }

diff --git a/packages/apputils/style/styling.css b/packages/apputils/style/styling.css
index de5146bb28..1b21b48370 100644
--- a/packages/apputils/style/styling.css
+++ b/packages/apputils/style/styling.css
@@ -10,9 +10,8 @@ button.jp-mod-styled {
   border: none;
   box-sizing: border-box;
   text-align: center;
-  line-height: 32px;
   height: 32px;
-  padding: 0px 12px;
+  width: 80px;
   letter-spacing: 0.8px;
   outline: none;
   appearance: none;
@@ -21,18 +20,19 @@ button.jp-mod-styled {
 }

 input.jp-mod-styled {
-  background: var(--jp-input-background);
-  height: 28px;
+  height: 32px;
   box-sizing: border-box;
-  border: var(--jp-border-width) solid var(--jp-border-color1);
-  padding-left: 7px;
-  padding-right: 7px;
-  font-size: var(--jp-ui-font-size2);
+  border: 1px solid #c8d3e9;
+  border-radius: 2px;
+  padding: 0 15px;
+  font-size: 12px;
   color: var(--jp-ui-font-color0);
   outline: none;
   appearance: none;
   -webkit-appearance: none;
   -moz-appearance: none;
+  transition: all 100ms;
+  outline: 1px solid transparent;
 }

 input[type='checkbox'].jp-mod-styled {
@@ -43,8 +43,7 @@ input[type='checkbox'].jp-mod-styled {
 }

 input.jp-mod-styled:focus {
-  border: var(--jp-border-width) solid var(--md-blue-500);
-  box-shadow: inset 0 0 4px var(--md-blue-300);
+  outline-color: #147bd1;
 }

 .jp-FileDialog-Checkbox {

diff --git a/packages/cells/style/inputarea.css b/packages/cells/style/inputarea.css
index ec6f1620c0..5831ab644d 100644
--- a/packages/cells/style/inputarea.css
+++ b/packages/cells/style/inputarea.css
@@ -36,7 +36,7 @@ body[data-format='mobile'] .jp-InputArea-editor {

 .jp-InputPrompt {
   flex: 0 0 var(--jp-cell-prompt-width);
-  color: var(--jp-cell-inprompt-font-color);
+  color: #147bd1;
   font-family: var(--jp-cell-prompt-font-family);
   padding: var(--jp-code-padding);
   letter-spacing: var(--jp-cell-prompt-letter-spacing);
  • 去除一些不符合需求的菜单项和组件
diff --git a/packages/console-extension/src/index.ts b/packages/console-extension/src/index.ts
index fe4c7d1d05..16cdb587e8 100644
--- a/packages/console-extension/src/index.ts
+++ b/packages/console-extension/src/index.ts
@@ -25,12 +25,12 @@ import { ConsolePanel, IConsoleTracker } from '@jupyterlab/console';
 import { IFileBrowserFactory } from '@jupyterlab/filebrowser';
 import { ILauncher } from '@jupyterlab/launcher';
 import {
-  IEditMenu,
-  IFileMenu,
-  IHelpMenu,
-  IKernelMenu,
-  IMainMenu,
-  IRunMenu
+  // IEditMenu,
+  // IFileMenu,
+  // IHelpMenu,
+  // IKernelMenu,
+  IMainMenu
+  // IRunMenu
 } from '@jupyterlab/mainmenu';
 import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
 import { ISettingRegistry } from '@jupyterlab/settingregistry';
@@ -673,79 +673,79 @@ async function activateConsole(
     });
   }

-  if (mainMenu) {
-    // Add a close and shutdown command to the file menu.
-    mainMenu.fileMenu.closeAndCleaners.add({
-      tracker,
-      closeAndCleanupLabel: (n: number) => trans.__('Shutdown Console'),
-      closeAndCleanup: (current: ConsolePanel) => {
-        return showDialog({
-          title: trans.__('Shut down the Console?'),
-          body: trans.__(
-            'Are you sure you want to close "%1"?',
-            current.title.label
-          ),
-          buttons: [Dialog.cancelButton(), Dialog.warnButton()]
-        }).then(result => {
-          if (result.button.accept) {
-            return current.console.sessionContext.shutdown().then(() => {
-              current.dispose();
-            });
-          } else {
-            return void 0;
-          }
-        });
-      }
-    } as IFileMenu.ICloseAndCleaner<ConsolePanel>);
-
-    // Add a kernel user to the Kernel menu
-    mainMenu.kernelMenu.kernelUsers.add({
-      tracker,
-      restartKernelAndClearLabel: n =>
-        trans.__('Restart Kernel and Clear Console'),
-      interruptKernel: current => {
-        const kernel = current.console.sessionContext.session?.kernel;
-        if (kernel) {
-          return kernel.interrupt();
-        }
-        return Promise.resolve(void 0);
-      },
-      restartKernel: current =>
-        sessionDialogs!.restart(current.console.sessionContext, translator),
-      restartKernelAndClear: current => {
-        return sessionDialogs!
-          .restart(current.console.sessionContext)
-          .then(restarted => {
-            if (restarted) {
-              current.console.clear();
-            }
-            return restarted;
-          });
-      },
-      changeKernel: current =>
-        sessionDialogs!.selectKernel(
-          current.console.sessionContext,
-          translator
-        ),
-      shutdownKernel: current => current.console.sessionContext.shutdown()
-    } as IKernelMenu.IKernelUser<ConsolePanel>);
-
-    // Add a code runner to the Run menu.
-    mainMenu.runMenu.codeRunners.add({
-      tracker,
-      runLabel: (n: number) => trans.__('Run Cell'),
-      run: current => current.console.execute(true)
-    } as IRunMenu.ICodeRunner<ConsolePanel>);
-
-    // Add a clearer to the edit menu
-    mainMenu.editMenu.clearers.add({
-      tracker,
-      clearCurrentLabel: (n: number) => trans.__('Clear Console Cell'),
-      clearCurrent: (current: ConsolePanel) => {
-        return current.console.clear();
-      }
-    } as IEditMenu.IClearer<ConsolePanel>);
-  }
+  // if (mainMenu) {
+  //   // Add a close and shutdown command to the file menu.
+  //   mainMenu.fileMenu.closeAndCleaners.add({
+  //     tracker,
+  //     closeAndCleanupLabel: (n: number) => trans.__('Shutdown Console'),
+  //     closeAndCleanup: (current: ConsolePanel) => {
+  //       return showDialog({
+  //         title: trans.__('Shut down the Console?'),
+  //         body: trans.__(
+  //           'Are you sure you want to close "%1"?',
+  //           current.title.label
+  //         ),
+  //         buttons: [Dialog.cancelButton(), Dialog.warnButton()]
+  //       }).then(result => {
+  //         if (result.button.accept) {
+  //           return current.console.sessionContext.shutdown().then(() => {
+  //             current.dispose();
+  //           });
+  //         } else {
+  //           return void 0;
+  //         }
+  //       });
+  //     }
+  //   } as IFileMenu.ICloseAndCleaner<ConsolePanel>);
+
+  //   // Add a kernel user to the Kernel menu
+  //   mainMenu.kernelMenu.kernelUsers.add({
+  //     tracker,
+  //     restartKernelAndClearLabel: n =>
+  //       trans.__('Restart Kernel and Clear Console'),
+  //     interruptKernel: current => {
+  //       const kernel = current.console.sessionContext.session?.kernel;
+  //       if (kernel) {
+  //         return kernel.interrupt();
+  //       }
+  //       return Promise.resolve(void 0);
+  //     },
+  //     restartKernel: current =>
+  //       sessionDialogs!.restart(current.console.sessionContext, translator),
+  //     restartKernelAndClear: current => {
+  //       return sessionDialogs!
+  //         .restart(current.console.sessionContext)
+  //         .then(restarted => {
+  //           if (restarted) {
+  //             current.console.clear();
+  //           }
+  //           return restarted;
+  //         });
+  //     },
+  //     changeKernel: current =>
+  //       sessionDialogs!.selectKernel(
+  //         current.console.sessionContext,
+  //         translator
+  //       ),
+  //     shutdownKernel: current => current.console.sessionContext.shutdown()
+  //   } as IKernelMenu.IKernelUser<ConsolePanel>);
+
+  //   // Add a code runner to the Run menu.
+  //   mainMenu.runMenu.codeRunners.add({
+  //     tracker,
+  //     runLabel: (n: number) => trans.__('Run Cell'),
+  //     run: current => current.console.execute(true)
+  //   } as IRunMenu.ICodeRunner<ConsolePanel>);
+
+  //   // Add a clearer to the edit menu
+  //   mainMenu.editMenu.clearers.add({
+  //     tracker,
+  //     clearCurrentLabel: (n: number) => trans.__('Clear Console Cell'),
+  //     clearCurrent: (current: ConsolePanel) => {
+  //       return current.console.clear();
+  //     }
+  //   } as IEditMenu.IClearer<ConsolePanel>);
+  // }

   // For backwards compatibility and clarity, we explicitly label the run
   // keystroke with the actual effected change, rather than the generic
@@ -775,13 +775,13 @@ async function activateConsole(
     isToggled: args => args['interactionMode'] === interactionMode
   });

-  if (mainMenu) {
-    // Add kernel information to the application help menu.
-    mainMenu.helpMenu.kernelUsers.add({
-      tracker,
-      getKernel: current => current.sessionContext.session?.kernel
-    } as IHelpMenu.IKernelUser<ConsolePanel>);
-  }
+  // if (mainMenu) {
+  //   // Add kernel information to the application help menu.
+  //   mainMenu.helpMenu.kernelUsers.add({
+  //     tracker,
+  //     getKernel: current => current.sessionContext.session?.kernel
+  //   } as IHelpMenu.IKernelUser<ConsolePanel>);
+  // }

   return tracker;
 }

diff --git a/packages/debugger-extension/src/index.ts b/packages/debugger-extension/src/index.ts
index fe710a9d2a..659f5c0692 100644
--- a/packages/debugger-extension/src/index.ts
+++ b/packages/debugger-extension/src/index.ts
@@ -15,7 +15,7 @@ import {
   ICommandPalette,
   IThemeManager,
   MainAreaWidget,
-  sessionContextDialogs,
+  // sessionContextDialogs,
   WidgetTracker
 } from '@jupyterlab/apputils';
 import { IEditorServices } from '@jupyterlab/codeeditor';
@@ -35,7 +35,7 @@ import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
 import { ILoggerRegistry } from '@jupyterlab/logconsole';
 import {
   INotebookTracker,
-  NotebookActions,
+  // NotebookActions,
   NotebookPanel
 } from '@jupyterlab/notebook';
 import {
@@ -167,88 +167,88 @@ const files: JupyterFrontEndPlugin<void> = {
 /**
  * A plugin that provides visual debugging support for notebooks.
  */
-const notebooks: JupyterFrontEndPlugin<IDebugger.IHandler> = {
-  id: '@jupyterlab/debugger-extension:notebooks',
-  autoStart: true,
-  requires: [IDebugger, INotebookTracker, ITranslator],
-  optional: [ILabShell, ICommandPalette],
-  provides: IDebuggerHandler,
-  activate: (
-    app: JupyterFrontEnd,
-    service: IDebugger,
-    notebookTracker: INotebookTracker,
-    translator: ITranslator,
-    labShell: ILabShell | null,
-    palette: ICommandPalette | null
-  ): Debugger.Handler => {
-    const handler = new Debugger.Handler({
-      type: 'notebook',
-      shell: app.shell,
-      service
-    });
-
-    const trans = translator.load('jupyterlab');
-    app.commands.addCommand(Debugger.CommandIDs.restartDebug, {
-      label: trans.__('Restart Kernel and Debug…'),
-      caption: trans.__('Restart Kernel and Debug…'),
-      isEnabled: () => service.isStarted,
-      execute: async () => {
-        const state = service.getDebuggerState();
-        await service.stop();
-
-        const widget = notebookTracker.currentWidget;
-        if (!widget) {
-          return;
-        }
-
-        const { content, sessionContext } = widget;
-        const restarted = await sessionContextDialogs.restart(sessionContext);
-        if (!restarted) {
-          return;
-        }
-
-        await service.restoreDebuggerState(state);
-        await handler.updateWidget(widget, sessionContext.session);
-        await NotebookActions.runAll(content, sessionContext);
-      }
-    });
-
-    const updateHandlerAndCommands = async (
-      widget: NotebookPanel
-    ): Promise<void> => {
-      if (widget) {
-        const { sessionContext } = widget;
-        await sessionContext.ready;
-        await handler.updateContext(widget, sessionContext);
-      }
-      app.commands.notifyCommandChanged();
-    };
-
-    if (labShell) {
-      labShell.currentChanged.connect((_, update) => {
-        const widget = update.newValue;
-        if (widget instanceof NotebookPanel) {
-          void updateHandlerAndCommands(widget);
-        }
-      });
-    } else {
-      notebookTracker.currentChanged.connect((_, notebookPanel) => {
-        if (notebookPanel) {
-          void updateHandlerAndCommands(notebookPanel);
-        }
-      });
-    }
-
-    if (palette) {
-      palette.addItem({
-        category: 'Notebook Operations',
-        command: Debugger.CommandIDs.restartDebug
-      });
-    }
-
-    return handler;
-  }
-};
+// const notebooks: JupyterFrontEndPlugin<IDebugger.IHandler> = {
+//   id: '@jupyterlab/debugger-extension:notebooks',
+//   autoStart: true,
+//   requires: [IDebugger, INotebookTracker, ITranslator],
+//   optional: [ILabShell, ICommandPalette],
+//   provides: IDebuggerHandler,
+//   activate: (
+//     app: JupyterFrontEnd,
+//     service: IDebugger,
+//     notebookTracker: INotebookTracker,
+//     translator: ITranslator,
+//     labShell: ILabShell | null,
+//     palette: ICommandPalette | null
+//   ): Debugger.Handler => {
+//     const handler = new Debugger.Handler({
+//       type: 'notebook',
+//       shell: app.shell,
+//       service
+//     });
+
+//     const trans = translator.load('jupyterlab');
+//     app.commands.addCommand(Debugger.CommandIDs.restartDebug, {
+//       label: trans.__('Restart Kernel and Debug…'),
+//       caption: trans.__('Restart Kernel and Debug…'),
+//       isEnabled: () => service.isStarted,
+//       execute: async () => {
+//         const state = service.getDebuggerState();
+//         await service.stop();
+
+//         const widget = notebookTracker.currentWidget;
+//         if (!widget) {
+//           return;
+//         }
+
+//         const { content, sessionContext } = widget;
+//         const restarted = await sessionContextDialogs.restart(sessionContext);
+//         if (!restarted) {
+//           return;
+//         }
+
+//         await service.restoreDebuggerState(state);
+//         await handler.updateWidget(widget, sessionContext.session);
+//         await NotebookActions.runAll(content, sessionContext);
+//       }
+//     });
+
+//     const updateHandlerAndCommands = async (
+//       widget: NotebookPanel
+//     ): Promise<void> => {
+//       if (widget) {
+//         const { sessionContext } = widget;
+//         await sessionContext.ready;
+//         await handler.updateContext(widget, sessionContext);
+//       }
+//       app.commands.notifyCommandChanged();
+//     };
+
+//     if (labShell) {
+//       labShell.currentChanged.connect((_, update) => {
+//         const widget = update.newValue;
+//         if (widget instanceof NotebookPanel) {
+//           void updateHandlerAndCommands(widget);
+//         }
+//       });
+//     } else {
+//       notebookTracker.currentChanged.connect((_, notebookPanel) => {
+//         if (notebookPanel) {
+//           void updateHandlerAndCommands(notebookPanel);
+//         }
+//       });
+//     }
+
+//     if (palette) {
+//       palette.addItem({
+//         category: 'Notebook Operations',
+//         command: Debugger.CommandIDs.restartDebug
+//       });
+//     }
+
+//     return handler;
+//   }
+// };

 /**
  * A plugin that provides a debugger service.
@@ -313,7 +313,7 @@ const sources: JupyterFrontEndPlugin<IDebugger.ISources> = {
  */
 const variables: JupyterFrontEndPlugin<void> = {
   id: '@jupyterlab/debugger-extension:variables',
-  autoStart: true,
+  autoStart: false,
   requires: [IDebugger, IDebuggerHandler, ITranslator],
   optional: [IThemeManager, IRenderMimeRegistry],
   activate: (
@@ -868,7 +868,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
   service,
   consoles,
   files,
-  notebooks,
+  // notebooks,
   variables,
   sidebar,
   main,

diff --git a/packages/docmanager-extension/src/index.tsx b/packages/docmanager-extension/src/index.tsx
index fac8aadb2b..4a0a90ae55 100644
--- a/packages/docmanager-extension/src/index.tsx
+++ b/packages/docmanager-extension/src/index.tsx
@@ -342,49 +342,49 @@ export const pathStatusPlugin: JupyterFrontEndPlugin<void> = {
 /**
  * A plugin providing download commands in the file menu and command palette.
  */
-export const downloadPlugin: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/docmanager-extension:download',
-  autoStart: true,
-  requires: [ITranslator, IDocumentManager],
-  optional: [ICommandPalette],
-  activate: (
-    app: JupyterFrontEnd,
-    translator: ITranslator,
-    docManager: IDocumentManager,
-    palette: ICommandPalette | null
-  ) => {
-    const trans = translator.load('jupyterlab');
-    const { commands, shell } = app;
-    const isEnabled = () => {
-      const { currentWidget } = shell;
-      return !!(currentWidget && docManager.contextForWidget(currentWidget));
-    };
-    commands.addCommand(CommandIDs.download, {
-      label: trans.__('Download'),
-      caption: trans.__('Download the file to your computer'),
-      isEnabled,
-      execute: () => {
-        // Checks that shell.currentWidget is valid:
-        if (isEnabled()) {
-          const context = docManager.contextForWidget(shell.currentWidget!);
-          if (!context) {
-            return showDialog({
-              title: trans.__('Cannot Download'),
-              body: trans.__('No context found for current widget!'),
-              buttons: [Dialog.okButton({ label: trans.__('OK') })]
-            });
-          }
-          return context.download();
-        }
-      }
-    });
-
-    const category = trans.__('File Operations');
-    if (palette) {
-      palette.addItem({ command: CommandIDs.download, category });
-    }
-  }
-};
+// export const downloadPlugin: JupyterFrontEndPlugin<void> = {
+//   id: '@jupyterlab/docmanager-extension:download',
+//   autoStart: true,
+//   requires: [ITranslator, IDocumentManager],
+//   optional: [ICommandPalette],
+//   activate: (
+//     app: JupyterFrontEnd,
+//     translator: ITranslator,
+//     docManager: IDocumentManager,
+//     palette: ICommandPalette | null
+//   ) => {
+//     const trans = translator.load('jupyterlab');
+//     const { commands, shell } = app;
+//     const isEnabled = () => {
+//       const { currentWidget } = shell;
+//       return !!(currentWidget && docManager.contextForWidget(currentWidget));
+//     };
+//     commands.addCommand(CommandIDs.download, {
+//       label: trans.__('Download'),
+//       caption: trans.__('Download the file to your computer'),
+//       isEnabled,
+//       execute: () => {
+//         // Checks that shell.currentWidget is valid:
+//         if (isEnabled()) {
+//           const context = docManager.contextForWidget(shell.currentWidget!);
+//           if (!context) {
+//             return showDialog({
+//               title: trans.__('Cannot Download'),
+//               body: trans.__('No context found for current widget!'),
+//               buttons: [Dialog.okButton({ label: trans.__('OK') })]
+//             });
+//           }
+//           return context.download();
+//         }
+//       }
+//     });
+
+//     const category = trans.__('File Operations');
+//     if (palette) {
+//       palette.addItem({ command: CommandIDs.download, category });
+//     }
+//   }
+// };

 /**
  * A plugin providing open-browser-tab commands.
@@ -438,7 +438,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
   docManagerPlugin,
   pathStatusPlugin,
   savingStatusPlugin,
-  downloadPlugin,
+  // downloadPlugin,
   openBrowserTabPlugin
 ];
 export default plugins;

diff --git a/packages/docregistry/src/context.ts b/packages/docregistry/src/context.ts
index 966dadf146..f9bb12ed26 100644
--- a/packages/docregistry/src/context.ts
+++ b/packages/docregistry/src/context.ts
@@ -1047,6 +1047,8 @@ namespace Private {
   function createSaveNode(path: string): HTMLElement {
     const input = document.createElement('input');
     input.value = path;
-    return input;
+    const container = document.createElement('div');
+    container.appendChild(input);
+    return container;
   }
 }

diff --git a/packages/extensionmanager-extension/examples/listings/index.js b/packages/extensionmanager-extension/examples/listings/index.js
index 9d05d78fa7..19776fc525 100644
--- a/packages/extensionmanager-extension/examples/listings/index.js
+++ b/packages/extensionmanager-extension/examples/listings/index.js
@@ -37,7 +37,6 @@ window.addEventListener('load', async function () {
     require('@jupyterlab/shortcuts-extension'),
     require('@jupyterlab/statusbar-extension'),
     require('@jupyterlab/terminal-extension'),
-    require('@jupyterlab/theme-dark-extension'),
     require('@jupyterlab/theme-light-extension'),
     require('@jupyterlab/tooltip-extension'),
     require('@jupyterlab/ui-components-extension')

diff --git a/packages/extensionmanager-extension/examples/listings/package.json b/packages/extensionmanager-extension/examples/listings/package.json
index c7e3b09207..6dca5df7b2 100644
--- a/packages/extensionmanager-extension/examples/listings/package.json
+++ b/packages/extensionmanager-extension/examples/listings/package.json
@@ -34,7 +34,6 @@
     "@jupyterlab/shortcuts-extension": "^2.0.2",
     "@jupyterlab/statusbar-extension": "^2.0.2",
     "@jupyterlab/terminal-extension": "^2.0.2",
-    "@jupyterlab/theme-dark-extension": "^2.0.2",
     "@jupyterlab/theme-light-extension": "^2.0.2",
     "@jupyterlab/tooltip-extension": "^2.0.2",
     "@jupyterlab/ui-components-extension": "^2.0.2",

diff --git a/packages/extensionmanager-extension/schema/plugin.json b/packages/extensionmanager-extension/schema/plugin.json
index dda72c020d..3f2c190d75 100644
--- a/packages/extensionmanager-extension/schema/plugin.json
+++ b/packages/extensionmanager-extension/schema/plugin.json
@@ -28,7 +28,7 @@
     "enabled": {
       "title": "Enabled Status",
       "description": "Enables extension manager (requires Node.js/npm).\nWARNING: installing untrusted extensions may be unsafe.",
-      "default": true,
+      "default": false,
       "type": "boolean"
     },
     "disclaimed": {

diff --git a/packages/filebrowser-extension/src/index.ts b/packages/filebrowser-extension/src/index.ts
index b6c810262b..18d20e6bb7 100644
--- a/packages/filebrowser-extension/src/index.ts
+++ b/packages/filebrowser-extension/src/index.ts
@@ -29,7 +29,7 @@ import { IDocumentManager } from '@jupyterlab/docmanager';
 import { DocumentRegistry } from '@jupyterlab/docregistry';
 import {
   FileBrowser,
-  FileUploadStatus,
+  // FileUploadStatus,
   FilterFileBrowserModel,
   IFileBrowserCommands,
   IFileBrowserFactory,
@@ -39,18 +39,19 @@ import { Launcher } from '@jupyterlab/launcher';
 import { Contents } from '@jupyterlab/services';
 import { ISettingRegistry } from '@jupyterlab/settingregistry';
 import { IStateDB } from '@jupyterlab/statedb';
-import { IStatusBar } from '@jupyterlab/statusbar';
+// import { IStatusBar } from '@jupyterlab/statusbar';
 import { ITranslator } from '@jupyterlab/translation';
 import {
   addIcon,
   closeIcon,
   copyIcon,
   cutIcon,
-  downloadIcon,
+  // downloadIcon,
   editIcon,
   fileIcon,
   folderIcon,
-  linkIcon,
+  LabIcon,
+  // linkIcon,
   markdownIcon,
   newFolderIcon,
   pasteIcon,
@@ -60,8 +61,14 @@ import {
 } from '@jupyterlab/ui-components';
 import { find, IIterator, map, reduce, toArray } from '@lumino/algorithm';
 import { CommandRegistry } from '@lumino/commands';
-import { ContextMenu } from '@lumino/widgets';
+// import { ContextMenu } from '@lumino/widgets';
 import { JSONObject } from '@lumino/coreutils';
+import analysisSvgStr from '../style/icons/analysis.svg';
+
+const analysisIcon = new LabIcon({
+  name: 'analysis',
+  svgstr: analysisSvgStr
+});

 const FILE_BROWSER_FACTORY = 'FileBrowser';

@@ -176,10 +183,10 @@ const browser: JupyterFrontEndPlugin<void> = {
     }

     // Navigate to preferred-dir trait if found
-    const preferredPath = PageConfig.getOption('preferredPath');
-    if (preferredPath) {
-      await browser.model.cd(preferredPath);
-    }
+    // const preferredPath = PageConfig.getOption('preferredPath');
+    // if (preferredPath) {
+    //   await browser.model.cd(preferredPath);
+    // }

     addCommands(app, factory, translator, settingRegistry, commandPalette);

@@ -322,50 +329,50 @@ const factory: JupyterFrontEndPlugin<IFileBrowserFactory> = {
  * Users will still be able to retrieve files from the file download URLs the
  * server provides.
  */
-const downloadPlugin: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/filebrowser-extension:download',
-  requires: [IFileBrowserFactory, ITranslator],
-  autoStart: true,
-  activate: (
-    app: JupyterFrontEnd,
-    factory: IFileBrowserFactory,
-    translator: ITranslator
-  ): void => {
-    const trans = translator.load('jupyterlab');
-    const { commands } = app;
-    const { tracker } = factory;
-
-    commands.addCommand(CommandIDs.download, {
-      execute: () => {
-        const widget = tracker.currentWidget;
-
-        if (widget) {
-          return widget.download();
-        }
-      },
-      icon: downloadIcon.bindprops({ stylesheet: 'menuItem' }),
-      label: trans.__('Download')
-    });
-
-    commands.addCommand(CommandIDs.copyDownloadLink, {
-      execute: () => {
-        const widget = tracker.currentWidget;
-        if (!widget) {
-          return;
-        }
-
-        return widget.model.manager.services.contents
-          .getDownloadUrl(widget.selectedItems().next()!.path)
-          .then(url => {
-            Clipboard.copyToSystem(url);
-          });
-      },
-      icon: copyIcon.bindprops({ stylesheet: 'menuItem' }),
-      label: trans.__('Copy Download Link'),
-      mnemonic: 0
-    });
-  }
-};
+// const downloadPlugin: JupyterFrontEndPlugin<void> = {
+//   id: '@jupyterlab/filebrowser-extension:download',
+//   requires: [IFileBrowserFactory, ITranslator],
+//   autoStart: true,
+//   activate: (
+//     app: JupyterFrontEnd,
+//     factory: IFileBrowserFactory,
+//     translator: ITranslator
+//   ): void => {
+//     const trans = translator.load('jupyterlab');
+//     const { commands } = app;
+//     const { tracker } = factory;
+
+//     commands.addCommand(CommandIDs.download, {
+//       execute: () => {
+//         const widget = tracker.currentWidget;
+
+//         if (widget) {
+//           return widget.download();
+//         }
+//       },
+//       icon: downloadIcon.bindprops({ stylesheet: 'menuItem' }),
+//       label: trans.__('Download')
+//     });
+
+//     commands.addCommand(CommandIDs.copyDownloadLink, {
+//       execute: () => {
+//         const widget = tracker.currentWidget;
+//         if (!widget) {
+//           return;
+//         }
+
+//         return widget.model.manager.services.contents
+//           .getDownloadUrl(widget.selectedItems().next()!.path)
+//           .then(url => {
+//             Clipboard.copyToSystem(url);
+//           });
+//       },
+//       icon: copyIcon.bindprops({ stylesheet: 'menuItem' }),
+//       label: trans.__('Copy Download Link'),
+//       mnemonic: 0
+//     });
+//   }
+// };

 /**
  * A plugin to add the file browser widget to an ILabShell
@@ -398,7 +405,8 @@ const browserWidget: JupyterFrontEndPlugin<void> = {
     // Set attributes when adding the browser to the UI
     browser.node.setAttribute('role', 'region');
     browser.node.setAttribute('aria-label', trans.__('File Browser Section'));
-    browser.title.icon = folderIcon;
+    browser.title.icon = analysisIcon;
+    browser.title.label = '可视化编程';

     // Toolbar
     toolbarRegistry.registerFactory(
@@ -519,43 +527,43 @@ const browserWidget: JupyterFrontEndPlugin<void> = {
  * /user-redirect URL for JupyterHub), disable this plugin and replace it
  * with another implementation.
  */
-const shareFile: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/filebrowser-extension:share-file',
-  requires: [IFileBrowserFactory, ITranslator],
-  autoStart: true,
-  activate: (
-    app: JupyterFrontEnd,
-    factory: IFileBrowserFactory,
-    translator: ITranslator
-  ): void => {
-    const trans = translator.load('jupyterlab');
-    const { commands } = app;
-    const { tracker } = factory;
-
-    commands.addCommand(CommandIDs.copyShareableLink, {
-      execute: () => {
-        const widget = tracker.currentWidget;
-        const model = widget?.selectedItems().next();
-        if (!model) {
-          return;
-        }
-
-        Clipboard.copyToSystem(
-          PageConfig.getUrl({
-            workspace: PageConfig.defaultWorkspace,
-            treePath: model.path,
-            toShare: true
-          })
-        );
-      },
-      isVisible: () =>
-        !!tracker.currentWidget &&
-        toArray(tracker.currentWidget.selectedItems()).length === 1,
-      icon: linkIcon.bindprops({ stylesheet: 'menuItem' }),
-      label: trans.__('Copy Shareable Link')
-    });
-  }
-};
+// const shareFile: JupyterFrontEndPlugin<void> = {
+//   id: '@jupyterlab/filebrowser-extension:share-file',
+//   requires: [IFileBrowserFactory, ITranslator],
+//   autoStart: true,
+//   activate: (
+//     app: JupyterFrontEnd,
+//     factory: IFileBrowserFactory,
+//     translator: ITranslator
+//   ): void => {
+//     const trans = translator.load('jupyterlab');
+//     const { commands } = app;
+//     const { tracker } = factory;
+
+//     commands.addCommand(CommandIDs.copyShareableLink, {
+//       execute: () => {
+//         const widget = tracker.currentWidget;
+//         const model = widget?.selectedItems().next();
+//         if (!model) {
+//           return;
+//         }
+
+//         Clipboard.copyToSystem(
+//           PageConfig.getUrl({
+//             workspace: PageConfig.defaultWorkspace,
+//             treePath: model.path,
+//             toShare: true
+//           })
+//         );
+//       },
+//       isVisible: () =>
+//         !!tracker.currentWidget &&
+//         toArray(tracker.currentWidget.selectedItems()).length === 1,
+//       icon: linkIcon.bindprops({ stylesheet: 'menuItem' }),
+//       label: trans.__('Copy Shareable Link')
+//     });
+//   }
+// };

 /**
  * The "Open With" context menu.
@@ -563,51 +571,51 @@ const shareFile: JupyterFrontEndPlugin<void> = {
  * This is its own plugin in case you would like to disable this feature.
  * e.g. jupyter labextension disable @jupyterlab/filebrowser-extension:open-with
  */
-const openWithPlugin: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/filebrowser-extension:open-with',
-  requires: [IFileBrowserFactory],
-  autoStart: true,
-  activate: (app: JupyterFrontEnd, factory: IFileBrowserFactory): void => {
-    const { docRegistry } = app;
-    const { tracker } = factory;
-
-    function updateOpenWithMenu(contextMenu: ContextMenu) {
-      const openWith =
-        contextMenu.menu.items.find(
-          item =>
-            item.type === 'submenu' &&
-            item.submenu?.id === 'jp-contextmenu-open-with'
-        )?.submenu ?? null;
-
-      if (!openWith) {
-        return; // Bail early if the open with menu is not displayed
-      }
-
-      // clear the current menu items
-      openWith.clearItems();
-
-      // get the widget factories that could be used to open all of the items
-      // in the current filebrowser selection
-      const factories = tracker.currentWidget
-        ? Private.OpenWith.intersection<string>(
-            map(tracker.currentWidget.selectedItems(), i => {
-              return Private.OpenWith.getFactories(docRegistry, i);
-            })
-          )
-        : new Set<string>();
-
-      // make new menu items from the widget factories
-      factories.forEach(factory => {
-        openWith.addItem({
-          args: { factory: factory },
-          command: CommandIDs.open
-        });
-      });
-    }
-
-    app.contextMenu.opened.connect(updateOpenWithMenu);
-  }
-};
+// const openWithPlugin: JupyterFrontEndPlugin<void> = {
+//   id: '@jupyterlab/filebrowser-extension:open-with',
+//   requires: [IFileBrowserFactory],
+//   autoStart: true,
+//   activate: (app: JupyterFrontEnd, factory: IFileBrowserFactory): void => {
+//     const { docRegistry } = app;
+//     const { tracker } = factory;
+
+//     function updateOpenWithMenu(contextMenu: ContextMenu) {
+//       const openWith =
+//         contextMenu.menu.items.find(
+//           item =>
+//             item.type === 'submenu' &&
+//             item.submenu?.id === 'jp-contextmenu-open-with'
+//         )?.submenu ?? null;
+
+//       if (!openWith) {
+//         return; // Bail early if the open with menu is not displayed
+//       }
+
+//       // clear the current menu items
+//       openWith.clearItems();
+
+//       // get the widget factories that could be used to open all of the items
+//       // in the current filebrowser selection
+//       const factories = tracker.currentWidget
+//         ? Private.OpenWith.intersection<string>(
+//             map(tracker.currentWidget.selectedItems(), i => {
+//               return Private.OpenWith.getFactories(docRegistry, i);
+//             })
+//           )
+//         : new Set<string>();
+
+//       // make new menu items from the widget factories
+//       factories.forEach(factory => {
+//         openWith.addItem({
+//           args: { factory: factory },
+//           command: CommandIDs.open
+//         });
+//       });
+//     }
+
+//     app.contextMenu.opened.connect(updateOpenWithMenu);
+//   }
+// };

 /**
  * The "Open in New Browser Tab" context menu.
@@ -618,181 +626,181 @@ const openWithPlugin: JupyterFrontEndPlugin<void> = {
  * Note: If disabling this, you may also want to disable:
  * @jupyterlab/docmanager-extension:open-browser-tab
  */
-const openBrowserTabPlugin: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/filebrowser-extension:open-browser-tab',
-  requires: [IFileBrowserFactory, ITranslator],
-  autoStart: true,
-  activate: (
-    app: JupyterFrontEnd,
-    factory: IFileBrowserFactory,
-    translator: ITranslator
-  ): void => {
-    const { commands } = app;
-    const trans = translator.load('jupyterlab');
-    const { tracker } = factory;
-
-    commands.addCommand(CommandIDs.openBrowserTab, {
-      execute: args => {
-        const widget = tracker.currentWidget;
-
-        if (!widget) {
-          return;
-        }
-
-        const mode = args['mode'] as string | undefined;
-
-        return Promise.all(
-          toArray(
-            map(widget.selectedItems(), item => {
-              if (mode === 'single-document') {
-                const url = PageConfig.getUrl({
-                  mode: 'single-document',
-                  treePath: item.path
-                });
-                const opened = window.open();
-                if (opened) {
-                  opened.opener = null;
-                  opened.location.href = url;
-                } else {
-                  throw new Error('Failed to open new browser tab.');
-                }
-              } else {
-                return commands.execute('docmanager:open-browser-tab', {
-                  path: item.path
-                });
-              }
-            })
-          )
-        );
-      },
-      icon: addIcon.bindprops({ stylesheet: 'menuItem' }),
-      label: args =>
-        args['mode'] === 'single-document'
-          ? trans.__('Open in Simple Mode')
-          : trans.__('Open in New Browser Tab'),
-      mnemonic: 0
-    });
-  }
-};
+// const openBrowserTabPlugin: JupyterFrontEndPlugin<void> = {
+//   id: '@jupyterlab/filebrowser-extension:open-browser-tab',
+//   requires: [IFileBrowserFactory, ITranslator],
+//   autoStart: true,
+//   activate: (
+//     app: JupyterFrontEnd,
+//     factory: IFileBrowserFactory,
+//     translator: ITranslator
+//   ): void => {
+//     const { commands } = app;
+//     const trans = translator.load('jupyterlab');
+//     const { tracker } = factory;
+
+//     commands.addCommand(CommandIDs.openBrowserTab, {
+//       execute: args => {
+//         const widget = tracker.currentWidget;
+
+//         if (!widget) {
+//           return;
+//         }
+
+//         const mode = args['mode'] as string | undefined;
+
+//         return Promise.all(
+//           toArray(
+//             map(widget.selectedItems(), item => {
+//               if (mode === 'single-document') {
+//                 const url = PageConfig.getUrl({
+//                   mode: 'single-document',
+//                   treePath: item.path
+//                 });
+//                 const opened = window.open();
+//                 if (opened) {
+//                   opened.opener = null;
+//                   opened.location.href = url;
+//                 } else {
+//                   throw new Error('Failed to open new browser tab.');
+//                 }
+//               } else {
+//                 return commands.execute('docmanager:open-browser-tab', {
+//                   path: item.path
+//                 });
+//               }
+//             })
+//           )
+//         );
+//       },
+//       icon: addIcon.bindprops({ stylesheet: 'menuItem' }),
+//       label: args =>
+//         args['mode'] === 'single-document'
+//           ? trans.__('Open in Simple Mode')
+//           : trans.__('Open in New Browser Tab'),
+//       mnemonic: 0
+//     });
+//   }
+// };

 /**
  * A plugin providing file upload status.
  */
-export const fileUploadStatus: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/filebrowser-extension:file-upload-status',
-  autoStart: true,
-  requires: [IFileBrowserFactory, ITranslator],
-  optional: [IStatusBar],
-  activate: (
-    app: JupyterFrontEnd,
-    browser: IFileBrowserFactory,
-    translator: ITranslator,
-    statusBar: IStatusBar | null
-  ) => {
-    if (!statusBar) {
-      // Automatically disable if statusbar missing
-      return;
-    }
-    const item = new FileUploadStatus({
-      tracker: browser.tracker,
-      translator
-    });
-
-    statusBar.registerStatusItem(
-      '@jupyterlab/filebrowser-extension:file-upload-status',
-      {
-        item,
-        align: 'middle',
-        isActive: () => {
-          return !!item.model && item.model.items.length > 0;
-        },
-        activeStateChanged: item.model.stateChanged
-      }
-    );
-  }
-};
+// export const fileUploadStatus: JupyterFrontEndPlugin<void> = {
+//   id: '@jupyterlab/filebrowser-extension:file-upload-status',
+//   autoStart: true,
+//   requires: [IFileBrowserFactory, ITranslator],
+//   optional: [IStatusBar],
+//   activate: (
+//     app: JupyterFrontEnd,
+//     browser: IFileBrowserFactory,
+//     translator: ITranslator,
+//     statusBar: IStatusBar | null
+//   ) => {
+//     if (!statusBar) {
+//       // Automatically disable if statusbar missing
+//       return;
+//     }
+//     const item = new FileUploadStatus({
+//       tracker: browser.tracker,
+//       translator
+//     });
+
+//     statusBar.registerStatusItem(
+//       '@jupyterlab/filebrowser-extension:file-upload-status',
+//       {
+//         item,
+//         align: 'middle',
+//         isActive: () => {
+//           return !!item.model && item.model.items.length > 0;
+//         },
+//         activeStateChanged: item.model.stateChanged
+//       }
+//     );
+//   }
+// };

 /**
  * A plugin to open files from remote URLs
  */
-const openUrlPlugin: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/filebrowser-extension:open-url',
-  autoStart: true,
-  requires: [IFileBrowserFactory, ITranslator],
-  optional: [ICommandPalette],
-  activate: (
-    app: JupyterFrontEnd,
-    factory: IFileBrowserFactory,
-    translator: ITranslator,
-    palette: ICommandPalette | null
-  ) => {
-    const { commands } = app;
-    const trans = translator.load('jupyterlab');
-    const { defaultBrowser: browser } = factory;
-    const command = CommandIDs.openUrl;
-
-    commands.addCommand(command, {
-      label: args =>
-        args.url ? trans.__('Open %1', args.url) : trans.__('Open from URL…'),
-      caption: args =>
-        args.url ? trans.__('Open %1', args.url) : trans.__('Open from URL'),
-      execute: async args => {
-        let url: string | undefined = (args?.url as string) ?? '';
-        if (!url) {
-          url =
-            (
-              await InputDialog.getText({
-                label: trans.__('URL'),
-                placeholder: 'https://example.com/path/to/file',
-                title: trans.__('Open URL'),
-                okLabel: trans.__('Open')
-              })
-            ).value ?? undefined;
-        }
-        if (!url) {
-          return;
-        }
-
-        let type = '';
-        let blob;
-
-        // fetch the file from the URL
-        try {
-          const req = await fetch(url);
-          blob = await req.blob();
-          type = req.headers.get('Content-Type') ?? '';
-        } catch (reason) {
-          if (reason.response && reason.response.status !== 200) {
-            reason.message = trans.__('Could not open URL: %1', url);
-          }
-          return showErrorMessage(trans.__('Cannot fetch'), reason);
-        }
-
-        // upload the content of the file to the server
-        try {
-          const name = PathExt.basename(url);
-          const file = new File([blob], name, { type });
-          const model = await browser.model.upload(file);
-          return commands.execute('docmanager:open', {
-            path: model.path
-          });
-        } catch (error) {
-          return showErrorMessage(
-            trans._p('showErrorMessage', 'Upload Error'),
-            error
-          );
-        }
-      }
-    });
-
-    if (palette) {
-      palette.addItem({
-        command,
-        category: trans.__('File Operations')
-      });
-    }
-  }
-};
+// const openUrlPlugin: JupyterFrontEndPlugin<void> = {
+//   id: '@jupyterlab/filebrowser-extension:open-url',
+//   autoStart: true,
+//   requires: [IFileBrowserFactory, ITranslator],
+//   optional: [ICommandPalette],
+//   activate: (
+//     app: JupyterFrontEnd,
+//     factory: IFileBrowserFactory,
+//     translator: ITranslator,
+//     palette: ICommandPalette | null
+//   ) => {
+//     const { commands } = app;
+//     const trans = translator.load('jupyterlab');
+//     const { defaultBrowser: browser } = factory;
+//     const command = CommandIDs.openUrl;
+
+//     commands.addCommand(command, {
+//       label: args =>
+//         args.url ? trans.__('Open %1', args.url) : trans.__('Open from URL…'),
+//       caption: args =>
+//         args.url ? trans.__('Open %1', args.url) : trans.__('Open from URL'),
+//       execute: async args => {
+//         let url: string | undefined = (args?.url as string) ?? '';
+//         if (!url) {
+//           url =
+//             (
+//               await InputDialog.getText({
+//                 label: trans.__('URL'),
+//                 placeholder: 'https://example.com/path/to/file',
+//                 title: trans.__('Open URL'),
+//                 okLabel: trans.__('Open')
+//               })
+//             ).value ?? undefined;
+//         }
+//         if (!url) {
+//           return;
+//         }
+
+//         let type = '';
+//         let blob;
+
+//         // fetch the file from the URL
+//         try {
+//           const req = await fetch(url);
+//           blob = await req.blob();
+//           type = req.headers.get('Content-Type') ?? '';
+//         } catch (reason) {
+//           if (reason.response && reason.response.status !== 200) {
+//             reason.message = trans.__('Could not open URL: %1', url);
+//           }
+//           return showErrorMessage(trans.__('Cannot fetch'), reason);
+//         }
+
+//         // upload the content of the file to the server
+//         try {
+//           const name = PathExt.basename(url);
+//           const file = new File([blob], name, { type });
+//           const model = await browser.model.upload(file);
+//           return commands.execute('docmanager:open', {
+//             path: model.path
+//           });
+//         } catch (error) {
+//           return showErrorMessage(
+//             trans._p('showErrorMessage', 'Upload Error'),
+//             error
+//           );
+//         }
+//       }
+//     });
+
+//     if (palette) {
+//       palette.addItem({
+//         command,
+//         category: trans.__('File Operations')
+//       });
+//     }
+//   }
+// };

 /**
  * Add the main file browser commands to the application's command registry.
@@ -1342,13 +1350,13 @@ namespace Private {
 const plugins: JupyterFrontEndPlugin<any>[] = [
   factory,
   browser,
-  shareFile,
-  fileUploadStatus,
-  downloadPlugin,
-  browserWidget,
-  openWithPlugin,
-  openBrowserTabPlugin,
-  openUrlPlugin
+  // shareFile,
+  // fileUploadStatus,
+  // downloadPlugin,
+  browserWidget
+  // openWithPlugin,
+  // openBrowserTabPlugin,
+  // openUrlPlugin
 ];
 export default plugins;
  • 侧边栏图标改动
diff --git a/packages/filebrowser-extension/src/svg.d.ts b/packages/filebrowser-extension/src/svg.d.ts
new file mode 100644
index 0000000000..0ca27598c5
--- /dev/null
+++ b/packages/filebrowser-extension/src/svg.d.ts
@@ -0,0 +1,4 @@
+declare module "*.svg" {
+  const value: string;
+  export default value;
+}
diff --git a/packages/filebrowser-extension/style/icons/analysis.svg b/packages/filebrowser-extension/style/icons/analysis.svg
new file mode 100644
index 0000000000..780413c090
--- /dev/null
+++ b/packages/filebrowser-extension/style/icons/analysis.svg
@@ -0,0 +1,5 @@
+<svg width="4.2214mm" height="4.2302mm" version="1.1" viewBox="0 0 4.2214 4.2302" xmlns="http://www.w3.org/2000/svg">
+  <path fill="#000000" class="jp-icon3 jp-icon-selectable"
+    d="m3.6685 2.0479a0.13229 0.13229 0 0 0-0.074931 0.026355 0.13229 0.13229 0 0 0-0.027388 0.18552l0.44752 0.60151a0.13229 0.13229 0 0 0 0.18552 0.027389 0.13229 0.13229 0 0 0 0.02222-0.021188v-0.17105l-0.4439-0.5948a0.13229 0.13229 0 0 0-0.0863-0.05271 0.13229 0.13229 0 0 0-0.022738-0.00103zm-0.648-1.9296a1.066 1.066 0 0 0-1.0661 1.0661 1.066 1.066 0 0 0 1.0661 1.0661 1.066 1.066 0 0 0 1.0661-1.0661 1.066 1.066 0 0 0-1.0661-1.0661zm0 0.051676a1.0142 1.0142 0 0 1 1.0139 1.0144 1.0142 1.0142 0 0 1-1.0139 1.0144 1.0142 1.0142 0 0 1-1.0144-1.0144 1.0142 1.0142 0 0 1 1.0144-1.0144zm-1.4834 3.2988v0.60938h0.26562v-0.60938zm-0.68944 0.4978a0.1325 0.1325 0 0 0-0.13281 0.13281 0.1325 0.1325 0 0 0 0.13281 0.13086h1.707a0.1325 0.1325 0 0 0 0.13281-0.13086 0.1325 0.1325 0 0 0-0.13281-0.13281zm-0.66576-3.6286c-0.098598 0-0.17777 0.079169-0.17777 0.17777v2.8448c0 0.098598 0.079169 0.17828 0.17777 0.17828h3.0949c0.098598 0 0.17777-0.079685 0.17777-0.17828v-0.86868h-0.22996v0.67903c0 0.085436-0.069077 0.154-0.15451 0.154h-2.6815c-0.085436 0-0.154-0.06856-0.154-0.154v-2.465c0-0.085436 0.06856-0.15451 0.154-0.15451h1.3715v-0.21342zm2.8309-0.33796c-0.65642-1.4612e-8 -1.1885 0.53212-1.1885 1.1885 1.188e-4 0.24219 0.074223 0.47857 0.21239 0.67746 0.00764 0.018057 0.019516 0.034009 0.034623 0.046508 0.22473 0.2926 0.5726 0.46423 0.94153 0.46458 0.20986-6e-5 0.41597-0.055689 0.59737-0.16123 0.00235 0.00547 0.0053 0.010668 0.00878 0.015503l0.45114 0.62115c0.026992 0.036636 0.078554 0.044497 0.11524 0.017571 0.036636-0.026992 0.044497-0.078554 0.017571-0.11524l-0.45114-0.61906c-0.00249-0.00348-0.00526-0.00677-0.00827-0.00982 0.28892-0.22521 0.45786-0.57107 0.45786-0.93741 0-0.65642-0.53212-1.1885-1.1885-1.1885zm0 0.16795c0.56366 1e-8 1.0206 0.45693 1.0206 1.0206 0 0.56366-0.45693 1.0206-1.0206 1.0206-0.28641-4.41e-5 -0.55961-0.12043-0.75292-0.33176l0.57567-0.75344c0.041476-0.053839 0.031025-0.13118-0.023254-0.17208-0.053488-0.040071-0.12925-0.029707-0.17001 0.023254l-0.53072 0.69193c-0.07831-0.14735-0.1193-0.31165-0.11937-0.47852 0-0.56366 0.45693-1.0206 1.0206-1.0206zm-2.8707 0.17001c-0.078728 0-0.14211 0.060602-0.14211 0.13539v2.9337c0 0.074799 0.06338 0.13487 0.14211 0.13487h1.4454v0.52347h-0.75807c-0.044899 4.497e-4 -0.081186 0.036747-0.081647 0.081647 1.8045e-4 0.045103 0.036546 0.0817 0.081647 0.082165h1.7446c0.0451-4.762e-4 0.081467-0.037062 0.081647-0.082165-4.498e-4 -0.044899-0.036748-0.081186-0.081647-0.081647h-0.82268v-0.52347h1.5689c0.078728 0 0.14211-0.060094 0.14211-0.13487l-0.0077522-0.91518-0.15141 0.018086v0.76223c0 0.067899-0.0577 0.12247-0.12919 0.12247h-2.8855c-0.07149 0-0.12919-0.054503-0.12919-0.12247v-0.7028c8.7629e-4 -6.694e-4 0.001738-0.00136 0.002584-0.00207l0.9591-1.1131 0.5059 1.0583c0.024655 0.053403 0.098384 0.05894 0.13074 0.00982l0.27233-0.3948c-0.035803-0.037507-0.069447-0.077016-0.10077-0.11834l-0.2222 0.32194-0.50021-1.0449c-0.022933-0.049186-0.088894-0.058491-0.12454-0.01757l-0.92293 1.0723v-1.7327c0-0.067899 0.0577-0.12299 0.12919-0.12299h1.4684l0.00332-0.14728z"
+    style="-inkscape-stroke:none" />
+</svg>
\ No newline at end of file
  • 将原来的文件浏览器改为编程列表, 修改布局和样式
diff --git a/packages/filebrowser/src/browser.ts b/packages/filebrowser/src/browser.ts
index d7fde430e2..fbe49b2562 100644
--- a/packages/filebrowser/src/browser.ts
+++ b/packages/filebrowser/src/browser.ts
@@ -106,10 +106,15 @@ export class FileBrowser extends Widget {
     this._filenameSearcher.addClass(FILTERBOX_CLASS);
     this.listing.addClass(LISTING_CLASS);

+    const header = new Widget();
+    header.node.textContent = '编程列表';
+    header.addClass('jp-FileBrowser-header');
+
     this.layout = new PanelLayout();
-    this.layout.addWidget(this.toolbar);
-    this.layout.addWidget(this._filenameSearcher);
-    this.layout.addWidget(this.crumbs);
+    this.layout.addWidget(header);
+    // this.layout.addWidget(this.toolbar);
+    // this.layout.addWidget(this._filenameSearcher);
+    // this.layout.addWidget(this.crumbs);
     this.layout.addWidget(this.listing);

     if (options.restore !== false) {
@@ -180,12 +185,12 @@ export class FileBrowser extends Widget {
     });
     this._filenameSearcher.addClass(FILTERBOX_CLASS);

-    this.layout.removeWidget(this._filenameSearcher);
-    this.layout.removeWidget(this.crumbs);
+    // this.layout.removeWidget(this._filenameSearcher);
+    // this.layout.removeWidget(this.crumbs);
     this.layout.removeWidget(this.listing);

-    this.layout.addWidget(this._filenameSearcher);
-    this.layout.addWidget(this.crumbs);
+    // this.layout.addWidget(this._filenameSearcher);
+    // this.layout.addWidget(this.crumbs);
     this.layout.addWidget(this.listing);
   }

diff --git a/packages/filebrowser/src/crumbs.ts b/packages/filebrowser/src/crumbs.ts
index b67e132a31..cd098f37fb 100644
--- a/packages/filebrowser/src/crumbs.ts
+++ b/packages/filebrowser/src/crumbs.ts
@@ -73,8 +73,9 @@ export class BreadCrumbs extends Widget {
     this.addClass(BREADCRUMB_CLASS);
     this._crumbs = Private.createCrumbs();
     this._crumbSeps = Private.createCrumbSeparators();
-    const hasPreferred = PageConfig.getOption('preferredPath');
-    this._hasPreferred = hasPreferred && hasPreferred !== '/' ? true : false;
+    // const hasPreferred = PageConfig.getOption('preferredPath');
+    // this._hasPreferred = hasPreferred && hasPreferred !== '/' ? true : false;
+    this._hasPreferred = false;
     if (this._hasPreferred) {
       this.node.appendChild(this._crumbs[Private.Crumb.Preferred]);
     }

diff --git a/packages/filebrowser/src/directoryswitcher.ts b/packages/filebrowser/src/directoryswitcher.ts
new file mode 100644
index 0000000000..2a96059486
--- /dev/null
+++ b/packages/filebrowser/src/directoryswitcher.ts
@@ -0,0 +1,19 @@
+import { Widget } from '@lumino/widgets';
+
+export default class DirectorySwitcher extends Widget {
+  constructor(options: Widget.IOptions) {
+    super(options);
+    this._createButtons();
+  }
+
+  private _createButtons() {
+    const btn1 = document.createElement('button');
+    btn1.textContent = '开发目录';
+    btn1.classList.add('jldbq-btn', 'jldbq-btn-current');
+    this.node.appendChild(btn1);
+    const btn2 = document.createElement('button');
+    btn2.textContent = '通用算子库';
+    btn2.classList.add('jldbq-btn');
+    this.node.appendChild(btn2);
+  }
+}

diff --git a/packages/filebrowser/style/base.css b/packages/filebrowser/style/base.css
index 940da54f23..1973c3f35a 100644
--- a/packages/filebrowser/style/base.css
+++ b/packages/filebrowser/style/base.css
@@ -36,6 +36,13 @@
   justify-content: flex-start;
 }

+.jp-FileBrowser-header {
+  margin: 5px;
+  padding: 15px;
+  font-weight: bold;
+  border-bottom: 1px solid #dbe3ea;
+}
+
 .jp-BreadCrumbs {
   flex: 0 0 auto;
   margin: 8px 12px 8px 12px;
@@ -127,13 +134,9 @@
   outline: 0;
 }

-.jp-DirListing:focus-visible {
-  border: 1px solid var(--jp-brand-color1);
-}
-
 .jp-DirListing-header {
   flex: 0 0 auto;
-  display: flex;
+  display: none;
   flex-direction: row;
   overflow: hidden;
   border-top: var(--jp-border-width) solid var(--jp-border-color2);
@@ -190,7 +193,6 @@
   padding: 0;
   list-style-type: none;
   overflow: auto;
-  background-color: var(--jp-layout-color1);
 }

 .jp-DirListing-content mark {
@@ -251,6 +253,7 @@
 }

 .jp-DirListing-itemModified {
+  display: none;
   flex: 0 0 125px;
   text-align: right;
 }
  • 添加数据资产相关代码
diff --git a/packages/jldbq-extenison/package.json b/packages/jldbq-extenison/package.json
new file mode 100644
index 0000000000..02e5c0f464
--- /dev/null
+++ b/packages/jldbq-extenison/package.json
@@ -0,0 +1,61 @@
+{
+  "name": "@jupyterlab/jldbq-extension",
+  "version": "1.0.0",
+  "description": "JLDBQ Extension",
+  "homepage": "",
+  "license": "MIT",
+  "author": "yili",
+  "sideEffects": [
+    "style/**/*.css",
+    "style/index.js"
+  ],
+  "main": "lib/index.js",
+  "types": "lib/index.d.ts",
+  "style": "style/index.css",
+  "directories": {
+    "lib": "lib/"
+  },
+  "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}",
+    "schema/*.json"
+  ],
+  "scripts": {
+    "build": "tsc -b",
+    "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo",
+    "docs": "typedoc src",
+    "watch": "tsc -b --watch"
+  },
+  "dependencies": {
+    "@emotion/react": "^11.9.0",
+    "@emotion/styled": "^11.8.1",
+    "@jupyterlab/application": "^3.4.3",
+    "@jupyterlab/apputils": "^3.4.3",
+    "@jupyterlab/settingregistry": "^3.4.3",
+    "@jupyterlab/ui-components": "^3.4.3",
+    "@lumino/signaling": "^1.10.0",
+    "@lumino/widgets": "^1.30.0",
+    "@mui/icons-material": "^5.8.0",
+    "@mui/lab": "^5.0.0-alpha.83",
+    "@mui/material": "^5.8.1",
+    "@silevis/reactgrid": "^4.0.3",
+    "antd": "^4.22.3",
+    "moment": "^2.29.4",
+    "react": "^17.0.1",
+    "react-select": "^5.4.0",
+    "swr": "^1.3.0"
+  },
+  "devDependencies": {
+    "rimraf": "~3.0.0",
+    "typedoc": "~0.21.2",
+    "typescript": "~4.1.3"
+  },
+  "publishConfig": {
+    "access": "public"
+  },
+  "jupyterlab": {
+    "extension": true,
+    "schemaDir": "schema"
+  },
+  "styleModule": "style/index.js"
+}
  • 数据资产前端连接后端的配置项
diff --git a/packages/jldbq-extenison/schema/plugin.json b/packages/jldbq-extenison/schema/plugin.json
new file mode 100644
index 0000000000..8a5b32c902
--- /dev/null
+++ b/packages/jldbq-extenison/schema/plugin.json
@@ -0,0 +1,14 @@
+{
+  "title": "JLDBQ",
+  "description": "JLDBQ settings.",
+  "type": "object",
+  "properties": {
+    "flaskBackend": {
+      "title": "Flask Backend address.",
+      "description": "Flask Backend address.",
+      "type": "string",
+      "default": "http://127.0.0.1:5000"
+    }
+  },
+  "additionalProperties": false
+}
  • 实现数据预览的表格
diff --git a/packages/jldbq-extenison/src/DataTable.tsx b/packages/jldbq-extenison/src/DataTable.tsx
new file mode 100644
index 0000000000..2f797b7356
--- /dev/null
+++ b/packages/jldbq-extenison/src/DataTable.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { Cell, Column, Id, ReactGrid, Row } from '@silevis/reactgrid';
+import { useMemo, useState } from 'react';
+import { ITableData } from './api/datasource';
+import '@silevis/reactgrid/styles.css';
+
+interface IProps {
+  data: ITableData;
+}
+
+const getText = (value: any): string => {
+  if (value === null || value === undefined) {
+    return '';
+  } else if (typeof value === 'object') {
+    return JSON.stringify(value);
+  } else {
+    return value.toString();
+  }
+};
+
+const DataTable = ({ data }: IProps): JSX.Element => {
+  const [cols, setCols] = useState<Column[]>(() =>
+    Object.keys(data).map(columnId => {
+      return { columnId, width: 100, resizable: true };
+    })
+  );
+
+  const rows = useMemo((): Row<Cell>[] => {
+    const colNames = Object.keys(data);
+    const header = {
+      rowId: 'header',
+      cells: colNames.map(text => {
+        return {
+          text,
+          type: 'header'
+        };
+      })
+    };
+    const dataRows = Object.keys(data[colNames[0]]).map(rowId => {
+      return {
+        rowId,
+        cells: colNames.map(c => {
+          return {
+            type: 'text',
+            text: getText(data[c][rowId]),
+            nonEditable: true
+          };
+        })
+      };
+    });
+    return [header, ...dataRows];
+  }, [data]);
+
+  const handleColumnResize = (ci: Id, width: number) => {
+    setCols(prevColumns => {
+      const columnIndex = prevColumns.findIndex(el => el.columnId === ci);
+      const resizedColumn = prevColumns[columnIndex];
+      const updatedColumn = { ...resizedColumn, width };
+      prevColumns[columnIndex] = updatedColumn;
+      return [...prevColumns];
+    });
+  };
+
+  return (
+    <div style={{ width: '100%', height: '100%', overflow: 'scroll' }}>
+      <ReactGrid
+        enableRangeSelection
+        stickyTopRows={1}
+        rows={rows}
+        columns={cols}
+        onColumnResized={handleColumnResize}
+      />
+    </div>
+  );
+};
+
+export default DataTable;

diff --git a/packages/jldbq-extenison/src/DataTableWidget.tsx b/packages/jldbq-extenison/src/DataTableWidget.tsx
new file mode 100644
index 0000000000..ff8ed85207
--- /dev/null
+++ b/packages/jldbq-extenison/src/DataTableWidget.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { ReactWidget } from '@jupyterlab/apputils';
+import { Widget } from '@lumino/widgets';
+import DataTable from './DataTable';
+import { fetchTableContent, IDatasource, ITableData } from './api/datasource';
+
+class DataTableWidget extends ReactWidget {
+  constructor(
+    datasource: IDatasource,
+    tableName: string,
+    options?: Widget.IOptions
+  ) {
+    super(options);
+    void this._requestData(datasource, tableName);
+  }
+
+  private _requestData = async (datasource: IDatasource, tableName: string) => {
+    const res = await fetchTableContent(datasource, tableName);
+    if (res === 'error') {
+      this._err = res;
+    } else {
+      this._data = res;
+    }
+    this.update();
+  };
+
+  render(): JSX.Element {
+    if (this._err) {
+      return <div>Error</div>;
+    } else if (!this._data) {
+      return <div>Loading ...</div>;
+    } else {
+      return <DataTable data={this._data} />;
+    }
+  }
+
+  private _data: ITableData | null = null;
+  private _err: any;
+}
+
+export default DataTableWidget;
  • 实现数据源管理面板
diff --git a/packages/jldbq-extenison/src/DataView.tsx b/packages/jldbq-extenison/src/DataView.tsx
new file mode 100644
index 0000000000..b2af5d4cf8
--- /dev/null
+++ b/packages/jldbq-extenison/src/DataView.tsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import { useState } from 'react';
+
+interface IProps {
+  dataSourceElement: JSX.Element;
+  dataSyncElement: JSX.Element;
+}
+
+type ViewType = 'datasource' | 'datasync';
+
+const SwitchButton: React.FunctionComponent<{
+  viewType: ViewType;
+  current: ViewType;
+  label: string;
+  onClick: (t: ViewType) => void;
+}> = props => {
+  return (
+    <button
+      type="button"
+      className={
+        'jldbq-btn ' +
+        (props.viewType === props.current ? 'jldbq-btn-current' : '')
+      }
+      onClick={() => props.onClick(props.viewType)}
+    >
+      {props.label}
+    </button>
+  );
+};
+
+const DataView: React.FunctionComponent<IProps> = props => {
+  const [view, setView] = useState('datasource' as ViewType);
+
+  const el =
+    view === 'datasource' ? props.dataSourceElement : props.dataSyncElement;
+
+  return (
+    <div
+      style={{
+        height: '100%',
+        display: 'flex',
+        flexDirection: 'column',
+        padding: '5px'
+      }}
+    >
+      <div
+        style={{
+          padding: '5px 5px 10px',
+          borderBottom: '1px solid #DBE3EA',
+          display: 'flex',
+          justifyContent: 'space-around',
+          alignItems: 'center'
+        }}
+      >
+        <SwitchButton
+          viewType="datasource"
+          current={view}
+          label="数据源管理"
+          onClick={setView}
+        />
+        <SwitchButton
+          viewType="datasync"
+          current={view}
+          label="同步配置"
+          onClick={setView}
+        />
+      </div>
+      {el}
+    </div>
+  );
+};
+
+export default DataView;

diff --git a/packages/jldbq-extenison/src/DataViewWidget.tsx b/packages/jldbq-extenison/src/DataViewWidget.tsx
new file mode 100644
index 0000000000..0ab84b92f9
--- /dev/null
+++ b/packages/jldbq-extenison/src/DataViewWidget.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { ReactWidget } from '@jupyterlab/apputils';
+import { ISignal, Signal } from '@lumino/signaling';
+import DatasourceView from './DatasourceView';
+import DatasyncView from './DatasyncView';
+import DataView from './DataView';
+import { IDatasource } from './api/datasource';
+import { Widget } from '@lumino/widgets';
+
+interface ITableOpenSignalData {
+  datasource: IDatasource;
+  tableName: string;
+}
+
+class DataViewWidget extends ReactWidget {
+  constructor(manager: string, options?: Widget.IOptions) {
+    super(options);
+    this._manager = manager;
+  }
+
+  public get tableOpened(): ISignal<DataViewWidget, ITableOpenSignalData> {
+    return this._tableOpened;
+  }
+
+  render(): JSX.Element {
+    const datasourceView = (
+      <DatasourceView
+        manager={this._manager}
+        onOpenTable={(datasource, tableName) =>
+          this._tableOpened.emit({ datasource, tableName })
+        }
+      />
+    );
+
+    const datasyncView = <DatasyncView />;
+
+    return (
+      <div
+        style={{
+          height: '100%',
+          display: 'flex',
+          flexDirection: 'column'
+        }}
+      >
+        <DataView
+          dataSourceElement={datasourceView}
+          dataSyncElement={datasyncView}
+        />
+      </div>
+    );
+  }
+
+  private _manager: string;
+  private _tableOpened = new Signal<DataViewWidget, ITableOpenSignalData>(this);
+}
+
+export default DataViewWidget;
  • 实现添加数据源的表单
diff --git a/packages/jldbq-extenison/src/DatasourceForm.tsx b/packages/jldbq-extenison/src/DatasourceForm.tsx
new file mode 100644
index 0000000000..e4a470440d
--- /dev/null
+++ b/packages/jldbq-extenison/src/DatasourceForm.tsx
@@ -0,0 +1,185 @@
+import React from 'react';
+import { Dialog, ReactWidget } from '@jupyterlab/apputils';
+import { IDatasource } from './api/datasource';
+import { Widget } from '@lumino/widgets';
+
+interface IDatasourceItem {
+  address: string;
+  username: string;
+  password: string;
+  databasename: string;
+  type: 'mysql' | 'hive';
+}
+
+interface IProps {
+  value: Partial<IDatasourceItem>;
+  error: keyof IDatasourceItem | null;
+  onChange: (v: Partial<IDatasourceItem>) => void;
+}
+
+const DatasourceForm: React.FunctionComponent<IProps> = ({
+  value,
+  error,
+  onChange
+}) => {
+  return (
+    <form className="jldbq-form">
+      <label className={error === 'type' ? 'jldbq-form-error' : ''}>
+        <span className="jldbq-form-label">数据源类型:</span>
+        <div className="jldbq-form-switcher">
+          <button
+            type="button"
+            className={
+              'jldbq-btn ' + (value.type === 'mysql' ? 'jldbq-btn-current' : '')
+            }
+            onClick={() => onChange({ type: 'mysql' })}
+          >
+            MySQL
+          </button>
+          <button
+            type="button"
+            className={
+              'jldbq-btn ' + (value.type === 'hive' ? 'jldbq-btn-current' : '')
+            }
+            onClick={() => onChange({ type: 'hive' })}
+          >
+            Hive
+          </button>
+        </div>
+      </label>
+      <label className={error === 'address' ? 'jldbq-form-error' : ''}>
+        <span className="jldbq-form-label">数据源地址:</span>
+        <input
+          className="jldbq-form-input"
+          placeholder="请输入数据源地址"
+          value={value.address || ''}
+          onChange={evt => onChange({ address: evt.target.value })}
+        />
+      </label>
+      <label className={error === 'username' ? 'jldbq-form-error' : ''}>
+        <span className="jldbq-form-label">用户名:</span>
+        <input
+          className="jldbq-form-input"
+          placeholder="请输入用户名"
+          value={value.username || ''}
+          onChange={evt => onChange({ username: evt.target.value })}
+        />
+      </label>
+      <label className={error === 'password' ? 'jldbq-form-error' : ''}>
+        <span className="jldbq-form-label">密码:</span>
+        <input
+          className="jldbq-form-input"
+          type="password"
+          placeholder="请输入密码"
+          value={value.password || ''}
+          onChange={evt => onChange({ password: evt.target.value })}
+        />
+      </label>
+      <label className={error === 'databasename' ? 'jldbq-form-error' : ''}>
+        <span className="jldbq-form-label">数据库名称:</span>
+        <input
+          className="jldbq-form-input"
+          placeholder="请输入数据库名称"
+          value={value.databasename || ''}
+          onChange={evt => onChange({ databasename: evt.target.value })}
+        />
+      </label>
+    </form>
+  );
+};
+
+export class DatasourceFormDialogBody
+  extends ReactWidget
+  implements Dialog.IBodyWidget<IDatasource> {
+  constructor(manager: string, options?: Widget.IOptions) {
+    super(options);
+    this._manager = manager;
+  }
+  render(): JSX.Element {
+    return (
+      <DatasourceForm
+        value={this._datasourceItem}
+        error={this._error}
+        onChange={v => {
+          this._datasourceItem = { ...this._datasourceItem, ...v };
+          this._error = null;
+          this.update();
+        }}
+      />
+    );
+  }
+
+  getValue(): IDatasource {
+    const {
+      address,
+      username: user,
+      password,
+      databasename,
+      type: source
+    } = this._datasourceItem;
+    if (!source) {
+      this._setError('type');
+    }
+    if (!address || !validateIpAndPort(address)) {
+      this._setError('address');
+    }
+    const [host, portStr] = address.split(':');
+    const port = portStr ? +portStr : 3306;
+    if (!user) {
+      this._setError('username');
+    }
+    if (!password) {
+      this._setError('password');
+    }
+    if (!databasename) {
+      this._setError('databasename');
+    }
+    return {
+      host,
+      user,
+      port,
+      password,
+      charset: 'utf8',
+      source,
+      databasename,
+      sourcename: databasename,
+      manager: this._manager
+    };
+  }
+
+  private _setError(error: keyof IDatasourceItem): never {
+    this._error = error;
+    this.update();
+    throw new Error(error);
+  }
+
+  private _datasourceItem: Partial<IDatasourceItem> = {};
+  private _error: IProps['error'] = null;
+  private _manager: string;
+}
+
+function validateIpAndPort(input: string): boolean {
+  const parts = input.split(':');
+  if (parts.length > 2) {
+    return false;
+  }
+  const ip = parts[0].split('.');
+  if (
+    ip.length !== 4 ||
+    !ip.every(function (segment) {
+      return validateNum(segment, 0, 255);
+    })
+  ) {
+    return false;
+  }
+  const port = parts[1];
+  if (port && !validateNum(port, 1, 65535)) {
+    return false;
+  }
+  return true;
+}
+
+function validateNum(input: string, min: number, max: number): boolean {
+  const num = +input;
+  return num >= min && num <= max && input === num.toString();
+}
  • 实现数据源预览的面板
diff --git a/packages/jldbq-extenison/src/DatasourceView.tsx b/packages/jldbq-extenison/src/DatasourceView.tsx
new file mode 100644
index 0000000000..f7e97e0702
--- /dev/null
+++ b/packages/jldbq-extenison/src/DatasourceView.tsx
@@ -0,0 +1,182 @@
+import React from 'react';
+import { useState } from 'react';
+import { Dialog, showDialog } from '@jupyterlab/apputils';
+import TreeView from '@mui/lab/TreeView';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import ChevronRightIcon from '@mui/icons-material/ChevronRight';
+import TreeItem from '@mui/lab/TreeItem';
+import databaseSvgStr from '../style/icons/database-solid-ghost.svg';
+import {
+  fetchTables,
+  IDatasource,
+  setDatasource,
+  useDatasourceList
+} from './api/datasource';
+import { DatasourceFormDialogBody } from './DatasourceForm';
+
+const dragImg = new Image();
+dragImg.src = 'data:image/svg+xml;base64,' + btoa(databaseSvgStr);
+
+interface IProps {
+  manager: string;
+  onOpenTable: (datasource: IDatasource, tableName: string) => void;
+}
+
+const DatasourceView: React.FunctionComponent<IProps> = ({
+  manager,
+  onOpenTable
+}) => {
+  const { datasources, loading, error, refresh } = useDatasourceList(manager);
+  const [tables, setTables] = useState<
+    Partial<{
+      [key: string]: string[] | 'error';
+    }>
+  >({});
+
+  const handleNodeToggle = async (ds: IDatasource) => {
+    if (!tables[ds.sourcename]) {
+      const tables = await fetchTables(ds);
+      setTables(prev => {
+        return { [ds.sourcename]: tables, ...prev };
+      });
+    }
+  };
+
+  let tree: JSX.Element[] | JSX.Element;
+  if (error) {
+    tree = <div style={{ padding: '0px 12px' }}>Error</div>;
+  } else if (loading) {
+    tree = <div style={{ padding: '0px 12px' }}>Loading ...</div>;
+  } else {
+    tree = datasources!.map(ds => {
+      const table = tables[ds.sourcename];
+      let tableItems;
+      if (!table) {
+        tableItems = (
+          <TreeItem nodeId={`${ds.sourcename}-loading`} label="Loading ..." />
+        );
+      } else if (table === 'error') {
+        tableItems = (
+          <TreeItem nodeId={`${ds.sourcename}-error`} label="Error" />
+        );
+      } else if (table.length === 0) {
+        tableItems = (
+          <TreeItem nodeId={`${ds.sourcename}-error`} label="Empty" />
+        );
+      } else {
+        tableItems = table.map(t => {
+          return (
+            <TreeItem
+              title={t}
+              key={`${ds.sourcename}-${t}`}
+              nodeId={`${ds.sourcename}-${t}`}
+              label={t}
+              onDoubleClick={() => onOpenTable(ds, t)}
+              draggable="true"
+              onDragStart={evt => {
+                const data = {
+                  operation: 'addToCanvas',
+                  data: {
+                    editType: 'createNode',
+                    finalized: true,
+                    nodeTemplate: {
+                      id: `${ds.manager}-${ds.sourcename}-${t}`,
+                      op: `database-${ds.source}-input`,
+                      description: t,
+                      type: 'execution_node',
+                      label: t,
+                      inputs: [],
+                      parameters: {},
+                      outputs: [
+                        {
+                          id: 'outPort',
+                          app_data: {
+                            ui_data: {
+                              cardinality: {
+                                min: 0,
+                                max: -1
+                              },
+                              label: 'Output Port'
+                            }
+                          }
+                        }
+                      ],
+                      app_data: {
+                        ui_data: {
+                          description: t,
+                          image: '',
+                          label: t
+                        }
+                      }
+                    }
+                  }
+                };
+                evt.dataTransfer.setData('text/plain', JSON.stringify(data));
+                evt.dataTransfer.setDragImage(dragImg, 10, 10);
+              }}
+              onFocusCapture={e => e.stopPropagation()}
+            />
+          );
+        });
+      }
+      return (
+        <TreeItem
+          key={ds.sourcename}
+          nodeId={ds.sourcename}
+          label={ds.databasename}
+          onClick={() => handleNodeToggle(ds)}
+        >
+          {tableItems}
+        </TreeItem>
+      );
+    });
+  }
+
+  const handleAddDatasource = async () => {
+    const { value } = await showDialog({
+      title: '添加数据源',
+      body: new DatasourceFormDialogBody(manager),
+      hasClose: true,
+      buttons: [
+        Dialog.okButton({ label: '确定' }),
+        Dialog.cancelButton({ label: '取消' })
+      ]
+    });
+    if (!value) {
+      return;
+    }
+    await setDatasource(value);
+    refresh();
+  };
+
+  return (
+    <div style={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
+      <div
+        style={{ display: 'flex', justifyContent: 'flex-end', padding: '10px' }}
+      >
+        <button className="jldbq-btn-add" onClick={handleAddDatasource}>
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="20"
+            height="20"
+            fill="currentColor"
+            viewBox="0 0 16 16"
+          >
+            <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
+          </svg>
+          添加数据源
+        </button>
+      </div>
+      <TreeView
+        aria-label="file system navigator"
+        defaultCollapseIcon={<ExpandMoreIcon />}
+        defaultExpandIcon={<ChevronRightIcon />}
+        sx={{ overflow: 'auto', flexGrow: 1 }}
+      >
+        {tree}
+      </TreeView>
+    </div>
+  );
+};
+
+export default DatasourceView;
  • 合并了数据同步表单的实现代码
diff --git a/packages/jldbq-extenison/src/DatasyncForm.tsx b/packages/jldbq-extenison/src/DatasyncForm.tsx
new file mode 100644
index 0000000000..d638ff736e
--- /dev/null
+++ b/packages/jldbq-extenison/src/DatasyncForm.tsx
@@ -0,0 +1,322 @@
+import React, { useState } from 'react';
+import {
+  Button,
+  DatePicker,
+  Form,
+  InputNumber,
+  Radio,
+  RadioChangeEvent,
+  Select,
+  Space
+} from 'antd';
+import { IDatasource } from './api/datasource';
+import arrowImgUrl from '../style/img/arrow.png';
+import deleteImgUrl from '../style/img/delete.png';
+
+type FreqUnit = 'day' | 'week' | 'month';
+
+interface ISyncMeta {
+  startTime: Date;
+  frequency: number;
+  freqUnit: FreqUnit;
+}
+
+interface ISyncMapping {
+  srcDatasource: IDatasource;
+  srcTableName: string;
+  dstDatasource: IDatasource;
+  dstTableName: string;
+}
+
+export interface ISyncData {
+  mapping: ISyncMapping;
+  meta: ISyncMeta;
+}
+
+interface IProps {
+  datasourceList: IDatasource[];
+  tableListFetcher: (ds: IDatasource) => Promise<string[]>;
+  onConfirm: (data: ISyncData[]) => void;
+  onCancel: () => void;
+}
+
+const DatasyncForm: React.FunctionComponent<IProps> = props => {
+  const [srcDatasource, setSrcDatasource] = useState<IDatasource | null>(null);
+  const [srcTableName, setSrcTableName] = useState<string | null>(null);
+  const [srcTableList, setSrcTableList] = useState<string[]>([]);
+  const [dstDatasource, setDstDatasource] = useState<IDatasource | null>(null);
+  const [dstTableName, setDstTableName] = useState<string | null>(null);
+  const [dstTableList, setDstTableList] = useState<string[]>([]);
+  const [mappingList, setMappingList] = useState<ISyncMapping[]>([]);
+  const [form] = Form.useForm();
+
+  const onFinish = (meta: any) => {
+    // startTime = moment(startTime).toDate();
+    const results = mappingList.map(mapping => {
+      return {
+        mapping,
+        meta: {
+          startTime: meta.startTime.toDate(),
+          frequency: Math.max(parseInt(meta.frequency), 0),
+          freqUnit: meta.freqUnit as FreqUnit
+        }
+      };
+    });
+    props.onConfirm(results);
+  };
+
+  const sourceDatasourceChange = (sourcename: string) => {
+    const ds = props.datasourceList.find(
+      item => item.sourcename === sourcename
+    )!;
+    setSrcDatasource(ds);
+    setSrcTableName(null);
+    setSrcTableList([]);
+    void props.tableListFetcher(ds).then(lst => {
+      setSrcTableList(lst);
+    });
+  };
+
+  const destDatasourceChange = (sourcename: string) => {
+    const ds = props.datasourceList.find(
+      item => item.sourcename === sourcename
+    )!;
+    setDstDatasource(ds);
+    setDstTableName(null);
+    setDstTableList([]);
+    void props.tableListFetcher(ds).then(lst => {
+      setDstTableList(lst);
+    });
+  };
+
+  const sourceTableChange = (e: RadioChangeEvent) => {
+    setSrcTableName(e.target.value as string);
+  };
+
+  const destTableChange = (e: RadioChangeEvent) => {
+    setDstTableName(e.target.value as string);
+  };
+
+  const addMapping = () => {
+    if (!srcDatasource || !srcTableName || !dstDatasource || !dstTableName) {
+      return;
+    }
+    if (
+      mappingList.find(
+        item =>
+          item.srcDatasource.sourcename === srcDatasource.sourcename &&
+          item.srcTableName === srcTableName &&
+          item.dstDatasource.sourcename === dstDatasource.sourcename &&
+          item.dstTableName === dstTableName
+      )
+    ) {
+      return;
+    }
+    setMappingList([
+      ...mappingList,
+      {
+        srcDatasource,
+        srcTableName,
+        dstDatasource,
+        dstTableName
+      }
+    ]);
+  };
+
+  const deleteMapping = (mapping: ISyncMapping) => {
+    setMappingList(mappingList.filter(item => item !== mapping));
+  };
+
+  const freqUnitSelector = (
+    <Form.Item
+      name="freqUnit"
+      rules={[{ required: true, message: '请输入同步频次单位' }]}
+      noStyle
+    >
+      <Select style={{ width: 100 }}>
+        <Select.Option value="day">天</Select.Option>
+        <Select.Option value="week">周</Select.Option>
+        <Select.Option value="month">月</Select.Option>
+      </Select>
+    </Form.Item>
+  );
+
+  return (
+    <div>
+      <Form
+        form={form}
+        name="datasyncForm"
+        onFinish={onFinish}
+        style={{ width: '800px' }}
+      >
+        <div style={{ display: 'flex' }}>
+          <Form.Item style={{ width: '25%' }}>
+            <Form.Item
+              name="srcDatasource"
+              noStyle
+              // rules={[{ required: true, message: '请选择数据源' }]}
+            >
+              <Select
+                placeholder="选择数据源"
+                onChange={sourceDatasourceChange}
+              >
+                {props.datasourceList.map(item => {
+                  return (
+                    <Select.Option
+                      value={item.sourcename}
+                      key={item.sourcename}
+                    >
+                      {item.databasename}
+                    </Select.Option>
+                  );
+                })}
+              </Select>
+            </Form.Item>
+
+            <Form.Item
+              name="srcTableName"
+              // rules={[{ required: true, message: '请选择源数据表' }]}
+              noStyle
+            >
+              <div className="plane">
+                <Radio.Group value={srcTableName} onChange={sourceTableChange}>
+                  <Space direction="vertical">
+                    {srcTableList.map(item => {
+                      return (
+                        <Radio value={item} key={item}>
+                          {item}
+                        </Radio>
+                      );
+                    })}
+                  </Space>
+                </Radio.Group>
+              </div>
+            </Form.Item>
+          </Form.Item>
+          <div className="arrow">
+            <img
+              src={arrowImgUrl}
+              alt=""
+              onClick={addMapping}
+              style={{ width: '20px', height: '20px', cursor: 'pointer' }}
+            />
+          </div>
+          <Form.Item style={{ width: '25%' }}>
+            <Form.Item
+              name="dstDatasource"
+              // rules={[{ required: true, message: '请选择同步目标' }]}
+              noStyle
+            >
+              <Select
+                placeholder="选择同步目标"
+                onChange={destDatasourceChange}
+              >
+                {props.datasourceList.map(item => {
+                  return (
+                    <Select.Option
+                      value={item.sourcename}
+                      key={item.sourcename}
+                    >
+                      {item.databasename}
+                    </Select.Option>
+                  );
+                })}
+              </Select>
+            </Form.Item>
+            <Form.Item
+              name="dstTableName"
+              // rules={[{ required: true, message: '请选择目标数据表' }]}
+              noStyle
+            >
+              <div className="plane">
+                <Radio.Group value={dstTableName} onChange={destTableChange}>
+                  <Space direction="vertical">
+                    {dstTableList.map(item => {
+                      return (
+                        <Radio value={item} key={item}>
+                          {item}
+                        </Radio>
+                      );
+                    })}
+                  </Space>
+                </Radio.Group>
+              </div>
+            </Form.Item>
+          </Form.Item>
+          <div className="line"></div>
+          <div className="mappings">
+            <p className="mappings__title">同步映射</p>
+            <div className="mappings__content">
+              {mappingList.map(item => {
+                const key = `${item.dstDatasource.sourcename}-${item.dstTableName}-${item.srcDatasource.sourcename}-${item.srcTableName}`;
+                return (
+                  <p key={key}>
+                    <span>{item.srcTableName}</span>
+                    <img
+                      src={arrowImgUrl}
+                      alt=""
+                      style={{
+                        width: '10px',
+                        height: '10px',
+                        margin: '0 50px'
+                      }}
+                    />
+                    <span>{item.dstTableName}</span>
+                    <img
+                      src={deleteImgUrl}
+                      alt=""
+                      onClick={() => {
+                        deleteMapping(item);
+                      }}
+                      style={{
+                        width: '10px',
+                        height: '10px',
+                        marginLeft: '30px',
+                        cursor: 'pointer'
+                      }}
+                    />
+                  </p>
+                );
+              })}
+            </div>
+          </div>
+        </div>
+        <Form.Item
+          label="开始时间"
+          name="startTime"
+          rules={[{ required: true, message: '请选择开始时间' }]}
+        >
+          <DatePicker placeholder="请选择日期" style={{ width: '50%' }} />
+        </Form.Item>
+        <Form.Item label="同步频次" required>
+          <span style={{ lineHeight: '32px' }}>每</span>
+          <Form.Item
+            name="frequency"
+            rules={[{ required: true, message: '请输入同步频次' }]}
+            noStyle
+          >
+            <InputNumber
+              addonAfter={freqUnitSelector}
+              style={{ width: '47%', margin: '0 1%' }}
+            />
+          </Form.Item>
+          <span style={{ lineHeight: '32px' }}>/次</span>
+        </Form.Item>
+        <Form.Item style={{ display: 'flex', justifyContent: 'center' }}>
+          <Button
+            type="primary"
+            htmlType="submit"
+            style={{ marginRight: '40px' }}
+          >
+            确定
+          </Button>
+          <Button htmlType="button" onClick={props.onCancel}>
+            取消
+          </Button>
+        </Form.Item>
+      </Form>
+    </div>
+  );
+};
+
+export default DatasyncForm;

diff --git a/packages/jldbq-extenison/src/DatasyncView.tsx b/packages/jldbq-extenison/src/DatasyncView.tsx
new file mode 100644
index 0000000000..910eef1ed6
--- /dev/null
+++ b/packages/jldbq-extenison/src/DatasyncView.tsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import { Dialog, ReactWidget } from '@jupyterlab/apputils';
+import { Widget } from '@lumino/widgets';
+import { Signal } from '@lumino/signaling';
+import DatasyncForm, { ISyncData } from './DatasyncForm';
+
+interface IProps {}
+
+const DatasyncView: React.FunctionComponent<IProps> = props => {
+  return (
+    <div style={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
+      <div
+        style={{ display: 'flex', justifyContent: 'flex-end', padding: '10px' }}
+      >
+        <button
+          className="jldbq-btn-add"
+          onClick={async () => {
+            const body = new DatasyncFormDialogBody();
+            const dialog = new Dialog({
+              title: '添加同步任务',
+              body,
+              renderer: new DatasyncFormDialogRenderer()
+            });
+            body.confirm.connect((_sender, data) => {
+              console.log(data);
+              dialog.reject();
+            });
+            body.cancel.connect(() => {
+              dialog.reject();
+            });
+            await dialog.launch();
+          }}
+        >
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="20"
+            height="20"
+            fill="currentColor"
+            viewBox="0 0 16 16"
+          >
+            <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
+          </svg>
+          添加同步任务
+        </button>
+      </div>
+    </div>
+  );
+};
+
+export default DatasyncView;
+
+class DatasyncFormDialogBody extends ReactWidget {
+  constructor(options?: Widget.IOptions) {
+    super(options);
+  }
+
+  render(): JSX.Element {
+    Signal;
+    return (
+      <DatasyncForm
+        datasourceList={[
+          {
+            host: '1.1.1.1',
+            user: 'user',
+            port: 3306,
+            password: '',
+            charset: 'utf8',
+            manager: 'manager',
+            source: 'mysql',
+            databasename: 'default',
+            sourcename: 'default'
+          }
+        ]}
+        tableListFetcher={async () => {
+          return ['1', '2', '3'];
+        }}
+        onConfirm={v => this._confirm.emit(v)}
+        onCancel={() => this._cancel.emit()}
+      />
+    );
+  }
+
+  get confirm() {
+    return this._confirm;
+  }
+
+  get cancel() {
+    return this._cancel;
+  }
+
+  private _confirm = new Signal<DatasyncFormDialogBody, ISyncData[]>(this);
+  private _cancel = new Signal<DatasyncFormDialogBody, void>(this);
+}
+
+class DatasyncFormDialogRenderer extends Dialog.Renderer {
+  createFooter(_buttons: ReadonlyArray<HTMLElement>): Widget {
+    return new Widget();
+  }
+}
  • 调度监控 Stub (尚未实现)
diff --git a/packages/jldbq-extenison/src/MonitorViewWidget.tsx b/packages/jldbq-extenison/src/MonitorViewWidget.tsx
new file mode 100644
index 0000000000..02baa7af47
--- /dev/null
+++ b/packages/jldbq-extenison/src/MonitorViewWidget.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { ReactWidget } from '@jupyterlab/apputils';
+
+export default class MonitorWidget extends ReactWidget {
+  render(): JSX.Element {
+    return <div style={{ height: '100%' }} />;
+  }
+}
  • 定时任务 Stub (尚未实现)
diff --git a/packages/jldbq-extenison/src/TaskViewWidget.tsx b/packages/jldbq-extenison/src/TaskViewWidget.tsx
new file mode 100644
index 0000000000..9497654443
--- /dev/null
+++ b/packages/jldbq-extenison/src/TaskViewWidget.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+import { ReactWidget } from '@jupyterlab/apputils';
+
+export default class TaskViewWidget extends ReactWidget {
+  render(): JSX.Element {
+    return <div style={{ height: '100%' }} />;
+  }
+}
  • Backend API 相关代码
diff --git a/packages/jldbq-extenison/src/api/config.ts b/packages/jldbq-extenison/src/api/config.ts
new file mode 100644
index 0000000000..78b468134b
--- /dev/null
+++ b/packages/jldbq-extenison/src/api/config.ts
@@ -0,0 +1,5 @@
+const config = {
+  endpoint: 'http://192.168.51.19:5000'
+};
+
+export default config;

diff --git a/packages/jldbq-extenison/src/api/datasource.ts b/packages/jldbq-extenison/src/api/datasource.ts
new file mode 100644
index 0000000000..8927b678bb
--- /dev/null
+++ b/packages/jldbq-extenison/src/api/datasource.ts
@@ -0,0 +1,106 @@
+import useSWR from 'swr';
+import config from './config';
+
+type DbEnc = 'utf8';
+type DbType = 'mysql' | 'hive';
+
+export interface IDatasource {
+  host: string;
+  user: string;
+  port: number;
+  password: string;
+  charset: DbEnc;
+  manager: string;
+  source: DbType;
+  databasename: string;
+  sourcename: string;
+}
+
+interface IDatasourceResponse {
+  datasources?: IDatasource[];
+  loading: boolean;
+  error?: any;
+  refresh: () => void;
+}
+
+export type ITableData = Record<string, Record<string, any>>;
+
+export const useDatasourceList = (manager: string): IDatasourceResponse => {
+  const endpoint = config.endpoint;
+  const item = encodeURIComponent(manager);
+  const url = `${endpoint}/managelist/item=${item}`;
+  const { data, error, mutate } = useSWR(url, (...args) =>
+    fetch(...args).then(res => res.json())
+  );
+  return {
+    datasources: data,
+    loading: !error && !data,
+    error,
+    refresh: mutate
+  };
+};
+
+export const setDatasource = async (
+  datasource: IDatasource
+): Promise<string> => {
+  const endpoint = config.endpoint;
+  const url = `${endpoint}/manage`;
+  try {
+    const resp = await fetch(url, {
+      method: 'post',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify(datasource)
+    });
+    if (!resp.ok) {
+      return 'error';
+    }
+    const data = await resp.text();
+    return data;
+  } catch (e) {
+    console.error(e);
+    return 'error';
+  }
+};
+
+export const fetchTables = async (
+  ds: IDatasource
+): Promise<'error' | string[]> => {
+  const endpoint = config.endpoint;
+  const item = encodeURIComponent(
+    `${ds.manager};${ds.sourcename};${ds.databasename}`
+  );
+  const url = `${endpoint}/show/${ds.source}/tables/item=${item}`;
+  try {
+    const resp = await fetch(url);
+    if (!resp.ok) {
+      return 'error';
+    }
+    const data = (await resp.json()) as string[];
+    return data;
+  } catch (e) {
+    console.error(e);
+    return 'error';
+  }
+};
+
+export const fetchTableContent = async (
+  ds: IDatasource,
+  t: string
+): Promise<ITableData | 'error'> => {
+  const endpoint = config.endpoint;
+  const item = encodeURIComponent(
+    `${ds.manager};${ds.sourcename};${ds.databasename};${t}`
+  );
+  const url = `${endpoint}/show/${ds.source}/results/item=${item}`;
+  try {
+    const resp = await fetch(url);
+    if (!resp.ok) {
+      return 'error';
+    }
+    const data = (await resp.json()) as ITableData;
+    return data;
+  } catch (e) {
+    console.error(e);
+    return 'error';
+  }
+};

diff --git a/packages/jldbq-extenison/src/api/index.ts b/packages/jldbq-extenison/src/api/index.ts
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/jldbq-extenison/src/icons.ts b/packages/jldbq-extenison/src/icons.ts
new file mode 100644
index 0000000000..9e2b76f812
--- /dev/null
+++ b/packages/jldbq-extenison/src/icons.ts
@@ -0,0 +1,19 @@
+import { LabIcon } from '@jupyterlab/ui-components';
+import cliSvgStr from '../style/icons/cli.svg';
+import timerSvgStr from '../style/icons/timer.svg';
+import monitorSvgStr from '../style/icons/monitor.svg';
+
+export const cliIcon = new LabIcon({
+  name: 'cli',
+  svgstr: cliSvgStr
+});
+
+export const timerIcon = new LabIcon({
+  name: 'timer',
+  svgstr: timerSvgStr
+});
+
+export const monitorIcon = new LabIcon({
+  name: 'monitor',
+  svgstr: monitorSvgStr
+});
  • 实现数据开发的 JupyterLab 插件 (添加左侧侧边栏)
diff --git a/packages/jldbq-extenison/src/index.ts b/packages/jldbq-extenison/src/index.ts
new file mode 100644
index 0000000000..28da880058
--- /dev/null
+++ b/packages/jldbq-extenison/src/index.ts
@@ -0,0 +1,71 @@
+/**
+ * @packageDocumentation
+ * @module jldbq-extension
+ */
+
+import {
+  JupyterFrontEnd,
+  JupyterFrontEndPlugin
+} from '@jupyterlab/application';
+import { MainAreaWidget } from '@jupyterlab/apputils';
+import { ISettingRegistry } from '@jupyterlab/settingregistry';
+import DataViewWidget from './DataViewWidget';
+import DataTableWidget from './DataTableWidget';
+import TaskViewWidget from './TaskViewWidget';
+import MonitorViewWidget from './MonitorViewWidget';
+import { cliIcon, monitorIcon, timerIcon } from './icons';
+import config from './api/config';
+
+const PLUGIN_ID = '@jupyterlab/jldbq-extension:plugin';
+
+const plugin: JupyterFrontEndPlugin<void> = {
+  id: PLUGIN_ID,
+  autoStart: true,
+  requires: [ISettingRegistry],
+  activate: async (app: JupyterFrontEnd, registry: ISettingRegistry) => {
+    registry.pluginChanged.connect(async (_sender, plugin) => {
+      if (plugin === PLUGIN_ID) {
+        await updateConfig(registry);
+      }
+    });
+    await updateConfig(registry);
+
+    const manager = 'test1'; // TODO: 读取当前用户
+
+    const dataViewWidget = new DataViewWidget(manager);
+    dataViewWidget.id = 'jldbq-data';
+    dataViewWidget.title.icon = cliIcon;
+    dataViewWidget.title.caption = '数据开发';
+    dataViewWidget.title.label = '数据开发';
+    dataViewWidget.tableOpened.connect((_sender, { datasource, tableName }) => {
+      const content = new DataTableWidget(datasource, tableName);
+      const widget = new MainAreaWidget({ content });
+      widget.title.label = tableName;
+      widget.title.icon = cliIcon;
+      app.shell.add(widget, 'main');
+    });
+    app.shell.add(dataViewWidget, 'left', { rank: 100 });
+
+    const taskViewWidget = new TaskViewWidget();
+    taskViewWidget.id = 'jldbq-sync';
+    taskViewWidget.title.icon = timerIcon;
+    taskViewWidget.title.caption = '定时任务';
+    taskViewWidget.title.label = '定时任务';
+    app.shell.add(taskViewWidget, 'left', { rank: 1000 });
+
+    const monitorViewWidget = new MonitorViewWidget();
+    monitorViewWidget.id = 'jldbq-monitor';
+    monitorViewWidget.title.icon = monitorIcon;
+    monitorViewWidget.title.caption = '调度监控';
+    monitorViewWidget.title.label = '调度监控';
+    app.shell.add(monitorViewWidget, 'left', { rank: 1100 });
+  }
+};
+
+export default plugin;
+
+async function updateConfig(registry: ISettingRegistry) {
+  const settings = await registry.load(PLUGIN_ID);
+  const endpoint = settings.composite['flaskBackend'] as string;
+  config.endpoint = endpoint;
+}
  • 数据开发的相关样式代码
diff --git a/packages/jldbq-extenison/src/png.d.ts b/packages/jldbq-extenison/src/png.d.ts
new file mode 100644
index 0000000000..1c5923252c
--- /dev/null
+++ b/packages/jldbq-extenison/src/png.d.ts
@@ -0,0 +1,4 @@
+declare module '*.png' {
+  const value: string;
+  export default value;
+}

diff --git a/packages/jldbq-extenison/src/svg.d.ts b/packages/jldbq-extenison/src/svg.d.ts
new file mode 100644
index 0000000000..0ca27598c5
--- /dev/null
+++ b/packages/jldbq-extenison/src/svg.d.ts
@@ -0,0 +1,4 @@
+declare module "*.svg" {
+  const value: string;
+  export default value;
+}

diff --git a/packages/jldbq-extenison/style/base.css b/packages/jldbq-extenison/style/base.css
new file mode 100644
index 0000000000..e2026de0cb
--- /dev/null
+++ b/packages/jldbq-extenison/style/base.css
@@ -0,0 +1,9 @@
+/*
+    See the JupyterLab Developer Guide for useful CSS Patterns:
+
+    https://jupyterlab.readthedocs.io/en/stable/developer/css.html
+*/
+
+@import './syncform.css';
+@import './button.css';
+@import './dsform.css';

diff --git a/packages/jldbq-extenison/style/button.css b/packages/jldbq-extenison/style/button.css
new file mode 100644
index 0000000000..ea9c093f0b
--- /dev/null
+++ b/packages/jldbq-extenison/style/button.css
@@ -0,0 +1,31 @@
+.jldbq-btn {
+  width: 95px;
+  height: 33px;
+  padding: 0;
+  border: none;
+  background-color: #edf0f6;
+  color: #4a4a4a;
+  border-radius: 0;
+}
+
+.jldbq-btn.jldbq-btn-current {
+  background-color: #d1e4f6;
+}
+
+.jldbq-btn:hover:not(.jldbq-btn-current) {
+  opacity: 0.8;
+}
+
+.jldbq-btn-add {
+  display: flex;
+  align-items: center;
+  padding: 0;
+  background: none;
+  border: none;
+  color: #147bd1;
+  cursor: pointer;
+}
+
+.jldbq-btn-add:hover {
+  opacity: 0.8;
+}

diff --git a/packages/jldbq-extenison/style/dsform.css b/packages/jldbq-extenison/style/dsform.css
new file mode 100644
index 0000000000..f952bc34f4
--- /dev/null
+++ b/packages/jldbq-extenison/style/dsform.css
@@ -0,0 +1,105 @@
+.jldbq-form {
+  display: flex;
+  flex-direction: column;
+}
+
+.jldbq-form label {
+  margin-bottom: 30px;
+  display: flex;
+  align-items: center;
+}
+
+.jldbq-form label:last-child {
+  margin-bottom: 0;
+}
+
+.jldbq-form label.jldbq-form-error:last-child {
+  border: 1px solid #ed5555;
+}
+
+.jldbq-form .jldbq-form-label {
+  color: #4a4a4a;
+  width: 75px;
+  text-align: end;
+}
+
+.jldbq-form .jldbq-form-switcher {
+  margin-left: 15px;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  width: 320px;
+}
+
+.jldbq-form .jldbq-form-input {
+  font-family: inherit;
+  box-sizing: border-box;
+  width: 320px;
+  height: 32px;
+  border-radius: 2px;
+  border: 1px solid #c8d3e9;
+  margin-left: 15px;
+  padding: 0 15px;
+  font-size: 12px;
+  transition: all 100ms;
+  outline: 1px solid transparent;
+}
+
+.jldbq-form .jldbq-form-input:focus {
+  outline-color: #147bd1;
+}
+
+.jldbq-form .react-select-container {
+  width: 320px;
+  margin-left: 15px;
+}
+
+.jldbq-form .react-select__control {
+  border-radius: 2px;
+  border: 1px solid #c8d3e9;
+  min-height: 32px;
+}
+
+.jldbq-form .react-select__control:hover {
+  border-color: #c8d3e9;
+}
+
+.jldbq-form .react-select__indicator-separator {
+  background-color: #c8d3e9;
+  margin: 4px 0;
+}
+
+.jldbq-form .react-select__indicator {
+  color: #4883fb;
+  padding: 0 8px;
+}
+
+.jldbq-form .react-select__value-container {
+  padding: 0 15px;
+  font-size: 12px;
+}
+
+.jldbq-form .react-select__single-value {
+  padding: 0;
+  margin: 0;
+}
+
+.jldbq-form .react-select__placeholder {
+  padding: 0;
+  margin: 0;
+}
+
+.jldbq-form .react-select__input-container {
+  padding: 0;
+  margin: 0;
+}
+
+.jldbq-form .react-select__menu-notice {
+  font-size: 12px;
+}
+
+.jldbq-form .react-select__option {
+  font-size: 12px;
+  padding-left: 16px;
+  padding-right: 16px;
+}

diff --git a/packages/jldbq-extenison/style/icons/cli.svg b/packages/jldbq-extenison/style/icons/cli.svg
new file mode 100644
index 0000000000..d712aada18
--- /dev/null
+++ b/packages/jldbq-extenison/style/icons/cli.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="4.2333331mm" height="4.2333331mm" viewBox="0 0 4.2333331 4.2333332" version="1.1"
+  xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+  <g transform="translate(-20.017746,-119.34786)">
+    <path fill="#000000" class="jp-icon3 jp-icon-selectable"
+      style="stroke-width:1.00157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
+      d="M 0 0 L 0 16 L 16 16 L 16 12.998047 L 15.240234 12.998047 L 15.240234 15.240234 L 0.75976562 15.240234 L 0.75976562 3.7871094 L 15.240234 3.7871094 L 15.240234 5.8144531 L 16 5.8144531 L 16 0 L 0 0 z M 0.75976562 0.75976562 L 15.240234 0.75976562 L 15.240234 3.1660156 L 0.75976562 3.1660156 L 0.75976562 0.75976562 z M 11.039062 6.0175781 A 0.49999937 0.49999937 0 0 0 10.574219 6.3476562 L 8.640625 11.839844 A 0.49999937 0.49999937 0 0 0 8.9433594 12.474609 A 0.49999937 0.49999937 0 0 0 9.5859375 12.171875 L 11.519531 6.6796875 A 0.49999937 0.49999937 0 0 0 11.210938 6.0449219 L 11.208984 6.0449219 A 0.49999937 0.49999937 0 0 0 11.039062 6.0175781 z M 13.609375 6.3925781 A 0.49999937 0.49999937 0 0 0 13.3125 6.4882812 A 0.49999937 0.49999937 0 0 0 13.208984 7.1894531 L 14.832031 9.4179688 L 13.134766 11.285156 A 0.49999937 0.49999937 0 0 0 13.171875 11.994141 A 0.49999937 0.49999937 0 0 0 13.880859 11.957031 L 15.84375 9.7871094 A 0.50004934 0.50004934 0 0 0 15.880859 9.1523438 L 14.013672 6.5976562 A 0.49999937 0.49999937 0 0 0 13.689453 6.3984375 L 13.6875 6.3984375 A 0.49999937 0.49999937 0 0 0 13.609375 6.3925781 z M 6.5507812 6.3945312 A 0.49999937 0.49999937 0 0 0 6.4726562 6.4003906 L 6.4726562 6.3984375 A 0.49999937 0.49999937 0 0 0 6.1484375 6.5976562 L 4.28125 9.1523438 A 0.50004934 0.50004934 0 0 0 4.3105469 9.7871094 L 6.28125 11.957031 A 0.49999937 0.49999937 0 0 0 6.9902344 11.996094 A 0.49999937 0.49999937 0 0 0 7.0273438 11.287109 L 5.3300781 9.4199219 L 6.953125 7.1894531 A 0.49999937 0.49999937 0 0 0 6.8417969 6.4882812 A 0.49999937 0.49999937 0 0 0 6.5507812 6.3945312 z "
+      transform="matrix(0.26458333,0,0,0.26458333,20.017746,119.34786)" />
+  </g>
+</svg>
\ No newline at end of file

diff --git a/packages/jldbq-extenison/style/icons/clock-fill.svg b/packages/jldbq-extenison/style/icons/clock-fill.svg
new file mode 100644
index 0000000000..7cece1ee7b
--- /dev/null
+++ b/packages/jldbq-extenison/style/icons/clock-fill.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#616161" class="jp-icon3 jp-icon-selectable"
+  viewBox="0 0 16 16">
+  <path
+    d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z" />
+</svg>
\ No newline at end of file

diff --git a/packages/jldbq-extenison/style/icons/database-solid-ghost.svg b/packages/jldbq-extenison/style/icons/database-solid-ghost.svg
new file mode 100644
index 0000000000..8edad9815c
--- /dev/null
+++ b/packages/jldbq-extenison/style/icons/database-solid-ghost.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 43 49.14286"
+   version="1.1"
+   id="svg19"
+   sodipodi:docname="database-solid-ghost.svg"
+   width="43"
+   height="49.14286"
+   inkscape:version="1.1.2 (b8e25be833, 2022-02-05)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs23" />
+  <sodipodi:namedview
+     id="namedview21"
+     pagecolor="#ffffff"
+     bordercolor="#999999"
+     borderopacity="1"
+     inkscape:pageshadow="0"
+     inkscape:pageopacity="0"
+     inkscape:pagecheckerboard="0"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:zoom="1"
+     inkscape:cx="71.5"
+     inkscape:cy="316"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     inkscape:window-x="-8"
+     inkscape:window-y="-8"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg19" />
+  <!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
+  <path
+     d="m 43,7.678571 v 4.607143 c 0,4.242411 -9.627009,7.678572 -21.5,7.678572 C 9.627009,19.964286 0,16.528125 0,12.285714 V 7.678571 C 0,3.43808 9.627009,0 21.5,0 33.372991,0 43,3.43808 43,7.678571 Z M 37.740179,20.607366 C 39.650223,19.897098 41.569866,18.985268 43,17.862277 v 9.78058 c 0,4.242411 -9.627009,7.678572 -21.5,7.678572 -11.872991,0 -21.5,-3.436161 -21.5,-7.678572 v -9.78058 c 1.433013,1.122991 3.265312,2.034821 5.264621,2.745089 4.300959,1.535714 10.044531,2.428348 16.235379,2.428348 6.190848,0 11.93058,-0.892634 16.240179,-2.428348 z M 5.264621,35.964509 C 9.56558,37.500223 15.309152,38.392857 21.5,38.392857 c 6.190848,0 11.93058,-0.892634 16.240179,-2.428348 C 39.650223,35.254241 41.569866,34.342411 43,33.21942 v 8.244866 C 43,45.7067 33.372991,49.14286 21.5,49.14286 9.627009,49.14286 0,45.7067 0,41.464286 V 33.21942 c 1.433013,1.122991 3.265312,2.034821 5.264621,2.745089 z"
+     id="path17"
+     style="fill:#616161;fill-opacity:1;stroke-width:0.0959821" />
+</svg>

diff --git a/packages/jldbq-extenison/style/icons/database-solid.svg b/packages/jldbq-extenison/style/icons/database-solid.svg
new file mode 100644
index 0000000000..e867c2bf34
--- /dev/null
+++ b/packages/jldbq-extenison/style/icons/database-solid.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg viewBox="0 0 548 626" version="1.1" width="548" height="626" xmlns="http://www.w3.org/2000/svg">
+   <path fill="#616161" class="jp-icon3 jp-icon-selectable"
+      d="m 498,137 v 48 c 0,44.2 -100.3,80 -224,80 C 150.3,265 50,229.2 50,185 v -48 c 0,-44.18 100.3,-80 224,-80 123.7,0 224,35.82 224,80 z m -54.8,134.7 c 19.9,-7.4 39.9,-16.9 54.8,-28.6 V 345 c 0,44.2 -100.3,80 -224,80 C 150.3,425 50,389.2 50,345 V 243.1 c 14.93,11.7 34.02,21.2 54.85,28.6 44.81,16 104.65,25.3 169.15,25.3 64.5,0 124.3,-9.3 169.2,-25.3 z m -338.35,160 c 44.81,16 104.65,25.3 169.15,25.3 64.5,0 124.3,-9.3 169.2,-25.3 19.9,-7.4 39.9,-16.9 54.8,-28.6 V 489 c 0,44.2 -100.3,80 -224,80 C 150.3,569 50,533.2 50,489 v -85.9 c 14.93,11.7 34.02,21.2 54.85,28.6 z" />
+</svg>
\ No newline at end of file

diff --git a/packages/jldbq-extenison/style/icons/monitor.svg b/packages/jldbq-extenison/style/icons/monitor.svg
new file mode 100644
index 0000000000..5cd7ad7c83
--- /dev/null
+++ b/packages/jldbq-extenison/style/icons/monitor.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="4.2333341mm" height="3.9620304mm" viewBox="0 0 4.2333341 3.9620305" version="1.1"
+  xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+  <g transform="translate(-24.619015,-119.41814)">
+    <path fill="#000000" class="jp-icon3 jp-icon-selectable"
+      style="stroke-width:0.171832;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
+      d="m 24.825721,119.41814 c -0.114651,0 -0.206706,0.0921 -0.206706,0.2067 v 2.75642 c 0,0.11466 0.09205,0.20671 0.206706,0.20671 h 1.110527 v 0.52658 h -0.644406 a 0.13249998,0.13249998 0 0 0 -0.132808,0.13281 0.13249998,0.13249998 0 0 0 0.132808,0.13281 h 2.886646 a 0.13249998,0.13249998 0 0 0 0.132808,-0.13281 0.13249998,0.13249998 0 0 0 -0.132808,-0.13281 h -0.652157 v -0.52658 h 1.118795 c 0.114651,0 0.207223,-0.0921 0.207223,-0.20671 v -2.75642 c 0,-0.11465 -0.09257,-0.2067 -0.207223,-0.2067 z m 0.23151,0.19172 h 3.356901 c 0.100775,0 0.181901,0.0811 0.181901,0.1819 v 2.42259 c 0,0.10077 -0.08113,0.1819 -0.181901,0.1819 h -3.356901 c -0.100774,0 -0.181901,-0.0811 -0.181901,-0.1819 v -2.42259 c 0,-0.10078 0.08113,-0.1819 0.181901,-0.1819 z m 2.596224,0.60461 a 0.08250825,0.08250825 0 0 0 -0.06873,0.0289 l -0.933277,1.1312 -0.519865,-0.61133 a 0.08250825,0.08250825 0 0 0 -0.128674,0.004 l -0.666109,0.88625 a 0.0825,0.0825 0 0 0 0.0155,0.11524 0.0825,0.0825 0 0 0 0.115238,-0.0155 l 0.605648,-0.80461 0.517798,0.60772 a 0.08250825,0.08250825 0 0 0 0.126607,0 l 0.923975,-1.12345 0.505912,0.74259 a 0.0825,0.0825 0 0 0 0.115238,0.0212 0.0825,0.0825 0 0 0 0.02119,-0.11524 l -0.566374,-0.83199 a 0.08250825,0.08250825 0 0 0 -0.06408,-0.0351 z m -1.453141,2.3735 h 1.0604 v 0.52658 h -1.0604 z" />
+  </g>
+</svg>
\ No newline at end of file

diff --git a/packages/jldbq-extenison/style/icons/timer.svg b/packages/jldbq-extenison/style/icons/timer.svg
new file mode 100644
index 0000000000..94d2d8509f
--- /dev/null
+++ b/packages/jldbq-extenison/style/icons/timer.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="4.2782922mm" height="4.26331mm" viewBox="0 0 4.2782922 4.26331" version="1.1"
+  xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+  <g transform="translate(-24.62537,-119.25549)">
+    <path fill="#000000" class="jp-icon3 jp-icon-selectable"
+      style="stroke-width:0.200096;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
+      d="m 24.826392,119.25549 c -0.11134,0 -0.201022,0.0897 -0.201022,0.20102 v 3.83129 c 0,0.11134 0.08968,0.20102 0.201022,0.20102 h 2.345076 a 1.2378182,1.2378182 0 0 1 -0.238745,-0.19172 h -1.944584 c -0.101249,0 -0.182418,-0.0817 -0.182418,-0.18293 v -2.5647 h 3.614767 v 0.80357 a 1.2378182,1.2378182 0 0 1 0.18035,0.12247 v -2.019 c 0,-0.11134 -0.08968,-0.20102 -0.201021,-0.20102 z m 0.161747,0.19172 h 3.249931 c 0.101248,0 0.182418,0.0817 0.182418,0.18293 v 0.75551 h -3.614767 v -0.75551 c 0,-0.10124 0.08117,-0.18293 0.182418,-0.18293 z m 1.805058,0.33951 a 0.14616301,0.14616301 0 0 0 -0.146245,0.14625 0.14616301,0.14616301 0 0 0 0.146245,0.14624 0.14616301,0.14616301 0 0 0 0.146244,-0.14624 0.14616301,0.14616301 0 0 0 -0.146244,-0.14625 z m 0.538985,0 a 0.14616301,0.14616301 0 0 0 -0.146244,0.14625 0.14616301,0.14616301 0 0 0 0.146244,0.14624 0.14616301,0.14616301 0 0 0 0.146244,-0.14624 0.14616301,0.14616301 0 0 0 -0.146244,-0.14625 z m 0.538985,0 a 0.14616301,0.14616301 0 0 0 -0.146244,0.14625 0.14616301,0.14616301 0 0 0 0.146244,0.14624 0.14616301,0.14616301 0 0 0 0.146244,-0.14624 0.14616301,0.14616301 0 0 0 -0.146244,-0.14625 z m -1.506368,1.07332 a 0.0825,0.0825 0 0 0 -0.05839,0.0238 l -0.592212,0.56638 -0.363285,-0.33021 a 0.0825,0.0825 0 0 0 -0.116789,0.004 0.0825,0.0825 0 0 0 0.0057,0.11731 l 0.42013,0.3824 a 0.08250825,0.08250825 0 0 0 0.113171,0 l 0.648539,-0.62115 a 0.0825,0.0825 0 0 0 0.0021,-0.11679 0.0825,0.0825 0 0 0 -0.05891,-0.0253 z m 1.456242,0.49351 a 1.0825199,1.0825199 0 0 0 -1.082621,1.08262 1.0825199,1.0825199 0 0 0 1.082621,1.08263 1.0825199,1.0825199 0 0 0 1.082621,-1.08263 1.0825199,1.0825199 0 0 0 -1.082621,-1.08262 z m -0.0047,0.17829 a 0.89981608,0.89981608 0 0 1 0.899687,0.89968 0.89981608,0.89981608 0 0 1 -0.899687,0.89969 0.89981608,0.89981608 0 0 1 -0.899687,-0.89969 0.89981608,0.89981608 0 0 1 0.899687,-0.89968 z m -0.0217,0.27388 a 0.0825,0.0825 0 0 0 -0.08423,0.0817 v 0.62115 a 0.08250825,0.08250825 0 0 0 0.08423,0.0842 h 0.546737 a 0.0825,0.0825 0 0 0 0.08216,-0.0842 0.0825,0.0825 0 0 0 -0.08216,-0.0817 H 27.87629 v -0.5395 a 0.0825,0.0825 0 0 0 -0.08165,-0.0817 z m -1.424203,0.0956 a 0.0825,0.0825 0 0 0 -0.05839,0.0233 l -0.593762,0.56844 -0.363285,-0.33228 a 0.0825,0.0825 0 0 0 -0.117306,0.004 0.0825,0.0825 0 0 0 0.0062,0.11731 l 0.419613,0.38499 a 0.08250825,0.08250825 0 0 0 0.113171,-0.002 l 0.64854,-0.62115 a 0.0825,0.0825 0 0 0 0.0021,-0.1173 0.0825,0.0825 0 0 0 -0.05684,-0.0253 z" />
+  </g>
+</svg>
\ No newline at end of file

diff --git a/packages/jldbq-extenison/style/img/arrow.png b/packages/jldbq-extenison/style/img/arrow.png
new file mode 100644
index 0000000000..50c9571f4e
Binary files /dev/null and b/packages/jldbq-extenison/style/img/arrow.png differ

diff --git a/packages/jldbq-extenison/style/img/delete.png b/packages/jldbq-extenison/style/img/delete.png
new file mode 100644
index 0000000000..a64b86a722
Binary files /dev/null and b/packages/jldbq-extenison/style/img/delete.png differ

diff --git a/packages/jldbq-extenison/style/index.css b/packages/jldbq-extenison/style/index.css
new file mode 100644
index 0000000000..2257bf1a6e
--- /dev/null
+++ b/packages/jldbq-extenison/style/index.css
@@ -0,0 +1,12 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+/* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
+@import url('~@lumino/widgets/style/index.css');
+@import url('~@jupyterlab/ui-components/style/index.css');
+@import url('~@jupyterlab/apputils/style/index.css');
+@import url('~@jupyterlab/application/style/index.css');
+
+@import url('./base.css');

diff --git a/packages/jldbq-extenison/style/index.js b/packages/jldbq-extenison/style/index.js
new file mode 100644
index 0000000000..6f57f28e14
--- /dev/null
+++ b/packages/jldbq-extenison/style/index.js
@@ -0,0 +1,12 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+/* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
+import '@lumino/widgets/style/index.js';
+import '@jupyterlab/ui-components/style/index.js';
+import '@jupyterlab/apputils/style/index.js';
+import '@jupyterlab/application/style/index.js';
+
+import './base.css';

diff --git a/packages/jldbq-extenison/style/syncform.css b/packages/jldbq-extenison/style/syncform.css
new file mode 100644
index 0000000000..701d4a2e25
--- /dev/null
+++ b/packages/jldbq-extenison/style/syncform.css
@@ -0,0 +1,45 @@
+@import '~antd/dist/antd.css';
+
+.arrow {
+  display: inline-flex;
+  width: 10%;
+  height: 300px;
+  justify-content: center;
+  align-items: center;
+}
+
+.plane {
+  height: 265px;
+  border: 1px solid #d9d9d9;
+  margin-top: 10px;
+  padding: 10px 0 0 10px;
+  overflow-y: scroll;
+}
+
+.line {
+  height: 307px;
+  border-left: 1px solid #d9d9d9;
+  margin: 0 5%;
+}
+
+.mappings {
+  width: 30%;
+  height: 307px;
+  border: 1px solid #d9d9d9;
+  padding-left: 15px;
+}
+
+.mappings__title {
+  height: 32px;
+  line-height: 32px;
+  font-weight: 500;
+}
+
+.mappings__content {
+  height: 260px;
+  overflow-y: scroll;
+}
+
+#datasyncForm > .ant-form-item:last-child {
+  margin-bottom: 0;
+}
  • 去除一些不符合需求的组件
diff --git a/packages/launcher-extension/src/index.ts b/packages/launcher-extension/src/index.ts
index 94dd0d7538..da8d738526 100644
--- a/packages/launcher-extension/src/index.ts
+++ b/packages/launcher-extension/src/index.ts
@@ -32,9 +32,9 @@ const plugin: JupyterFrontEndPlugin<ILauncher> = {
   activate,
   id: '@jupyterlab/launcher-extension:plugin',
   requires: [ITranslator],
-  optional: [ILabShell, ICommandPalette],
-  provides: ILauncher,
-  autoStart: true
+  optional: [ILabShell, ICommandPalette]
+  // provides: ILauncher,
+  // autoStart: true
 };

 /**
diff --git a/packages/metapackage/package.json b/packages/metapackage/package.json
index 2832703b72..921f346507 100644
--- a/packages/metapackage/package.json
+++ b/packages/metapackage/package.json
@@ -15,13 +15,16 @@
   "sideEffects": false,
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
+  "style": "style/index.css",
   "directories": {
     "lib": "lib/"
   },
   "files": [
     "lib/*.d.ts",
     "lib/*.js.map",
-    "lib/*.js"
+    "lib/*.js",
+    "style/index.css",
+    "style/index.js"
   ],
   "scripts": {
     "build": "tsc -b",
@@ -75,6 +78,7 @@
     "@jupyterlab/inspector": "^3.4.3",
     "@jupyterlab/inspector-extension": "^3.4.3",
     "@jupyterlab/javascript-extension": "^3.4.3",
+    "@jupyterlab/jldbq-extension": "^1.0.0",
     "@jupyterlab/json-extension": "^3.4.3",
     "@jupyterlab/launcher": "^3.4.3",
     "@jupyterlab/launcher-extension": "^3.4.3",
@@ -86,6 +90,7 @@
     "@jupyterlab/markdownviewer-extension": "^3.4.3",
     "@jupyterlab/mathjax2": "^3.4.3",
     "@jupyterlab/mathjax2-extension": "^3.4.3",
+    "@jupyterlab/mytest-extension": "^1.0.0",
     "@jupyterlab/nbconvert-css": "^3.4.3",
     "@jupyterlab/nbformat": "^3.4.3",
     "@jupyterlab/notebook": "^3.4.3",
@@ -110,7 +115,6 @@
     "@jupyterlab/statusbar-extension": "^3.4.3",
     "@jupyterlab/terminal": "^3.4.3",
     "@jupyterlab/terminal-extension": "^3.4.3",
-    "@jupyterlab/theme-dark-extension": "^3.4.3",
     "@jupyterlab/theme-light-extension": "^3.4.3",
     "@jupyterlab/toc": "^5.4.3",
     "@jupyterlab/toc-extension": "^5.4.3",
@@ -132,5 +136,6 @@
   },
   "publishConfig": {
     "access": "public"
-  }
+  },
+  "styleModule": "style/index.js"
 }

diff --git a/packages/theme-dark-extension/style/theme.css b/packages/metapackage/style/index.css
similarity index 52%
rename from packages/theme-dark-extension/style/theme.css
rename to packages/metapackage/style/index.css
index 4761c94d2b..b2f19abe63 100644
--- a/packages/theme-dark-extension/style/theme.css
+++ b/packages/metapackage/style/index.css
@@ -3,15 +3,6 @@
 | Distributed under the terms of the Modified BSD License.
 |----------------------------------------------------------------------------*/

-@import './variables.css';
-
-/* Set the default typography for monospace elements */
-tt,
-code,
-kbd,
-samp,
-pre {
-  font-family: var(--jp-code-font-family);
-  font-size: var(--jp-code-font-size);
-  line-height: var(--jp-code-line-height);
-}
+/* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
+@import url('~@jupyterlab/jldbq-extension/style/index.css');
+@import url('~@jupyterlab/mytest-extension/style/index.css');

diff --git a/packages/metapackage/style/index.js b/packages/metapackage/style/index.js
new file mode 100644
index 0000000000..cae86f17db
--- /dev/null
+++ b/packages/metapackage/style/index.js
@@ -0,0 +1,8 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+
+/* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
+import '@jupyterlab/jldbq-extension/style/index.js';
+import '@jupyterlab/mytest-extension/style/index.js';
  • 修改 Notebook 的样式和布局, 匹配前端原型
diff --git a/packages/notebook-extension/src/index.ts b/packages/notebook-extension/src/index.ts
index 39a8057b24..c1524b80ad 100644
--- a/packages/notebook-extension/src/index.ts
+++ b/packages/notebook-extension/src/index.ts
@@ -21,7 +21,6 @@ import {
   MainAreaWidget,
   sessionContextDialogs,
   showDialog,
-  Toolbar,
   WidgetTracker
 } from '@jupyterlab/apputils';
 import { Cell, CodeCell, ICellModel, MarkdownCell } from '@jupyterlab/cells';
@@ -94,7 +93,8 @@ import {
 } from '@lumino/coreutils';
 import { DisposableSet, IDisposable } from '@lumino/disposable';
 import { Message, MessageLoop } from '@lumino/messaging';
-import { Menu, Panel } from '@lumino/widgets';
+// import { Menu, Panel } from '@lumino/widgets';
+import { Panel } from '@lumino/widgets';
 import { logNotebookOutput } from './nboutput';

 /**
@@ -270,7 +270,7 @@ const FACTORY = 'Notebook';
  * The excluded Export To ...
  * (returned from nbconvert's export list)
  */
-const FORMAT_EXCLUDE = ['notebook', 'python', 'custom'];
+// const FORMAT_EXCLUDE = ['notebook', 'python', 'custom'];

 /**
  * Setting Id storing the customized toolbar definition.
@@ -477,111 +477,111 @@ export const executionIndicator: JupyterFrontEndPlugin<void> = {
 /**
  * A plugin providing export commands in the main menu and command palette
  */
-export const exportPlugin: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/notebook-extension:export',
-  autoStart: true,
-  requires: [ITranslator, INotebookTracker],
-  optional: [IMainMenu, ICommandPalette],
-  activate: (
-    app: JupyterFrontEnd,
-    translator: ITranslator,
-    tracker: INotebookTracker,
-    mainMenu: IMainMenu | null,
-    palette: ICommandPalette | null
-  ) => {
-    const trans = translator.load('jupyterlab');
-    const { commands, shell } = app;
-    const services = app.serviceManager;
-
-    const isEnabled = (): boolean => {
-      return Private.isEnabled(shell, tracker);
-    };
-
-    commands.addCommand(CommandIDs.exportToFormat, {
-      label: args => {
-        const formatLabel = args['label'] as string;
-        return args['isPalette']
-          ? trans.__('Save and Export Notebook: %1', formatLabel)
-          : formatLabel;
-      },
-      execute: args => {
-        const current = getCurrent(tracker, shell, args);
-
-        if (!current) {
-          return;
-        }
-
-        const url = PageConfig.getNBConvertURL({
-          format: args['format'] as string,
-          download: true,
-          path: current.context.path
-        });
-        const { context } = current;
-
-        if (context.model.dirty && !context.model.readOnly) {
-          return context.save().then(() => {
-            window.open(url, '_blank', 'noopener');
-          });
-        }
-
-        return new Promise<void>(resolve => {
-          window.open(url, '_blank', 'noopener');
-          resolve(undefined);
-        });
-      },
-      isEnabled
-    });
-
-    // Add a notebook group to the File menu.
-    let exportTo: Menu | null | undefined;
-    if (mainMenu) {
-      exportTo = mainMenu.fileMenu.items.find(
-        item =>
-          item.type === 'submenu' &&
-          item.submenu?.id === 'jp-mainmenu-file-notebookexport'
-      )?.submenu;
-    }
-
-    void services.nbconvert.getExportFormats().then(response => {
-      if (response) {
-        const formatLabels: any = Private.getFormatLabels(translator);
-
-        // Convert export list to palette and menu items.
-        const formatList = Object.keys(response);
-        formatList.forEach(function (key) {
-          const capCaseKey = trans.__(key[0].toUpperCase() + key.substr(1));
-          const labelStr = formatLabels[key] ? formatLabels[key] : capCaseKey;
-          let args = {
-            format: key,
-            label: labelStr,
-            isPalette: false
-          };
-          if (FORMAT_EXCLUDE.indexOf(key) === -1) {
-            if (exportTo) {
-              exportTo.addItem({
-                command: CommandIDs.exportToFormat,
-                args: args
-              });
-            }
-            if (palette) {
-              args = {
-                format: key,
-                label: labelStr,
-                isPalette: true
-              };
-              const category = trans.__('Notebook Operations');
-              palette.addItem({
-                command: CommandIDs.exportToFormat,
-                category,
-                args
-              });
-            }
-          }
-        });
-      }
-    });
-  }
-};
+// export const exportPlugin: JupyterFrontEndPlugin<void> = {
+//   id: '@jupyterlab/notebook-extension:export',
+//   autoStart: true,
+//   requires: [ITranslator, INotebookTracker],
+//   optional: [IMainMenu, ICommandPalette],
+//   activate: (
+//     app: JupyterFrontEnd,
+//     translator: ITranslator,
+//     tracker: INotebookTracker,
+//     mainMenu: IMainMenu | null,
+//     palette: ICommandPalette | null
+//   ) => {
+//     const trans = translator.load('jupyterlab');
+//     const { commands, shell } = app;
+//     const services = app.serviceManager;
+
+//     const isEnabled = (): boolean => {
+//       return Private.isEnabled(shell, tracker);
+//     };
+
+//     commands.addCommand(CommandIDs.exportToFormat, {
+//       label: args => {
+//         const formatLabel = args['label'] as string;
+//         return args['isPalette']
+//           ? trans.__('Save and Export Notebook: %1', formatLabel)
+//           : formatLabel;
+//       },
+//       execute: args => {
+//         const current = getCurrent(tracker, shell, args);
+
+//         if (!current) {
+//           return;
+//         }
+
+//         const url = PageConfig.getNBConvertURL({
+//           format: args['format'] as string,
+//           download: true,
+//           path: current.context.path
+//         });
+//         const { context } = current;
+
+//         if (context.model.dirty && !context.model.readOnly) {
+//           return context.save().then(() => {
+//             window.open(url, '_blank', 'noopener');
+//           });
+//         }
+
+//         return new Promise<void>(resolve => {
+//           window.open(url, '_blank', 'noopener');
+//           resolve(undefined);
+//         });
+//       },
+//       isEnabled
+//     });
+
+//     // Add a notebook group to the File menu.
+//     let exportTo: Menu | null | undefined;
+//     if (mainMenu) {
+//       exportTo = mainMenu.fileMenu.items.find(
+//         item =>
+//           item.type === 'submenu' &&
+//           item.submenu?.id === 'jp-mainmenu-file-notebookexport'
+//       )?.submenu;
+//     }
+
+//     void services.nbconvert.getExportFormats().then(response => {
+//       if (response) {
+//         const formatLabels: any = Private.getFormatLabels(translator);
+
+//         // Convert export list to palette and menu items.
+//         const formatList = Object.keys(response);
+//         formatList.forEach(function (key) {
+//           const capCaseKey = trans.__(key[0].toUpperCase() + key.substr(1));
+//           const labelStr = formatLabels[key] ? formatLabels[key] : capCaseKey;
+//           let args = {
+//             format: key,
+//             label: labelStr,
+//             isPalette: false
+//           };
+//           if (FORMAT_EXCLUDE.indexOf(key) === -1) {
+//             if (exportTo) {
+//               exportTo.addItem({
+//                 command: CommandIDs.exportToFormat,
+//                 args: args
+//               });
+//             }
+//             if (palette) {
+//               args = {
+//                 format: key,
+//                 label: labelStr,
+//                 isPalette: true
+//               };
+//               const category = trans.__('Notebook Operations');
+//               palette.addItem({
+//                 command: CommandIDs.exportToFormat,
+//                 category,
+//                 args
+//               });
+//             }
+//           }
+//         });
+//       }
+//     });
+//   }
+// };

 /**
  * A plugin that adds a notebook trust status item to the status bar.
@@ -682,7 +682,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
   factory,
   trackerPlugin,
   executionIndicator,
-  exportPlugin,
+  // exportPlugin,
   tools,
   commandEditItem,
   notebookTrustItem,
@@ -844,13 +844,6 @@ function activateWidgetFactory(
   toolbarRegistry.registerFactory<NotebookPanel>(FACTORY, 'cellType', panel =>
     ToolbarItems.createCellTypeItem(panel, translator)
   );
-  toolbarRegistry.registerFactory<NotebookPanel>(FACTORY, 'kernelName', panel =>
-    Toolbar.createKernelNameItem(
-      panel.sessionContext,
-      sessionContextDialogs,
-      translator
-    )
-  );

   toolbarRegistry.registerFactory<NotebookPanel>(
     FACTORY,

diff --git a/packages/notebook/src/default-toolbar.tsx b/packages/notebook/src/default-toolbar.tsx
index bf94760cc6..5e931bb525 100644
--- a/packages/notebook/src/default-toolbar.tsx
+++ b/packages/notebook/src/default-toolbar.tsx
@@ -278,15 +278,7 @@ export namespace ToolbarItems {
         widget: createRestartRunAllButton(panel, sessionDialogs, translator)
       },
       { name: 'cellType', widget: createCellTypeItem(panel, translator) },
-      { name: 'spacer', widget: Toolbar.createSpacerItem() },
-      {
-        name: 'kernelName',
-        widget: Toolbar.createKernelNameItem(
-          panel.sessionContext,
-          sessionDialogs,
-          translator
-        )
-      }
+      { name: 'spacer', widget: Toolbar.createSpacerItem() }
     ];
   }
 }

diff --git a/packages/notebook/src/panel.ts b/packages/notebook/src/panel.ts
index e1f70352f4..a30e01bd00 100644
--- a/packages/notebook/src/panel.ts
+++ b/packages/notebook/src/panel.ts
@@ -17,6 +17,7 @@ import {
   TranslationBundle
 } from '@jupyterlab/translation';
 import { each } from '@lumino/algorithm';
+import { BoxLayout } from '@lumino/widgets';
 import { Token } from '@lumino/coreutils';
 import { INotebookModel } from './model';
 import { Notebook, StaticNotebook } from './widget';
@@ -54,6 +55,8 @@ export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
     // Set up CSS classes
     this.addClass(NOTEBOOK_PANEL_CLASS);
     this.toolbar.addClass(NOTEBOOK_PANEL_TOOLBAR_CLASS);
+    BoxLayout.setSizeBasis(this.toolbar, 42);
+    (this.layout as BoxLayout).spacing = 10;
     this.content.addClass(NOTEBOOK_PANEL_NOTEBOOK_CLASS);

     // Set up things related to the context
@@ -136,7 +139,7 @@ export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
   /**
    * Set URI fragment identifier.
    */
-  setFragment(fragment: string) {
+  setFragment(fragment: string): void {
     void this.context.ready.then(() => {
       this.content.setFragment(fragment);
     });
@@ -154,7 +157,7 @@ export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
    * Prints the notebook by converting to HTML with nbconvert.
    */
   [Printing.symbol]() {
-    return async () => {
+    return async (): Promise<void> => {
       // Save before generating HTML
       if (this.context.model.dirty && !this.context.model.readOnly) {
         await this.context.save();

diff --git a/packages/notebook/style/base.css b/packages/notebook/style/base.css
index 4d90131c53..11b23e5da2 100644
--- a/packages/notebook/style/base.css
+++ b/packages/notebook/style/base.css
@@ -28,6 +28,7 @@
 .jp-NotebookPanel {
   display: block;
   height: 100%;
+  padding: 20px;
 }

 .jp-NotebookPanel.jp-Document {
@@ -36,7 +37,7 @@
 }

 .jp-Notebook {
-  padding: var(--jp-notebook-padding);
+  padding-right: var(--jp-notebook-padding);
   outline: none;
   overflow: auto;
   background: var(--jp-layout-color0);
@@ -95,7 +96,7 @@

 /* cell is active */
 .jp-Notebook .jp-Cell.jp-mod-active .jp-Collapser {
-  background: var(--jp-brand-color1);
+  background: #147bd1;
 }

 /* cell is dirty */

diff --git a/packages/notebook/style/toolbar.css b/packages/notebook/style/toolbar.css
index c1a3259af0..28a4859c59 100644
--- a/packages/notebook/style/toolbar.css
+++ b/packages/notebook/style/toolbar.css
@@ -17,8 +17,16 @@
 | Styles
 |----------------------------------------------------------------------------*/

-.jp-NotebookPanel-toolbar {
-  padding: var(--jp-notebook-toolbar-padding);
+.jp-Toolbar.jp-NotebookPanel-toolbar {
+  align-items: center;
+  background-color: #f7f9fd;
+  border: none;
+  box-shadow: none;
+  padding: 0 24px;
+}
+
+.jp-Toolbar.jp-NotebookPanel-toolbar > .jp-Toolbar-item {
+  height: auto;
 }

 .jp-Toolbar-item.jp-Notebook-toolbarCellType .jp-select-wrapper.jp-mod-focused {
  • 去除不符合需求的组件和菜单项
diff --git a/packages/running-extension/src/index.ts b/packages/running-extension/src/index.ts
index fa4e785803..5a8efd480b 100644
--- a/packages/running-extension/src/index.ts
+++ b/packages/running-extension/src/index.ts
@@ -68,7 +68,7 @@ function activate(
   addKernelRunningSessionManager(runningSessionManagers, translator, app);
   // Rank has been chosen somewhat arbitrarily to give priority to the running
   // sessions widget in the sidebar.
-  app.shell.add(running, 'left', { rank: 200 });
+  // app.shell.add(running, 'left', { rank: 200 });

   return runningSessionManagers;
 }

diff --git a/packages/statusbar/src/statusbar.ts b/packages/statusbar/src/statusbar.ts
index 58da932baa..dadfbfcdc1 100644
--- a/packages/statusbar/src/statusbar.ts
+++ b/packages/statusbar/src/statusbar.ts
@@ -116,7 +116,7 @@ export class StatusBar extends Widget implements IStatusBar {
   /**
    * Dispose of the status bar.
    */
-  dispose() {
+  dispose(): void {
     this._leftRankItems.length = 0;
     this._rightRankItems.length = 0;
     this._disposables.dispose();
@@ -126,7 +126,7 @@ export class StatusBar extends Widget implements IStatusBar {
   /**
    * Handle an 'update-request' message to the status bar.
    */
-  protected onUpdateRequest(msg: Message) {
+  protected onUpdateRequest(msg: Message): void {
     this._refreshAll();
     super.onUpdateRequest(msg);
   }

diff --git a/packages/terminal-extension/src/index.ts b/packages/terminal-extension/src/index.ts
index 6aca46fbd2..5a18f9f165 100644
--- a/packages/terminal-extension/src/index.ts
+++ b/packages/terminal-extension/src/index.ts
@@ -17,7 +17,8 @@ import {
   WidgetTracker
 } from '@jupyterlab/apputils';
 import { ILauncher } from '@jupyterlab/launcher';
-import { IFileMenu, IMainMenu } from '@jupyterlab/mainmenu';
+// import { IFileMenu, IMainMenu } from '@jupyterlab/mainmenu';
+import { IMainMenu } from '@jupyterlab/mainmenu';
 import { IRunningSessionManagers, IRunningSessions } from '@jupyterlab/running';
 import { Terminal, TerminalAPI } from '@jupyterlab/services';
 import { ISettingRegistry } from '@jupyterlab/settingregistry';
@@ -27,7 +28,7 @@ import * as WidgetModuleType from '@jupyterlab/terminal/lib/widget';
 import { ITranslator } from '@jupyterlab/translation';
 import { terminalIcon } from '@jupyterlab/ui-components';
 import { toArray } from '@lumino/algorithm';
-import { Menu } from '@lumino/widgets';
+// import { Menu } from '@lumino/widgets';

 /**
  * The command IDs used by the terminal plugin.
@@ -85,7 +86,8 @@ function activate(
   runningSessionManagers: IRunningSessionManagers | null
 ): ITerminalTracker {
   const trans = translator.load('jupyterlab');
-  const { serviceManager, commands } = app;
+  // const { serviceManager, commands } = app;
+  const { serviceManager } = app;
   const category = trans.__('Terminal');
   const namespace = 'terminal';
   const tracker = new WidgetTracker<MainAreaWidget<ITerminal.ITerminal>>({
@@ -171,57 +173,57 @@ function activate(

   addCommands(app, tracker, settingRegistry, translator, options);

-  if (mainMenu) {
-    // Add "Terminal Theme" menu below "Theme" menu.
-    const themeMenu = new Menu({ commands });
-    themeMenu.title.label = trans._p('menu', 'Terminal Theme');
-    themeMenu.addItem({
-      command: CommandIDs.setTheme,
-      args: {
-        theme: 'inherit',
-        displayName: trans.__('Inherit'),
-        isPalette: false
-      }
-    });
-    themeMenu.addItem({
-      command: CommandIDs.setTheme,
-      args: {
-        theme: 'light',
-        displayName: trans.__('Light'),
-        isPalette: false
-      }
-    });
-    themeMenu.addItem({
-      command: CommandIDs.setTheme,
-      args: { theme: 'dark', displayName: trans.__('Dark'), isPalette: false }
-    });
-
-    // Add some commands to the "View" menu.
-    mainMenu.settingsMenu.addGroup(
-      [
-        { command: CommandIDs.increaseFont },
-        { command: CommandIDs.decreaseFont },
-        { type: 'submenu', submenu: themeMenu }
-      ],
-      40
-    );
-
-    // Add terminal creation to the file menu.
-    mainMenu.fileMenu.newMenu.addItem({
-      command: CommandIDs.createNew,
-      rank: 20
-    });
-
-    // Add terminal close-and-shutdown to the file menu.
-    mainMenu.fileMenu.closeAndCleaners.add({
-      tracker,
-      closeAndCleanupLabel: (n: number) => trans.__('Shutdown Terminal'),
-      closeAndCleanup: (current: MainAreaWidget<ITerminal.ITerminal>) => {
-        // The widget is automatically disposed upon session shutdown.
-        return current.content.session.shutdown();
-      }
-    } as IFileMenu.ICloseAndCleaner<MainAreaWidget<ITerminal.ITerminal>>);
-  }
+  // if (mainMenu) {
+  //   // Add "Terminal Theme" menu below "Theme" menu.
+  //   const themeMenu = new Menu({ commands });
+  //   themeMenu.title.label = trans._p('menu', 'Terminal Theme');
+  //   themeMenu.addItem({
+  //     command: CommandIDs.setTheme,
+  //     args: {
+  //       theme: 'inherit',
+  //       displayName: trans.__('Inherit'),
+  //       isPalette: false
+  //     }
+  //   });
+  //   themeMenu.addItem({
+  //     command: CommandIDs.setTheme,
+  //     args: {
+  //       theme: 'light',
+  //       displayName: trans.__('Light'),
+  //       isPalette: false
+  //     }
+  //   });
+  //   themeMenu.addItem({
+  //     command: CommandIDs.setTheme,
+  //     args: { theme: 'dark', displayName: trans.__('Dark'), isPalette: false }
+  //   });
+
+  //   // Add some commands to the "View" menu.
+  //   mainMenu.settingsMenu.addGroup(
+  //     [
+  //       { command: CommandIDs.increaseFont },
+  //       { command: CommandIDs.decreaseFont },
+  //       { type: 'submenu', submenu: themeMenu }
+  //     ],
+  //     40
+  //   );
+
+  //   // Add terminal creation to the file menu.
+  //   mainMenu.fileMenu.newMenu.addItem({
+  //     command: CommandIDs.createNew,
+  //     rank: 20
+  //   });
+
+  //   // Add terminal close-and-shutdown to the file menu.
+  //   mainMenu.fileMenu.closeAndCleaners.add({
+  //     tracker,
+  //     closeAndCleanupLabel: (n: number) => trans.__('Shutdown Terminal'),
+  //     closeAndCleanup: (current: MainAreaWidget<ITerminal.ITerminal>) => {
+  //       // The widget is automatically disposed upon session shutdown.
+  //       return current.content.session.shutdown();
+  //     }
+  //   } as IFileMenu.ICloseAndCleaner<MainAreaWidget<ITerminal.ITerminal>>);
+  // }

   if (palette) {
     // Add command palette items.

diff --git a/packages/theme-dark-extension/README.md b/packages/theme-dark-extension/README.md
deleted file mode 100644
index 6c19ca27cd..0000000000
--- a/packages/theme-dark-extension/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# @jupyterlab/theme-dark-extension
-
-A JupyterLab theme extension which provides the default dark-colored theme.

diff --git a/packages/theme-dark-extension/package.json b/packages/theme-dark-extension/package.json
deleted file mode 100644
index eaded8c880..0000000000
--- a/packages/theme-dark-extension/package.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "name": "@jupyterlab/theme-dark-extension",
-  "version": "3.4.3",
-  "description": "JupyterLab - Default Dark Theme",
-  "homepage": "https://github.com/jupyterlab/jupyterlab",
-  "bugs": {
-    "url": "https://github.com/jupyterlab/jupyterlab/issues"
-  },
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/jupyterlab/jupyterlab.git"
-  },
-  "license": "BSD-3-Clause",
-  "author": "Project Jupyter",
-  "sideEffects": true,
-  "main": "lib/index.js",
-  "types": "lib/index.d.ts",
-  "directories": {
-    "lib": "lib/"
-  },
-  "files": [
-    "lib/*.d.ts",
-    "lib/*.js.map",
-    "lib/*.js",
-    "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
-    "style/index.js"
-  ],
-  "scripts": {
-    "build": "tsc -b",
-    "clean": "rimraf lib && rimraf tsconfig.tsbuildinfo",
-    "watch": "tsc -b --watch"
-  },
-  "dependencies": {
-    "@jupyterlab/application": "^3.4.3",
-    "@jupyterlab/apputils": "^3.4.3",
-    "@jupyterlab/translation": "^3.4.3"
-  },
-  "devDependencies": {
-    "rimraf": "~3.0.0",
-    "typedoc": "~0.21.2",
-    "typescript": "~4.1.3"
-  },
-  "publishConfig": {
-    "access": "public"
-  },
-  "jupyterlab": {
-    "extension": true,
-    "themePath": "style/theme.css"
-  }
-}

diff --git a/packages/theme-dark-extension/src/index.ts b/packages/theme-dark-extension/src/index.ts
deleted file mode 100644
index 6f20ea8e9a..0000000000
--- a/packages/theme-dark-extension/src/index.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-/**
- * @packageDocumentation
- * @module theme-dark-extension
- */
-
-import {
-  JupyterFrontEnd,
-  JupyterFrontEndPlugin
-} from '@jupyterlab/application';
-import { IThemeManager } from '@jupyterlab/apputils';
-import { ITranslator } from '@jupyterlab/translation';
-
-/**
- * A plugin for the Jupyter Dark Theme.
- */
-const plugin: JupyterFrontEndPlugin<void> = {
-  id: '@jupyterlab/theme-dark-extension:plugin',
-  requires: [IThemeManager, ITranslator],
-  activate: (
-    app: JupyterFrontEnd,
-    manager: IThemeManager,
-    translator: ITranslator
-  ) => {
-    const trans = translator.load('jupyterlab');
-    const style = '@jupyterlab/theme-dark-extension/index.css';
-    manager.register({
-      name: 'JupyterLab Dark',
-      displayName: trans.__('JupyterLab Dark'),
-      isLight: false,
-      themeScrollbars: true,
-      load: () => manager.loadCSS(style),
-      unload: () => Promise.resolve(undefined)
-    });
-  },
-  autoStart: true
-};
-
-export default plugin;

diff --git a/packages/theme-dark-extension/style/variables.css b/packages/theme-dark-extension/style/variables.css
deleted file mode 100644
index ff87a764c3..0000000000
--- a/packages/theme-dark-extension/style/variables.css
+++ /dev/null
@@ -1,410 +0,0 @@
-/*-----------------------------------------------------------------------------
-| Copyright (c) Jupyter Development Team.
-| Distributed under the terms of the Modified BSD License.
-|----------------------------------------------------------------------------*/
-
-/*
-The following CSS variables define the main, public API for styling JupyterLab.
-These variables should be used by all plugins wherever possible. In other
-words, plugins should not define custom colors, sizes, etc unless absolutely
-necessary. This enables users to change the visual theme of JupyterLab
-by changing these variables.
-
-Many variables appear in an ordered sequence (0,1,2,3). These sequences
-are designed to work well together, so for example, `--jp-border-color1` should
-be used with `--jp-layout-color1`. The numbers have the following meanings:
-
-* 0: super-primary, reserved for special emphasis
-* 1: primary, most important under normal situations
-* 2: secondary, next most important under normal situations
-* 3: tertiary, next most important under normal situations
-
-Throughout JupyterLab, we are mostly following principles from Google's
-Material Design when selecting colors. We are not, however, following
-all of MD as it is not optimized for dense, information rich UIs.
-*/
-
-:root {
-  /* Elevation
-   *
-   * We style box-shadows using Material Design's idea of elevation. These particular numbers are taken from here:
-   *
-   * https://github.com/material-components/material-components-web
-   * https://material-components-web.appspot.com/elevation.html
-   */
-
-  /* The dark theme shadows need a bit of work, but this will probably also require work on the core layout
-   * colors used in the theme as well. */
-  --jp-shadow-base-lightness: 32;
-  --jp-shadow-umbra-color: rgba(
-    var(--jp-shadow-base-lightness),
-    var(--jp-shadow-base-lightness),
-    var(--jp-shadow-base-lightness),
-    0.2
-  );
-  --jp-shadow-penumbra-color: rgba(
-    var(--jp-shadow-base-lightness),
-    var(--jp-shadow-base-lightness),
-    var(--jp-shadow-base-lightness),
-    0.14
-  );
-  --jp-shadow-ambient-color: rgba(
-    var(--jp-shadow-base-lightness),
-    var(--jp-shadow-base-lightness),
-    var(--jp-shadow-base-lightness),
-    0.12
-  );
-  --jp-elevation-z0: none;
-  --jp-elevation-z1: 0px 2px 1px -1px var(--jp-shadow-umbra-color),
-    0px 1px 1px 0px var(--jp-shadow-penumbra-color),
-    0px 1px 3px 0px var(--jp-shadow-ambient-color);
-  --jp-elevation-z2: 0px 3px 1px -2px var(--jp-shadow-umbra-color),
-    0px 2px 2px 0px var(--jp-shadow-penumbra-color),
-    0px 1px 5px 0px var(--jp-shadow-ambient-color);
-  --jp-elevation-z4: 0px 2px 4px -1px var(--jp-shadow-umbra-color),
-    0px 4px 5px 0px var(--jp-shadow-penumbra-color),
-    0px 1px 10px 0px var(--jp-shadow-ambient-color);
-  --jp-elevation-z6: 0px 3px 5px -1px var(--jp-shadow-umbra-color),
-    0px 6px 10px 0px var(--jp-shadow-penumbra-color),
-    0px 1px 18px 0px var(--jp-shadow-ambient-color);
-  --jp-elevation-z8: 0px 5px 5px -3px var(--jp-shadow-umbra-color),
-    0px 8px 10px 1px var(--jp-shadow-penumbra-color),
-    0px 3px 14px 2px var(--jp-shadow-ambient-color);
-  --jp-elevation-z12: 0px 7px 8px -4px var(--jp-shadow-umbra-color),
-    0px 12px 17px 2px var(--jp-shadow-penumbra-color),
-    0px 5px 22px 4px var(--jp-shadow-ambient-color);
-  --jp-elevation-z16: 0px 8px 10px -5px var(--jp-shadow-umbra-color),
-    0px 16px 24px 2px var(--jp-shadow-penumbra-color),
-    0px 6px 30px 5px var(--jp-shadow-ambient-color);
-  --jp-elevation-z20: 0px 10px 13px -6px var(--jp-shadow-umbra-color),
-    0px 20px 31px 3px var(--jp-shadow-penumbra-color),
-    0px 8px 38px 7px var(--jp-shadow-ambient-color);
-  --jp-elevation-z24: 0px 11px 15px -7px var(--jp-shadow-umbra-color),
-    0px 24px 38px 3px var(--jp-shadow-penumbra-color),
-    0px 9px 46px 8px var(--jp-shadow-ambient-color);
-
-  /* Borders
-   *
-   * The following variables, specify the visual styling of borders in JupyterLab.
-   */
-
-  --jp-border-width: 1px;
-  --jp-border-color0: var(--md-grey-700);
-  --jp-border-color1: var(--md-grey-700);
-  --jp-border-color2: var(--md-grey-800);
-  --jp-border-color3: var(--md-grey-900);
-  --jp-inverse-border-color: var(--md-grey-600);
-  --jp-border-radius: 2px;
-
-  /* UI Fonts
-   *
-   * The UI font CSS variables are used for the typography all of the JupyterLab
-   * user interface elements that are not directly user generated content.
-   *
-   * The font sizing here is done assuming that the body font size of --jp-ui-font-size1
-   * is applied to a parent element. When children elements, such as headings, are sized
-   * in em all things will be computed relative to that body size.
-   */
-
-  --jp-ui-font-scale-factor: 1.2;
-  --jp-ui-font-size0: 0.83333em;
-  --jp-ui-font-size1: 13px; /* Base font size */
-  --jp-ui-font-size2: 1.2em;
-  --jp-ui-font-size3: 1.44em;
-
-  --jp-ui-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica,
-    Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
-
-  /*
-   * Use these font colors against the corresponding main layout colors.
-   * In a light theme, these go from dark to light.
-   */
-
-  /* Defaults use Material Design specification */
-  --jp-ui-font-color0: rgba(255, 255, 255, 1);
-  --jp-ui-font-color1: rgba(255, 255, 255, 0.87);
-  --jp-ui-font-color2: rgba(255, 255, 255, 0.54);
-  --jp-ui-font-color3: rgba(255, 255, 255, 0.38);
-
-  /*
-   * Use these against the brand/accent/warn/error colors.
-   * These will typically go from light to darker, in both a dark and light theme.
-   */
-
-  --jp-ui-inverse-font-color0: rgba(0, 0, 0, 1);
-  --jp-ui-inverse-font-color1: rgba(0, 0, 0, 0.8);
-  --jp-ui-inverse-font-color2: rgba(0, 0, 0, 0.5);
-  --jp-ui-inverse-font-color3: rgba(0, 0, 0, 0.3);
-
-  /* Content Fonts
-   *
-   * Content font variables are used for typography of user generated content.
-   *
-   * The font sizing here is done assuming that the body font size of --jp-content-font-size1
-   * is applied to a parent element. When children elements, such as headings, are sized
-   * in em all things will be computed relative to that body size.
-   */
-
-  --jp-content-line-height: 1.6;
-  --jp-content-font-scale-factor: 1.2;
-  --jp-content-font-size0: 0.83333em;
-  --jp-content-font-size1: 14px; /* Base font size */
-  --jp-content-font-size2: 1.2em;
-  --jp-content-font-size3: 1.44em;
-  --jp-content-font-size4: 1.728em;
-  --jp-content-font-size5: 2.0736em;
-
-  /* This gives a magnification of about 125% in presentation mode over normal. */
-  --jp-content-presentation-font-size1: 17px;
-
-  --jp-content-heading-line-height: 1;
-  --jp-content-heading-margin-top: 1.2em;
-  --jp-content-heading-margin-bottom: 0.8em;
-  --jp-content-heading-font-weight: 500;
-
-  /* Defaults use Material Design specification */
-  --jp-content-font-color0: rgba(255, 255, 255, 1);
-  --jp-content-font-color1: rgba(255, 255, 255, 1);
-  --jp-content-font-color2: rgba(255, 255, 255, 0.7);
-  --jp-content-font-color3: rgba(255, 255, 255, 0.5);
-
-  --jp-content-link-color: var(--md-blue-300);
-
-  --jp-content-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
-    Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
-    'Segoe UI Symbol';
-
-  /*
-   * Code Fonts
-   *
-   * Code font variables are used for typography of code and other monospaces content.
-   */
-
-  --jp-code-font-size: 13px;
-  --jp-code-line-height: 1.3077; /* 17px for 13px base */
-  --jp-code-padding: 5px; /* 5px for 13px base, codemirror highlighting needs integer px value */
-  --jp-code-font-family-default: Menlo, Consolas, 'DejaVu Sans Mono', monospace;
-  --jp-code-font-family: var(--jp-code-font-family-default);
-
-  /* This gives a magnification of about 125% in presentation mode over normal. */
-  --jp-code-presentation-font-size: 16px;
-
-  /* may need to tweak cursor width if you change font size */
-  --jp-code-cursor-width0: 1.4px;
-  --jp-code-cursor-width1: 2px;
-  --jp-code-cursor-width2: 4px;
-
-  /* Layout
-   *
-   * The following are the main layout colors use in JupyterLab. In a light
-   * theme these would go from light to dark.
-   */
-
-  --jp-layout-color0: #111111;
-  --jp-layout-color1: var(--md-grey-900);
-  --jp-layout-color2: var(--md-grey-800);
-  --jp-layout-color3: var(--md-grey-700);
-  --jp-layout-color4: var(--md-grey-600);
-
-  /* Inverse Layout
-   *
-   * The following are the inverse layout colors use in JupyterLab. In a light
-   * theme these would go from dark to light.
-   */
-
-  --jp-inverse-layout-color0: white;
-  --jp-inverse-layout-color1: white;
-  --jp-inverse-layout-color2: var(--md-grey-200);
-  --jp-inverse-layout-color3: var(--md-grey-400);
-  --jp-inverse-layout-color4: var(--md-grey-600);
-
-  /* Brand/accent */
-
-  --jp-brand-color0: var(--md-blue-700);
-  --jp-brand-color1: var(--md-blue-500);
-  --jp-brand-color2: var(--md-blue-300);
-  --jp-brand-color3: var(--md-blue-100);
-  --jp-brand-color4: var(--md-blue-50);
-
-  --jp-accent-color0: var(--md-green-700);
-  --jp-accent-color1: var(--md-green-500);
-  --jp-accent-color2: var(--md-green-300);
-  --jp-accent-color3: var(--md-green-100);
-
-  /* State colors (warn, error, success, info) */
-
-  --jp-warn-color0: var(--md-orange-700);
-  --jp-warn-color1: var(--md-orange-500);
-  --jp-warn-color2: var(--md-orange-300);
-  --jp-warn-color3: var(--md-orange-100);
-
-  --jp-error-color0: var(--md-red-700);
-  --jp-error-color1: var(--md-red-500);
-  --jp-error-color2: var(--md-red-300);
-  --jp-error-color3: var(--md-red-100);
-
-  --jp-success-color0: var(--md-green-700);
-  --jp-success-color1: var(--md-green-500);
-  --jp-success-color2: var(--md-green-300);
-  --jp-success-color3: var(--md-green-100);
-
-  --jp-info-color0: var(--md-cyan-700);
-  --jp-info-color1: var(--md-cyan-500);
-  --jp-info-color2: var(--md-cyan-300);
-  --jp-info-color3: var(--md-cyan-100);
-
-  /* Cell specific styles */
-
-  --jp-cell-padding: 5px;
-
-  --jp-cell-collapser-width: 8px;
-  --jp-cell-collapser-min-height: 20px;
-  --jp-cell-collapser-not-active-hover-opacity: 0.6;
-
-  --jp-cell-editor-background: var(--jp-layout-color1);
-  --jp-cell-editor-border-color: var(--md-grey-700);
-  --jp-cell-editor-box-shadow: inset 0 0 2px var(--md-blue-300);
-  --jp-cell-editor-active-background: var(--jp-layout-color0);
-  --jp-cell-editor-active-border-color: var(--jp-brand-color1);
-
-  --jp-cell-prompt-width: 64px;
-  --jp-cell-prompt-font-family: var(--jp-code-font-family-default);
-  --jp-cell-prompt-letter-spacing: 0px;
-  --jp-cell-prompt-opacity: 1;
-  --jp-cell-prompt-not-active-opacity: 1;
-  --jp-cell-prompt-not-active-font-color: var(--md-grey-300);
-
-  /* A custom blend of MD grey and blue 600
-   * See https://meyerweb.com/eric/tools/color-blend/#546E7A:1E88E5:5:hex */
-  --jp-cell-inprompt-font-color: #307fc1;
-  /* A custom blend of MD grey and orange 600
-   * https://meyerweb.com/eric/tools/color-blend/#546E7A:F4511E:5:hex */
-  --jp-cell-outprompt-font-color: #bf5b3d;
-
-  /* Notebook specific styles */
-
-  --jp-notebook-padding: 10px;
-  --jp-notebook-select-background: var(--jp-layout-color1);
-  --jp-notebook-multiselected-color: rgba(33, 150, 243, 0.24);
-
-  /* The scroll padding is calculated to fill enough space at the bottom of the
-  notebook to show one single-line cell (with appropriate padding) at the top
-  when the notebook is scrolled all the way to the bottom. We also subtract one
-  pixel so that no scrollbar appears if we have just one single-line cell in the
-  notebook. This padding is to enable a 'scroll past end' feature in a notebook.
-  */
-  --jp-notebook-scroll-padding: calc(
-    100% - var(--jp-code-font-size) * var(--jp-code-line-height) -
-      var(--jp-code-padding) - var(--jp-cell-padding) - 1px
-  );
-
-  /* Rendermime styles */
-
-  --jp-rendermime-error-background: rgba(244, 67, 54, 0.28);
-  --jp-rendermime-table-row-background: var(--md-grey-900);
-  --jp-rendermime-table-row-hover-background: rgba(3, 169, 244, 0.2);
-
-  /* Dialog specific styles */
-
-  --jp-dialog-background: rgba(0, 0, 0, 0.6);
-
-  /* Console specific styles */
-
-  --jp-console-padding: 10px;
-
-  /* Toolbar specific styles */
-
-  --jp-toolbar-border-color: var(--jp-border-color2);
-  --jp-toolbar-micro-height: 8px;
-  --jp-toolbar-background: var(--jp-layout-color1);
-  --jp-toolbar-box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.8);
-  --jp-toolbar-header-margin: 4px 4px 0px 4px;
-  --jp-toolbar-active-background: var(--jp-layout-color0);
-
-  /* Statusbar specific styles */
-
-  --jp-statusbar-height: 24px;
-
-  /* Input field styles */
-
-  --jp-input-box-shadow: inset 0 0 2px var(--md-blue-300);
-  --jp-input-active-background: var(--jp-layout-color0);
-  --jp-input-hover-background: var(--jp-layout-color2);
-  --jp-input-background: var(--md-grey-800);
-  --jp-input-border-color: var(--jp-inverse-border-color);
-  --jp-input-active-border-color: var(--jp-brand-color1);
-  --jp-input-active-box-shadow-color: rgba(19, 124, 189, 0.3);
-
-  /* General editor styles */
-
-  --jp-editor-selected-background: var(--jp-layout-color2);
-  --jp-editor-selected-focused-background: rgba(33, 150, 243, 0.24);
-  --jp-editor-cursor-color: var(--jp-ui-font-color0);
-
-  /* Code mirror specific styles */
-
-  --jp-mirror-editor-keyword-color: var(--md-green-500);
-  --jp-mirror-editor-atom-color: var(--md-blue-300);
-  --jp-mirror-editor-number-color: var(--md-green-400);
-  --jp-mirror-editor-def-color: var(--md-blue-600);
-  --jp-mirror-editor-variable-color: var(--md-grey-300);
-  --jp-mirror-editor-variable-2-color: var(--md-blue-400);
-  --jp-mirror-editor-variable-3-color: var(--md-green-600);
-  --jp-mirror-editor-punctuation-color: var(--md-blue-400);
-  --jp-mirror-editor-property-color: var(--md-blue-400);
-  --jp-mirror-editor-operator-color: #aa22ff;
-  --jp-mirror-editor-comment-color: #408080;
-  --jp-mirror-editor-string-color: #ff7070;
-  --jp-mirror-editor-string-2-color: var(--md-purple-300);
-  --jp-mirror-editor-meta-color: #aa22ff;
-  --jp-mirror-editor-qualifier-color: #555;
-  --jp-mirror-editor-builtin-color: var(--md-green-600);
-  --jp-mirror-editor-bracket-color: #997;
-  --jp-mirror-editor-tag-color: var(--md-green-700);
-  --jp-mirror-editor-attribute-color: var(--md-blue-700);
-  --jp-mirror-editor-header-color: var(--md-blue-500);
-  --jp-mirror-editor-quote-color: var(--md-green-300);
-  --jp-mirror-editor-link-color: var(--md-blue-700);
-  --jp-mirror-editor-error-color: #f00;
-  --jp-mirror-editor-hr-color: #999;
-
-  /* Vega extension styles */
-
-  --jp-vega-background: var(--md-grey-400);
-
-  /* Sidebar-related styles */
-
-  --jp-sidebar-min-width: 250px;
-
-  /* Search-related styles */
-
-  --jp-search-toggle-off-opacity: 0.6;
-  --jp-search-toggle-hover-opacity: 0.8;
-  --jp-search-toggle-on-opacity: 1;
-  --jp-search-selected-match-background-color: rgb(255, 225, 0);
-  --jp-search-selected-match-color: black;
-  --jp-search-unselected-match-background-color: var(
-    --jp-inverse-layout-color0
-  );
-  --jp-search-unselected-match-color: var(--jp-ui-inverse-font-color0);
-
-  /* scrollbar related styles. Supports every browser except Edge. */
-
-  /* colors based on JetBrain's Darcula theme */
-
-  --jp-scrollbar-background-color: #3f4244;
-  --jp-scrollbar-thumb-color: 88, 96, 97; /* need to specify thumb color as an RGB triplet */
-
-  --jp-scrollbar-endpad: 3px; /* the minimum gap between the thumb and the ends of a scrollbar */
-
-  /* hacks for setting the thumb shape. These do nothing in Firefox */
-
-  --jp-scrollbar-thumb-margin: 3.5px; /* the space in between the sides of the thumb and the track */
-  --jp-scrollbar-thumb-radius: 9px; /* set to a large-ish value for rounded endcaps on the thumb */
-
-  /* Icon colors that work well with light or dark backgrounds */
-  --jp-icon-contrast-color0: var(--md-purple-600);
-  --jp-icon-contrast-color1: var(--md-green-600);
-  --jp-icon-contrast-color2: var(--md-pink-600);
-  --jp-icon-contrast-color3: var(--md-blue-600);
-}

diff --git a/packages/theme-dark-extension/typedoc.json b/packages/theme-dark-extension/typedoc.json
deleted file mode 100644
index 7ddb378afe..0000000000
--- a/packages/theme-dark-extension/typedoc.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "out": "../../docs/api/theme-dark-extension",
-  "theme": "../../typedoc-theme"
-}

diff --git a/packages/toc-extension/src/index.ts b/packages/toc-extension/src/index.ts
index 334a5a2a2d..94ac4441ed 100644
--- a/packages/toc-extension/src/index.ts
+++ b/packages/toc-extension/src/index.ts
@@ -85,7 +85,7 @@ async function activateTOC(
   toc.node.setAttribute('role', 'region');
   toc.node.setAttribute('aria-label', trans.__('Table of Contents section'));

-  app.shell.add(toc, 'left', { rank: 400 });
+  // app.shell.add(toc, 'left', { rank: 400 });

   app.commands.addCommand(CommandIDs.runCells, {
     execute: args => {
  • 更改默认的 Locale 为 zh_CN
diff --git a/packages/translation-extension/schema/plugin.json b/packages/translation-extension/schema/plugin.json
index 2ef8ed8bb7..b6cf3f74e9 100644
--- a/packages/translation-extension/schema/plugin.json
+++ b/packages/translation-extension/schema/plugin.json
@@ -34,7 +34,7 @@
       "type": "string",
       "title": "Language locale",
       "description": "Set the interface display language. Examples: 'es_CO', 'fr'.",
-      "default": "en"
+      "default": "zh_CN"
     },
     "stringsPrefix": {
       "type": "string",

diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json
index beb11f76d7..78abe9a098 100644
--- a/packages/ui-components/package.json
+++ b/packages/ui-components/package.json
@@ -50,6 +50,7 @@
     "@lumino/commands": "^1.19.0",
     "@lumino/coreutils": "^1.11.0",
     "@lumino/disposable": "^1.10.0",
+    "@lumino/messaging": "^1.10.0",
     "@lumino/signaling": "^1.10.0",
     "@lumino/virtualdom": "^1.14.0",
     "@lumino/widgets": "^1.30.0",
  • 修正中间面板 Tab 的 z-index
diff --git a/packages/ui-components/src/icon/widgets/tabbarsvg.ts b/packages/ui-components/src/icon/widgets/tabbarsvg.ts
index fbcae09f57..330b751260 100644
--- a/packages/ui-components/src/icon/widgets/tabbarsvg.ts
+++ b/packages/ui-components/src/icon/widgets/tabbarsvg.ts
@@ -7,6 +7,8 @@ import { LabIconStyle } from '../../style';
 import { classes } from '../../utils';
 import { addIcon, closeIcon } from '../iconimports';
 import { ITranslator, nullTranslator } from '@jupyterlab/translation';
+import { Message } from '@lumino/messaging';
+import { ElementInlineStyle, VirtualDOM } from '@lumino/virtualdom';

 /**
  * a widget which displays titles as a single row or column of tabs.
@@ -29,6 +31,20 @@ export class TabBarSvg<T> extends TabBar<T> {
       title: trans.__('New Launcher')
     });
   }
+
+  protected onUpdateRequest(msg: Message): void {
+    let titles = this.titles;
+    let renderer = this.renderer;
+    let currentTitle = this.currentTitle;
+    let content = new Array<VirtualElement>(titles.length);
+    for (let i = 0, n = titles.length; i < n; ++i) {
+      let title = titles[i];
+      let current = title === currentTitle;
+      let zIndex = current ? 3 : 0;
+      content[i] = renderer.renderTab({ title, current, zIndex });
+    }
+    VirtualDOM.render(content, this.contentNode);
+  }
 }

 export namespace TabBarSvg {
@@ -59,6 +75,13 @@ export namespace TabBarSvg {
         closeIcon
       ) as unknown) as VirtualElement;
     }
+
+    createTabStyle(data: TabBar.IRenderData<any>): ElementInlineStyle {
+      if (data.zIndex === 0) {
+        return {};
+      }
+      return { zIndex: `${data.zIndex}` };
+    }
   }

   export const defaultRenderer = new Renderer();