CONTRIBUTING.md 6.4 KB

LabIcon design reference

Notes on design of JLIcon

Design goals for JLIcon (already implemented):

  • the one true icon
    • create a single, canonical, simple (as possible) way to set up and use icons in jlab
  • every icon is a symbol
    • each icon is defined exactly once, and is thereafter referred to via a unique symbol
    • to use an icon fooIcon outside of the file in which it is defined, the pattern will be to import fooIcon
    • this enables compile-time checking; helps to ensure that icons are specified correctly when used (as opposed to the old pattern of specifying icon via a string with name or className)
  • every icon is flexible
    • can used in any context in which icons are used in jlab
    • every icon can be passed into Lumino
    • every icon can create a DOM node
    • every icon can create a React component
  • dynamic lookup (for when absolutely needed)
    • Use dynamic lookup for the few cases in which an icon can only be specified as a string (such as in json schema files)
    • In all other cases, force (or at least strongly encourage) the pattern of import-icon
  • reusable
    • every defined icon can be used any number of times in any set of contexts
    • where an icon is defined should not matter; all icons defined in core and extensions are reusable in any other extension
  • replacable
    • all icons can be customized by replacing their svg dynamically during runtime
    • replaceability does not depend on dynamic lookup (you can assign to the .svgstr property of any LabIcon instance to trigger replacement)
    • whenever its svg is replaced, all visible copies of an icon should immediately rerender
    • implementation, which depends on the method used to render the icon:
      • LabIcon.element method
      • find all existing icon nodes by querying on data-icon-id
      • directly patch DOM
      • LabIcon.react React component
      • emit a signal whenever the icon's svg changes
      • in response, the component does a forced update
    • other possible implementations:
      • observables
      • MutationObserver

Design goals for JLIcon (partially implemented):

  • remove all other ways of creating icons (though leave an escape hatch)
    • need to deprecate, then later remove, iconClass from a number of interfaces
  • icon themes
    • sets of replacement icons should be organized as icon themes
    • not sure if icon themes should be independent of main UI theme, or just an optional part of one

Possible design patterns for JLIcon:

  1. each icon is a class. The icon is used by creating a new instance
  2. each icon is a function with methods (ie a callable instance). The icon is used by calling the appropriate method
  3. each icon is an instance of a well-defined class. The icon is used by calling the appropriate instance method

Patterns 1) and 2) were both initially investigated (see jupyterlab/jupyterlab#7299). Pattern 3) was found to be easiest to reason about, and had a desirable set of tradeoffs relating to features like dynamic lookup and replaceability (eg you can replace the svg of an icon by just setting the svgstr field of the icon instance).

How icon resolution works

In general, icons in JupyterLab should be consumed either by creating a new instance of LabIcon, or by importing an existing one from another file/package. This standard usage pattern does not work with a small set of edge cases. For these cases, it is necessary to dynamically fetch or create an icon. The process by which this occurs is referred to as icon resolution.

For example, in some cases an icon needs to be specified as a string containing the icon's name (eg in one of an extension's JSON-format .schema files). So long as the candidate icon meets a certain minimal interface:

type IResolvable = string | {name: string, svgstr: string}

it can be resolved into an actual LabIcon instance by LabIcon.resolve. The following is the intended specification for how icon resolution is intended to work:

cases when resolving icon: IResolvable alone

  • case: icon is an instance of LabIcon
    • do: nothing needs doing, just return icon
  • case: icon is an empty string
    • do: console error, return badIcon
  • case: icon is a non-empty string
    • do: assume icon is an icon name, attempt lookup in LabIcon._instances
    • case: lookup succeeds
      • do: return the found icon
    • case: lookup fails
      • do: assume that the icon is correctly specified, but has not yet actually been defined/instantiated anywhere. Create a new icon with params {name: icon, svgstr: loadingSvgstr}. Whenever the icon actually does get defined, the loading image will be dynamically replaced with the real one (implemented in LabIcon.constructor)
  • case: icon is an object
    • do: sanity check icon's params
    • case: at least one of icon.name or icon.svgstr are empty
      • do: console error, return badIcon
    • case: both icon.name and icon.svgstr are non-empty
      • do: assume that icon is a definition of a new icon, return new LabIcon(icon)

cases when resolving icon: IResolvable and iconClass: string together

  • case: iconClass is empty, icon is any
    • do: resolve as you would icon alone
  • case: iconClass is non-empty, icon is undefined
    • do return just the container with className set to iconClass
  • case: iconClass is non-empty, icon is an object
    • do: resolve as you would icon alone, but set things up so that on icon render, the className of the resulting DOM node is set to iconClass (for support of icon-as-css-background-image).

cases when resolving icon: IResolvable, iconClass: string, and fallback: LabIcon all together

  • case: iconClass is empty, icon is undefined, fallback is defined
    • do: return fallback
  • case: anything else
    • do: resolve as you would just icon and iconClass together

Icon sourcing notes

The following icons were originally taken from a set of material design icons:

  • filetype/folder.svg
    • originally ic_folder_24px.svg
  • sidebar/build.svg
    • originally ic_build_24px.svg
  • sidebar/extension.svg
    • originally ic_extension_24px.svg
  • sidebar/palette.svg
    • originally ic_palette_24px.svg
  • sidebar/tab.svg
    • originally ic_tab_24px.svg
  • statusbar/kernel.svg
    • originally ic_memory_24px.svg