icon.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import React from 'react';
  4. import { classes } from 'typestyle/lib';
  5. import { camelize } from '../utils';
  6. import { IIconStyle, iconStyle, iconStyleFlat } from '../style/icon';
  7. /**
  8. * To add an icon to the defaultIconRegistry requires two lines of code:
  9. * 1. import the icon's .svg
  10. * 2. add a relevant entry to _defaultIcons
  11. */
  12. /* tslint:disable */
  13. import jupyterFaviconSvg from '../../style/icons/jupyter-favicon.svg';
  14. // filetype icons
  15. import html5Svg from '../../style/icons/html5.svg';
  16. // status bar icons
  17. import kernelSvg from '../../style/icons/kernel.svg';
  18. import lineFormSvg from '../../style/icons/line-form.svg';
  19. import notTrustedSvg from '../../style/icons/not-trusted.svg';
  20. import statusBarSvg from '../../style/icons/status-bar.svg';
  21. import terminalSvg from '../../style/icons/terminal.svg';
  22. import trustedSvg from '../../style/icons/trusted.svg';
  23. // sidebar tab icons
  24. import buildSvg from '../../style/tabicons/build.svg'; // originally ic-build-24px.svg
  25. import extensionSvg from '../../style/tabicons/extension.svg'; // originally ic-extension-24px.svg
  26. import folderSvg from '../../style/tabicons/folder.svg'; // originally ic-folder-24px.svg
  27. import paletteSvg from '../../style/tabicons/palette.svg'; // originally ic-palette-24px.svg
  28. import runningSvg from '../../style/tabicons/running.svg'; // originally stop-circle.svg
  29. import tabSvg from '../../style/tabicons/tab.svg'; // originally ic-tab-24px.svg
  30. const _defaultIcons: ReadonlyArray<IconRegistry.IModel> = [
  31. { name: 'jupyter-favicon', svg: jupyterFaviconSvg },
  32. { name: 'html5', svg: html5Svg },
  33. { name: 'kernel', svg: kernelSvg },
  34. { name: 'line-form', svg: lineFormSvg },
  35. { name: 'not-trusted', svg: notTrustedSvg },
  36. { name: 'status-bar', svg: statusBarSvg },
  37. { name: 'terminal', svg: terminalSvg },
  38. { name: 'trusted', svg: trustedSvg },
  39. { name: 'build', svg: buildSvg },
  40. { name: 'extension', svg: extensionSvg },
  41. { name: 'folder', svg: folderSvg },
  42. { name: 'palette', svg: paletteSvg },
  43. { name: 'running', svg: runningSvg },
  44. { name: 'tab', svg: tabSvg }
  45. ];
  46. /* tslint:enable */
  47. /**
  48. * The icon registry class.
  49. */
  50. export class IconRegistry {
  51. constructor(...icons: IconRegistry.IModel[]) {
  52. if (icons.length) {
  53. this.addIcon(...icons);
  54. } else {
  55. this.addIcon(..._defaultIcons);
  56. }
  57. }
  58. addIcon(...icons: IconRegistry.IModel[]): void {
  59. icons.forEach((icon: IconRegistry.IModel) => {
  60. let className = icon.className
  61. ? icon.className
  62. : IconRegistry.iconClassName(icon.name);
  63. this._classNameToName[className] = icon.name;
  64. this._nameToClassName[icon.name] = className;
  65. this._svgs[icon.name] = icon.svg;
  66. });
  67. }
  68. get classNameToName(): { [key: string]: string } {
  69. return this._classNameToName;
  70. }
  71. get nameToClassName(): { [key: string]: string } {
  72. return this._nameToClassName;
  73. }
  74. /**
  75. * Get the icon as an HTMLElement of tag <svg><svg/>
  76. */
  77. icon(
  78. props: IconRegistry.IIconOptions & { container: HTMLElement } & IIconStyle
  79. ): HTMLElement {
  80. const { name, className, title, container, ...propsStyle } = props;
  81. let svgNode = Private.parseSvg(this.svg(name));
  82. if (title) {
  83. Private.setTitleSvg(svgNode, title);
  84. }
  85. if (container) {
  86. // clear any existing icon in container (and all other child elements)
  87. container.textContent = '';
  88. container.appendChild(svgNode);
  89. let styleClass = propsStyle ? iconStyle(propsStyle) : '';
  90. if (className) {
  91. // override the className, if explicitly passed
  92. container.className = classes(className, styleClass);
  93. } else if (!(styleClass in container.classList)) {
  94. // add icon styling class to the container's class, if not already present
  95. container.className = classes(container.className, styleClass);
  96. }
  97. } else {
  98. // add icon styling class directly to the svg node
  99. svgNode.setAttribute(
  100. 'class',
  101. classes(className, propsStyle ? iconStyleFlat(propsStyle) : '')
  102. );
  103. }
  104. return svgNode;
  105. }
  106. /**
  107. * Get the icon as a ReactElement of tag <tag><svg><svg/><tag/>
  108. * TODO: figure out how to remove the unnecessary outer <tag>
  109. */
  110. iconReact(
  111. props: IconRegistry.IIconOptions & { tag?: 'div' | 'span' } & IIconStyle
  112. ): React.ReactElement {
  113. const { name, className, title, tag, ...propsStyle } = props;
  114. const Tag = tag || 'div';
  115. return (
  116. <Tag
  117. className={classes(className, propsStyle ? iconStyle(propsStyle) : '')}
  118. dangerouslySetInnerHTML={{ __html: this.svg(name) }}
  119. />
  120. );
  121. }
  122. override(
  123. props: { className: string; name?: string; title?: string } & IIconStyle
  124. ) {
  125. const { name, className, title, ...propsStyle } = props;
  126. let usedname = name;
  127. if (!usedname) {
  128. // for now, just assume that the first className is the relevant one
  129. usedname = className.split(/\s+/)[0];
  130. // bail if no corresponding icon is found
  131. if (!(usedname in this._classNameToName)) {
  132. return;
  133. }
  134. usedname = this._classNameToName[usedname];
  135. }
  136. for (let container of document.getElementsByClassName(
  137. className
  138. ) as HTMLCollectionOf<HTMLElement>) {
  139. this.icon({
  140. name: usedname,
  141. title: title,
  142. container: container,
  143. ...propsStyle
  144. });
  145. }
  146. }
  147. svg(name: string): string {
  148. if (!(name in this._svgs)) {
  149. console.error(`Invalid icon name: ${name}`);
  150. }
  151. return this._svgs[name];
  152. }
  153. static iconClassName(name: string): string {
  154. return 'jp-' + camelize(name, true) + 'Icon';
  155. }
  156. private _classNameToName: { [key: string]: string } = Object.create(null);
  157. private _nameToClassName: { [key: string]: string } = Object.create(null);
  158. private _svgs: { [key: string]: string } = Object.create(null);
  159. }
  160. /**
  161. * The defaultIconRegistry instance.
  162. */
  163. export const defaultIconRegistry: IconRegistry = new IconRegistry();
  164. /**
  165. * Alias for defaultIconRegistry.iconReact that can be used as a React component
  166. */
  167. export const IconReact = (
  168. props: IconRegistry.IIconOptions & { tag?: 'div' | 'span' } & IIconStyle
  169. ): React.ReactElement => {
  170. return defaultIconRegistry.iconReact(props);
  171. };
  172. export namespace IconRegistry {
  173. export interface IModel {
  174. name: string;
  175. className?: string;
  176. svg: string;
  177. }
  178. export interface IIconOptions {
  179. name: string;
  180. className?: string;
  181. title?: string;
  182. }
  183. export const defaultIcons = _defaultIcons;
  184. }
  185. namespace Private {
  186. export function parseSvg(svg: string): HTMLElement {
  187. let parser = new DOMParser();
  188. return parser.parseFromString(svg, 'image/svg+xml').documentElement;
  189. }
  190. export function setTitleSvg(svgNode: HTMLElement, title: string): void {
  191. // add a title node to the top level svg node
  192. let titleNodes = svgNode.getElementsByTagName('title');
  193. if (titleNodes) {
  194. titleNodes[0].textContent = title;
  195. } else {
  196. let titleNode = document.createElement('title');
  197. titleNode.textContent = title;
  198. svgNode.appendChild(titleNode);
  199. }
  200. }
  201. }