Explorar el Código

Use components in extensionmanager

Grant Nestor hace 6 años
padre
commit
e99788ac7b

+ 1 - 0
packages/extensionmanager/package.json

@@ -33,6 +33,7 @@
   "dependencies": {
     "@jupyterlab/apputils": "^0.19.1",
     "@jupyterlab/services": "^3.2.1",
+    "@jupyterlab/ui-components": "^0.0.1",
     "@phosphor/messaging": "^1.2.2",
     "react": "~16.4.2",
     "react-paginate": "^5.2.3",

+ 120 - 147
packages/extensionmanager/src/widget.tsx

@@ -7,6 +7,8 @@ import { ServiceManager } from '@jupyterlab/services';
 
 import { Message } from '@phosphor/messaging';
 
+import { Button, InputGroup, Collapse } from '@jupyterlab/ui-components';
+
 import * as React from 'react';
 
 import ReactPaginate from 'react-paginate';
@@ -37,15 +39,14 @@ export class SearchBar extends React.Component<
   render(): React.ReactNode {
     return (
       <div className="jp-extensionmanager-search-bar">
-        <div className="jp-extensionmanager-search-wrapper">
-          <input
-            type="text"
-            className="jp-extensionmanager-input"
-            placeholder={this.props.placeholder}
-            onChange={this.handleChange.bind(this)}
-            value={this.state.value}
-          />
-        </div>
+        <InputGroup
+          className="jp-extensionmanager-search-wrapper"
+          type="text"
+          placeholder={this.props.placeholder}
+          onChange={this.handleChange}
+          value={this.state.value}
+          rightIcon="search"
+        />
       </div>
     );
   }
@@ -53,12 +54,12 @@ export class SearchBar extends React.Component<
   /**
    * Handler for search input changes.
    */
-  handleChange(e: KeyboardEvent) {
+  handleChange = (e: React.FormEvent<HTMLElement>) => {
     let target = e.target as HTMLInputElement;
     this.setState({
       value: target.value
     });
-  }
+  };
 }
 
 /**
@@ -97,18 +98,12 @@ function BuildPrompt(props: BuildPrompt.IProperties): React.ReactElement<any> {
       <div className="jp-extensionmanager-buildmessage">
         A build is needed to include the latest changes
       </div>
-      <button
-        className="jp-extensionmanager-rebuild"
-        onClick={props.performBuild}
-      >
+      <Button onClick={props.performBuild} minimal small>
         Rebuild
-      </button>
-      <button
-        className="jp-extensionmanager-ignorebuild"
-        onClick={props.ignoreBuild}
-      >
+      </Button>
+      <Button onClick={props.ignoreBuild} minimal small>
         Ignore
-      </button>
+      </Button>
     </div>
   );
 }
@@ -139,15 +134,6 @@ namespace BuildPrompt {
 function ListEntry(props: ListEntry.IProperties): React.ReactElement<any> {
   const { entry } = props;
   const flagClasses = [];
-  if (entry.installed) {
-    flagClasses.push('jp-extensionmanager-entry-installed');
-  }
-  if (entry.enabled) {
-    flagClasses.push('jp-extensionmanager-entry-enabled');
-  }
-  if (ListModel.entryHasUpdate(entry)) {
-    flagClasses.push('jp-extensionmanager-entry-update');
-  }
   if (entry.status && ['ok', 'warning', 'error'].indexOf(entry.status) !== -1) {
     flagClasses.push(`jp-extensionmanager-entry-${entry.status}`);
   }
@@ -174,37 +160,51 @@ function ListEntry(props: ListEntry.IProperties): React.ReactElement<any> {
           {entry.description}
         </div>
         <div className="jp-extensionmanager-entry-buttons">
-          <button
-            className="jp-extensionmanager-install"
-            onClick={() => props.performAction('install', entry)}
-          >
-            Install
-          </button>
-          <button
-            className="jp-extensionmanager-update"
-            // An install action will update the extension:
-            onClick={() => props.performAction('install', entry)}
-          >
-            Update
-          </button>
-          <button
-            className="jp-extensionmanager-uninstall"
-            onClick={() => props.performAction('uninstall', entry)}
-          >
-            Uninstall
-          </button>
-          <button
-            className="jp-extensionmanager-enable"
-            onClick={() => props.performAction('enable', entry)}
-          >
-            Enable
-          </button>
-          <button
-            className="jp-extensionmanager-disable"
-            onClick={() => props.performAction('disable', entry)}
-          >
-            Disable
-          </button>
+          {!entry.installed && (
+            <Button
+              onClick={() => props.performAction('install', entry)}
+              minimal
+              small
+            >
+              Install
+            </Button>
+          )}
+          {ListModel.entryHasUpdate(entry) && (
+            <Button
+              onClick={() => props.performAction('install', entry)}
+              minimal
+              small
+            >
+              Update
+            </Button>
+          )}
+          {entry.installed && (
+            <Button
+              onClick={() => props.performAction('uninstall', entry)}
+              minimal
+              small
+            >
+              Uninstall
+            </Button>
+          )}
+          {entry.enabled && (
+            <Button
+              onClick={() => props.performAction('enable', entry)}
+              minimal
+              small
+            >
+              Enable
+            </Button>
+          )}
+          {!entry.enabled && (
+            <Button
+              onClick={() => props.performAction('disable', entry)}
+              minimal
+              small
+            >
+              Disable
+            </Button>
+          )}
         </div>
       </div>
     </li>
@@ -307,32 +307,6 @@ export namespace ListView {
   }
 }
 
-/**
- *
- *
- * @param {RefreshButton.IProperties} props
- * @returns {React.ReactElement<any>}
- */
-function RefreshButton(
-  props: RefreshButton.IProperties
-): React.ReactElement<any> {
-  return (
-    <ToolbarButtonComponent
-      key="refreshButton"
-      className="jp-extensionmanager-refresh"
-      iconClassName="jp-RefreshIcon jp-Icon jp-Icon-16"
-      onClick={props.onClick}
-      tooltip="Refresh extension list"
-    />
-  );
-}
-
-namespace RefreshButton {
-  export interface IProperties {
-    onClick: () => void;
-  }
-}
-
 function ErrorMessage(props: ErrorMessage.IProperties) {
   return (
     <div key="error-msg" className="jp-extensionmanager-error">
@@ -357,7 +331,7 @@ export class CollapsibleSection extends React.Component<
   constructor(props: CollapsibleSection.IProperties) {
     super(props);
     this.state = {
-      collapsed: props.startCollapsed
+      isOpen: props.isOpen || true
     };
   }
 
@@ -365,49 +339,44 @@ export class CollapsibleSection extends React.Component<
    * Render the collapsible section using the virtual DOM.
    */
   render(): React.ReactNode {
-    const elements: Array<React.ReactNode> = [
-      <header key="header">
-        <ToolbarButtonComponent
-          key="collapser"
-          iconClassName={
-            'jp-Icon jp-Icon-16 ' +
-            (this.state.collapsed
-              ? 'jp-extensionmanager-collapseIcon'
-              : 'jp-extensionmanager-expandIcon')
-          }
-          className={'jp-collapser-button'}
-          onClick={() => {
-            this.onCollapse();
-          }}
-        />
-        <span className="jp-extensionmanager-headerText">
-          {this.props.header}
-        </span>
-        {this.props.headerElements}
-      </header>
-    ];
-
-    if (!this.state.collapsed) {
-      if (Array.isArray(this.props.children)) {
-        elements.push(...this.props.children);
-      } else {
-        elements.push(this.props.children);
-      }
-    }
-
-    return elements;
+    return (
+      <>
+        <header>
+          <ToolbarButtonComponent
+            iconClassName={
+              'jp-Icon jp-Icon-16 ' +
+              (this.state.isOpen
+                ? 'jp-extensionmanager-expandIcon'
+                : 'jp-extensionmanager-collapseIcon')
+            }
+            onClick={() => {
+              this.handleCollapse();
+            }}
+          />
+          <span className="jp-extensionmanager-headerText">
+            {this.props.header}
+          </span>
+          {this.props.headerElements}
+        </header>
+        <Collapse isOpen={this.state.isOpen}>{this.props.children}</Collapse>
+      </>
+    );
   }
 
   /**
    * Handler for search input changes.
    */
-  onCollapse() {
-    if (this.props.onCollapse !== undefined) {
-      this.props.onCollapse(!this.state.collapsed);
-    }
-    this.setState({
-      collapsed: !this.state.collapsed
-    });
+  handleCollapse() {
+    this.setState(
+      {
+        isOpen: !this.state.isOpen
+      },
+      () => {
+        if (this.props.onCollapse) {
+          this.props.onCollapse(this.state.isOpen);
+        }
+      }
+    );
   }
 }
 
@@ -425,14 +394,14 @@ export namespace CollapsibleSection {
     header: string;
 
     /**
-     * Whether the view will be collapsed initially or not.
+     * Whether the view will be expanded or collapsed initially, defaults to open.
      */
-    startCollapsed: boolean;
+    isOpen?: boolean;
 
     /**
-     * Callback for collapse action.
+     * Handle collapse event.
      */
-    onCollapse?: (collapsed: boolean) => void;
+    onCollapse?: (isOpen: boolean) => void;
 
     /**
      * Any additional elements to add to the header.
@@ -450,9 +419,9 @@ export namespace CollapsibleSection {
    */
   export interface IState {
     /**
-     * Whther the section is collapsed or not.
+     * Whether the section is expanded or collapsed.
      */
-    collapsed: boolean;
+    isOpen: boolean;
   }
 }
 
@@ -470,9 +439,9 @@ export class ExtensionView extends VDomRenderer<ListModel> {
    * The search input node.
    */
   get inputNode(): HTMLInputElement {
-    return this.node.getElementsByClassName(
-      'jp-extensionmanager-input'
-    )[0] as HTMLInputElement;
+    return this.node.querySelector(
+      '.jp-extensionmanager-search-wrapper input'
+    ) as HTMLInputElement;
   }
 
   /**
@@ -526,7 +495,7 @@ export class ExtensionView extends VDomRenderer<ListModel> {
       );
     } else if (model.serverRequirementsError !== null) {
       content.push(
-        <ErrorMessage key="error-msg">
+        <ErrorMessage key="server-requirements-error">
           <p>
             The server has some missing requirements for installing extensions.
           </p>
@@ -541,7 +510,7 @@ export class ExtensionView extends VDomRenderer<ListModel> {
       let installedContent = [];
       if (model.installedError !== null) {
         installedContent.push(
-          <ErrorMessage>
+          <ErrorMessage key="install-error">
             {`Error querying installed extensions${
               model.installedError ? `: ${model.installedError}` : '.'
             }`}
@@ -550,7 +519,7 @@ export class ExtensionView extends VDomRenderer<ListModel> {
       } else {
         installedContent.push(
           <ListView
-            key="list-view"
+            key="installed-items"
             entries={model.installed}
             numPages={1}
             onPage={value => {
@@ -563,14 +532,18 @@ export class ExtensionView extends VDomRenderer<ListModel> {
 
       content.push(
         <CollapsibleSection
-          header="Installed"
           key="installed-section"
-          startCollapsed={false}
+          isOpen={true}
+          header="Installed"
           headerElements={
-            <RefreshButton
+            <ToolbarButtonComponent
+              key="refresh-button"
+              className="jp-extensionmanager-refresh"
+              iconClassName="jp-RefreshIcon jp-Icon jp-Icon-16"
               onClick={() => {
                 model.refreshInstalled();
               }}
+              tooltip="Refresh extension list"
             />
           }
         >
@@ -581,7 +554,7 @@ export class ExtensionView extends VDomRenderer<ListModel> {
       let searchContent = [];
       if (model.searchError !== null) {
         searchContent.push(
-          <ErrorMessage>
+          <ErrorMessage key="search-error">
             {`Error searching for extensions${
               model.searchError ? `: ${model.searchError}` : '.'
             }`}
@@ -590,7 +563,7 @@ export class ExtensionView extends VDomRenderer<ListModel> {
       } else {
         searchContent.push(
           <ListView
-            key="list-view"
+            key="search-items"
             // Filter out installed extensions:
             entries={model.searchResult.filter(
               entry => model.installed.indexOf(entry) === -1
@@ -606,11 +579,11 @@ export class ExtensionView extends VDomRenderer<ListModel> {
 
       content.push(
         <CollapsibleSection
-          header={model.query ? 'Search Results' : 'Discover'}
           key="search-section"
-          startCollapsed={true}
-          onCollapse={(collapsed: boolean) => {
-            if (!collapsed && model.query === null) {
+          isOpen={false}
+          header={model.query ? 'Search Results' : 'Discover'}
+          onCollapse={(isOpen: boolean) => {
+            if (isOpen && model.query === null) {
               model.query = '';
             }
           }}

+ 19 - 110
packages/extensionmanager/style/index.css

@@ -13,6 +13,7 @@
 
 .jp-extensionmanager-content {
   overflow: auto;
+  overflow-x: hidden;
 }
 
 /*
@@ -22,7 +23,7 @@
 .jp-extensionmanager-listview-wrapper {
   margin: 0;
   padding: 0;
-  padding-bottom: 5px;
+  padding-bottom: 8px;
   display: flex;
   flex-direction: column;
   flex: 0 0 auto;
@@ -44,7 +45,7 @@
   justify-content: space-between;
   flex: 0 0 auto;
   margin: 0px;
-  padding: 8px 12px;
+  padding-bottom: 8px;
   font-weight: 600;
   text-transform: uppercase;
   border-bottom: var(--jp-border-width) solid var(--jp-border-color2);
@@ -52,6 +53,7 @@
   font-size: var(--jp-ui-font-size0);
   line-height: 24px;
   height: 24px;
+  padding: 4px;
 }
 
 .jp-extensionmanager-view header > .jp-ToolbarButtonComponent {
@@ -60,7 +62,7 @@
 
 .jp-extensionmanager-view header .jp-extensionmanager-headerText {
   flex: 1 0 auto;
-  margin: 0px 2px;
+  margin: 0px 4px;
 }
 
 .jp-extensionmanager-view header > .jp-ToolbarButtonComponent {
@@ -70,7 +72,7 @@
 }
 
 .jp-extensionmanager-view header > .jp-ToolbarButtonComponent:hover {
-  border: 1px solid var(--jp-brand-color1);
+  /* border: 1px solid var(--jp-brand-color1); */
 }
 
 /*
@@ -85,50 +87,21 @@
 */
 
 .jp-extensionmanager-search-bar {
-  padding: 8px 8px 0px 8px;
-  background-color: var(--jp-layout-color1);
-  z-index: 2;
-  flex: 0 0 auto;
+  padding: 4px;
 }
 
-.jp-extensionmanager-input {
+.jp-extensionmanager-search-wrapper input {
   background: transparent;
-  width: calc(100% - 18px);
-  float: left;
-  border: none;
-  outline: none;
   font-size: var(--jp-ui-font-size1);
   color: var(--jp-ui-font-color0);
   line-height: var(--jp-private-commandpalette-search-height);
+  box-sizing: border-box;
+  border-radius: 0;
 }
 
-.jp-extensionmanager-search-wrapper {
-  overflow: overlay;
-  padding: 0px 8px;
-  border: 1px solid var(--jp-border-color0);
-  background-color: var(--jp-input-active-background);
-  height: 30px;
-}
-
-.jp-extensionmanager-search-wrapper::after {
-  content: ' ';
-  color: var(--jp-ui-inverse-font-color1);
-  background-color: var(--jp-brand-color1);
-  position: absolute;
-  top: 8px;
-  right: 8px;
-  height: 32px;
-  width: 12px;
-  padding: 0px 12px;
-  background-image: var(--jp-icon-search-white);
-  background-size: 20px;
-  background-repeat: no-repeat;
-  background-position: center;
-}
-
-.jp-extensionmanager-view.p-mod-focused .jp-extensionmanager-search-wrapper {
-  border: var(--jp-border-width) solid var(--jp-brand-color1);
-  box-shadow: inset 0 0 4px var(--jp-brand-color2);
+.jp-extensionmanager-search-wrapper input:focus {
+  box-shadow: inset 0 0 0 1px rgba(19, 124, 189, 0.3),
+    inset 0 0 0 3px rgba(19, 124, 189, 0.3);
 }
 
 /*
@@ -153,46 +126,12 @@
   display: flex;
   justify-content: center;
 }
-
-/*
-  Entry buttons visibility
-*/
-
-.jp-extensionmanager-entry .jp-extensionmanager-install,
-.jp-extensionmanager-entry.jp-extensionmanager-entry-update
-  .jp-extensionmanager-update,
-.jp-extensionmanager-entry.jp-extensionmanager-entry-installed
-  .jp-extensionmanager-uninstall {
-  display: inline-block;
-}
-
-.jp-extensionmanager-entry .jp-extensionmanager-update,
-.jp-extensionmanager-entry .jp-extensionmanager-uninstall,
-.jp-extensionmanager-entry.jp-extensionmanager-entry-installed
-  .jp-extensionmanager-install {
-  display: none;
-}
-
-.jp-extensionmanager-entry .jp-extensionmanager-enable,
-.jp-extensionmanager-entry .jp-extensionmanager-disable,
-.jp-extensionmanager-entry.jp-extensionmanager-entry-installed.jp-extensionmanager-entry-enabled
-  .jp-extensionmanager-enable {
-  display: none;
-}
-
-.jp-extensionmanager-entry.jp-extensionmanager-entry-installed
-  .jp-extensionmanager-enable,
-.jp-extensionmanager-entry.jp-extensionmanager-entry-installed.jp-extensionmanager-entry-enabled
-  .jp-extensionmanager-disable {
-  display: inline-block;
-}
-
 /*
   Entry layout and styling
 */
 
 .jp-extensionmanager-entry {
-  padding: 4px 12px;
+  padding: 8px;
   border-bottom: solid var(--jp-border-width) var(--jp-border-color2);
 }
 
@@ -270,7 +209,7 @@
 
 .jp-extensionmanager-entry-content {
   display: flex;
-  flex-direction: row;
+  flex-direction: column;
   justify-content: space-between;
 }
 
@@ -294,7 +233,7 @@
   left: -200px;
   width: 200px;
   height: 4px;
-  border-radius: 5px;
+  border-radius: 4px;
   background-color: var(--jp-brand-color1);
   animation: loading 2s linear infinite;
 }
@@ -327,38 +266,8 @@
 
 .jp-extensionmanager-entry-buttons {
   display: flex;
-  flex-direction: column;
-}
-
-.jp-extensionmanager-entry button {
-  border: solid var(--jp-border-width) var(--jp-border-color2);
-  font-size: var(--jp-ui-font-size0);
-  margin-top: 1px;
-  margin-bottom: 1px;
-  margin-right: 0px;
-  margin-left: 3px;
-  color: var(--jp-ui-font-color1);
-  background-color: var(--jp-layout-color2);
-}
-
-.jp-extensionmanager-entry button.jp-extensionmanager-uninstall:hover {
-  background-color: var(--jp-error-color2);
-}
-
-.jp-extensionmanager-entry button.jp-extensionmanager-disable:hover {
-  background-color: var(--jp-error-color3);
-}
-
-.jp-extensionmanager-entry button.jp-extensionmanager-install:hover {
-  background-color: var(--jp-success-color2);
-}
-
-.jp-extensionmanager-entry button.jp-extensionmanager-enable:hover {
-  background-color: var(--jp-success-color3);
-}
-
-.jp-extensionmanager-entry button.jp-extensionmanager-update:hover {
-  background-color: var(--jp-brand-color3);
+  flex-direction: row;
+  padding-top: 4px;
 }
 
 /*
@@ -380,7 +289,7 @@
   font-size: var(--jp-ui-font-size1);
   font-weight: 600;
   float: right;
-  margin: 3px;
+  margin: 4px;
 }
 
 /*

+ 3 - 0
packages/extensionmanager/tsconfig.json

@@ -11,6 +11,9 @@
     },
     {
       "path": "../services"
+    },
+    {
+      "path": "../ui-components"
     }
   ]
 }