Browse Source

unified icon handling in React and plain DOM

telamonian 6 years ago
parent
commit
58e0163e20

+ 1 - 1
packages/apputils-extension/src/index.ts

@@ -293,7 +293,7 @@ const splash: JupyterFrontEndPlugin<ISplashScreen> = {
     galaxy.id = 'galaxy';
     galaxy.id = 'galaxy';
     logo.id = 'main-logo';
     logo.id = 'main-logo';
 
 
-    defaultIconRegistry.attachIcon(logo, 'jupyter-favicon');
+    defaultIconRegistry.icon({ node: logo, name: 'jupyter-favicon' });
 
 
     galaxy.appendChild(logo);
     galaxy.appendChild(logo);
     ['1', '2', '3'].forEach(id => {
     ['1', '2', '3'].forEach(id => {

+ 10 - 10
packages/filebrowser/src/listing.ts

@@ -1760,20 +1760,20 @@ export namespace DirListing {
 
 
       if (fileType) {
       if (fileType) {
         if (fileType.iconName) {
         if (fileType.iconName) {
-          defaultIconRegistry.setIcon(
-            icon,
-            fileType.iconName,
-            fileType.iconLabel
-          );
+          defaultIconRegistry.icon({
+            name: fileType.iconName,
+            title: fileType.iconLabel,
+            parent: icon
+          });
           // filthy hack to get the tab icons
           // filthy hack to get the tab icons
           for (let iconNode of document.getElementsByClassName(
           for (let iconNode of document.getElementsByClassName(
             fileType.iconClass
             fileType.iconClass
           ) as HTMLCollectionOf<HTMLElement>) {
           ) as HTMLCollectionOf<HTMLElement>) {
-            defaultIconRegistry.setIcon(
-              iconNode,
-              fileType.iconName,
-              fileType.iconLabel
-            );
+            defaultIconRegistry.icon({
+              name: fileType.iconName,
+              title: fileType.iconLabel,
+              parent: iconNode
+            });
           }
           }
         } else {
         } else {
           icon.textContent = fileType.iconLabel || '';
           icon.textContent = fileType.iconLabel || '';

+ 3 - 3
packages/notebook/src/truststatus.tsx

@@ -6,7 +6,7 @@ import { INotebookModel, Notebook } from '.';
 
 
 import { Cell } from '@jupyterlab/cells';
 import { Cell } from '@jupyterlab/cells';
 
 
-import { IconX } from '@jupyterlab/ui-components';
+import { IconReact } from '@jupyterlab/ui-components';
 
 
 import { toArray } from '@phosphor/algorithm';
 import { toArray } from '@phosphor/algorithm';
 
 
@@ -45,9 +45,9 @@ function NotebookTrustComponent(
   props: NotebookTrustComponent.IProps
   props: NotebookTrustComponent.IProps
 ): React.ReactElement<NotebookTrustComponent.IProps> {
 ): React.ReactElement<NotebookTrustComponent.IProps> {
   if (props.allCellsTrusted) {
   if (props.allCellsTrusted) {
-    return <IconX name="trusted" top={2} />;
+    return <IconReact name="trusted" top={'2px'} />;
   } else {
   } else {
-    return <IconX name="not-trusted" top={2} />;
+    return <IconReact name="not-trusted" top={'2px'} />;
   }
   }
 }
 }
 
 

+ 2 - 2
packages/settingeditor/src/pluginlist.tsx

@@ -5,7 +5,7 @@
 
 
 import { ISettingRegistry } from '@jupyterlab/coreutils';
 import { ISettingRegistry } from '@jupyterlab/coreutils';
 
 
-import { IconX } from '@jupyterlab/ui-components';
+import { IconReact } from '@jupyterlab/ui-components';
 
 
 import { Message } from '@phosphor/messaging';
 import { Message } from '@phosphor/messaging';
 
 
@@ -274,7 +274,7 @@ namespace Private {
           title={itemTitle}
           title={itemTitle}
         >
         >
           {iconName ? (
           {iconName ? (
-            <IconX
+            <IconReact
               tag={'span'}
               tag={'span'}
               name={iconName}
               name={iconName}
               className={iconClass}
               className={iconClass}

+ 2 - 2
packages/statusbar/src/defaults/lineCol.tsx

@@ -7,7 +7,7 @@ import { VDomRenderer, VDomModel, ReactWidget } from '@jupyterlab/apputils';
 
 
 import { CodeEditor } from '@jupyterlab/codeeditor';
 import { CodeEditor } from '@jupyterlab/codeeditor';
 
 
-import { SvgX } from '@jupyterlab/ui-components';
+import { IconReact } from '@jupyterlab/ui-components';
 
 
 import { classes } from 'typestyle/lib';
 import { classes } from 'typestyle/lib';
 
 
@@ -116,7 +116,7 @@ class LineFormComponent extends React.Component<
               }}
               }}
             />
             />
             <div className={lineFormButtonDiv}>
             <div className={lineFormButtonDiv}>
-              <SvgX name="line-form" className={lineFormButtonIcon} />
+              <IconReact name="line-form" className={lineFormButtonIcon} />
               <input type="submit" className={lineFormButton} value="" />
               <input type="submit" className={lineFormButton} value="" />
             </div>
             </div>
           </div>
           </div>

+ 3 - 3
packages/statusbar/src/defaults/runningSessions.tsx

@@ -13,7 +13,7 @@ import {
   SessionManager
   SessionManager
 } from '@jupyterlab/services';
 } from '@jupyterlab/services';
 
 
-import { IconX } from '@jupyterlab/ui-components';
+import { IconReact } from '@jupyterlab/ui-components';
 
 
 import { GroupItem, interactiveItem, TextItem } from '..';
 import { GroupItem, interactiveItem, TextItem } from '..';
 
 
@@ -36,11 +36,11 @@ function RunningSessionsComponent(
     <GroupItem spacing={HALF_SPACING} onClick={props.handleClick}>
     <GroupItem spacing={HALF_SPACING} onClick={props.handleClick}>
       <GroupItem spacing={HALF_SPACING}>
       <GroupItem spacing={HALF_SPACING}>
         <TextItem source={props.terminals} />
         <TextItem source={props.terminals} />
-        <IconX name={'terminal'} left={1} top={3} />
+        <IconReact name={'terminal'} left={'1px'} top={'3px'} />
       </GroupItem>
       </GroupItem>
       <GroupItem spacing={HALF_SPACING}>
       <GroupItem spacing={HALF_SPACING}>
         <TextItem source={props.kernels} />
         <TextItem source={props.kernels} />
-        <IconX name={'kernel'} top={2} />
+        <IconReact name={'kernel'} top={'2px'} />
       </GroupItem>
       </GroupItem>
     </GroupItem>
     </GroupItem>
   );
   );

+ 0 - 38
packages/ui-components/src/icon/icon-react.tsx

@@ -1,38 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-
-import React from 'react';
-
-import { classes, style } from 'typestyle/lib';
-
-import { defaultIconRegistry } from './icon';
-import icon from '../style/icon';
-
-type SvgPropsX = { name: string; tag?: 'div' | 'span' } & React.HTMLAttributes<
-  HTMLDivElement
->;
-
-// functions that return an icon in the form of a React element
-
-export function SvgX(props: SvgPropsX): React.ReactElement {
-  const { name, tag, ...rest } = props;
-  const Tag = tag || 'div';
-  return (
-    <Tag
-      {...rest}
-      dangerouslySetInnerHTML={{ __html: defaultIconRegistry.svg(name) }}
-    />
-  );
-}
-
-export function IconX(
-  props: { left?: number; top?: number } & SvgPropsX
-): React.ReactElement {
-  const { className, left, top, ...rest } = props;
-  return (
-    <SvgX
-      className={classes(className, style(icon({ left, top })))}
-      {...rest}
-    />
-  );
-}

+ 60 - 18
packages/ui-components/src/icon/icon.ts → packages/ui-components/src/icon/icon.tsx

@@ -1,6 +1,11 @@
 // Copyright (c) Jupyter Development Team.
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 // Distributed under the terms of the Modified BSD License.
 
 
+import React from 'react';
+import { classes } from 'typestyle/lib';
+
+import { IIconStyle, iconStyle, iconNestedStyle } from '../style/icon';
+
 /**
 /**
  * To add an icon to the defaultIconRegistry requires two lines of code:
  * To add an icon to the defaultIconRegistry requires two lines of code:
  *   1. import the icon's .svg
  *   1. import the icon's .svg
@@ -47,9 +52,27 @@ export class IconRegistry {
     );
     );
   }
   }
 
 
-  icon(name: string, title?: string): HTMLElement {
+  svg(name: string): string {
+    if (!(name in this._svgs)) {
+      console.error(`Invalid icon name: ${name}`);
+    }
+
+    return this._svgs[name];
+  }
+
+  /**
+   * Get the icon as an HTMLElement of tag <svg><svg/>
+   */
+  icon(props: IconRegistry.IIcon): HTMLElement {
+    const { name, title, parent, ...propStyle } = props;
+
+    let svgNode = Private.parseSvg(this.svg(name));
+
+    // style the svg node
+    svgNode.className = iconStyle(propStyle);
+
     if (title) {
     if (title) {
-      let svgNode = Private.parseSvg(this.svg(name));
+      // add a title node to the top level svg node
       let titleNodes = svgNode.getElementsByTagName('title');
       let titleNodes = svgNode.getElementsByTagName('title');
       if (titleNodes) {
       if (titleNodes) {
         titleNodes[0].textContent = title;
         titleNodes[0].textContent = title;
@@ -58,28 +81,32 @@ export class IconRegistry {
         titleNode.textContent = title;
         titleNode.textContent = title;
         svgNode.appendChild(titleNode);
         svgNode.appendChild(titleNode);
       }
       }
-      return svgNode;
-    } else {
-      return Private.parseSvg(this.svg(name));
     }
     }
-  }
 
 
-  svg(name: string): string {
-    if (!(name in this._svgs)) {
-      console.error(`Invalid icon name: ${name}`);
+    if (parent) {
+      // clear any existing icon in parent (and all other child elements)
+      parent.textContent = '';
+      parent.appendChild(svgNode);
     }
     }
 
 
-    return this._svgs[name];
-  }
-
-  attachIcon(node: HTMLElement, name: string, title?: string): void {
-    node.appendChild(this.icon(name, title));
+    return svgNode;
   }
   }
 
 
-  setIcon(node: HTMLElement, name: string, title?: string): void {
-    // clear any existing icon (and all other child elements)
-    node.textContent = '';
-    this.attachIcon(node, name, title);
+  /**
+   * Get the icon as a ReactElement of tag <tag><svg><svg/><tag/>
+   * TODO: figure out how to remove the unnecessary outer <tag>
+   */
+  iconReact(
+    props: IconRegistry.IIcon & { tag?: 'div' | 'span'; className?: string }
+  ): React.ReactElement {
+    const { name, className, tag, ...propStyle } = props;
+    const Tag = tag || 'div';
+    return (
+      <Tag
+        className={classes(className, iconNestedStyle(propStyle))}
+        dangerouslySetInnerHTML={{ __html: this.svg(name) }}
+      />
+    );
   }
   }
 
 
   private _svgs: { [key: string]: string } = Object.create(null);
   private _svgs: { [key: string]: string } = Object.create(null);
@@ -90,12 +117,27 @@ export class IconRegistry {
  */
  */
 export const defaultIconRegistry: IconRegistry = new IconRegistry();
 export const defaultIconRegistry: IconRegistry = new IconRegistry();
 
 
+/**
+ * Alias for defaultIconRegistry.iconReact that can be used as a React component
+ */
+export const IconReact = (
+  props: IconRegistry.IIcon & { tag?: 'div' | 'span'; className?: string }
+): React.ReactElement => {
+  return defaultIconRegistry.iconReact(props);
+};
+
 export namespace IconRegistry {
 export namespace IconRegistry {
   export interface IModel {
   export interface IModel {
     name: string;
     name: string;
     svg: string;
     svg: string;
   }
   }
 
 
+  export interface IIcon extends IIconStyle {
+    name: string;
+    title?: string;
+    parent?: HTMLElement;
+  }
+
   export const defaultIcons = _defaultIcons;
   export const defaultIcons = _defaultIcons;
 }
 }
 
 

+ 0 - 1
packages/ui-components/src/icon/index.ts

@@ -2,4 +2,3 @@
 // Distributed under the terms of the Modified BSD License.
 // Distributed under the terms of the Modified BSD License.
 
 
 export * from './icon';
 export * from './icon';
-export * from './icon-react';

+ 34 - 13
packages/ui-components/src/style/icon.ts

@@ -1,23 +1,44 @@
 // Copyright (c) Jupyter Development Team.
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 // Distributed under the terms of the Modified BSD License.
 
 
-import vars from './variables';
-import { NestedCSSProperties } from 'typestyle/lib/types';
+import { style } from 'typestyle/lib';
 
 
-export default (props: {
-  left?: number;
-  top?: number;
-}): NestedCSSProperties => {
-  return {
-    minHeight: vars.iconMinHeight,
+export interface IIconStyle {
+  left?: string;
+  top?: string;
+  height?: string;
+  width?: string;
+}
+
+export const defaultIconStyle: IIconStyle = {
+  left: '0px',
+  top: '0px',
+  height: '18px',
+  width: '20px'
+};
+
+export const iconStyle = (props: IIconStyle): string => {
+  const { left, top, height, width } = { ...defaultIconStyle, ...props };
+  return style({
+    height: height,
+    width: width,
+    position: 'relative',
+    left: left,
+    top: top
+  });
+};
+
+export const iconNestedStyle = (props: IIconStyle): string => {
+  const { left, top, height, width } = { ...defaultIconStyle, ...props };
+  return style({
     $nest: {
     $nest: {
       ['svg']: {
       ['svg']: {
-        height: vars.iconHeight,
-        width: vars.iconWidth,
+        height: height,
+        width: width,
         position: 'relative',
         position: 'relative',
-        left: `${props.left ? props.left : 0}px`,
-        top: `${props.top ? props.top : 0}px`
+        left: left,
+        top: top
       }
       }
     }
     }
-  };
+  });
 };
 };

+ 0 - 8
packages/ui-components/src/style/variables.ts

@@ -1,8 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-
-export default {
-  iconMinHeight: '24px',
-  iconHeight: '18px',
-  iconWidth: '20px'
-};