TypeScript interfaces for implementing MIME renderer extensions in JupyterLab
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Interfaces for defining and rendering icons within the JupyterLab interface, supporting both SVG-based and custom renderers.
The LabIcon namespace contains interfaces for icon definition and rendering within JupyterLab.
namespace LabIcon {
/**
* The simplest possible interface for defining a generic icon
*/
interface IIcon {
/**
* The name of the icon. By convention, the icon name will be namespaced
* as so: "pkg-name:icon-name"
*/
readonly name: string;
/** A string containing the raw contents of an svg file */
svgstr: string;
}
/**
* Interface for generic renderer
*/
interface IRenderer {
readonly render: (container: HTMLElement, options?: any) => void;
readonly unrender?: (container: HTMLElement, options?: any) => void;
}
/**
* A type that can be resolved to a LabIcon instance
*/
type IResolvable = string | (IIcon & Partial<IRenderer>);
}Defines a basic icon with a name and SVG content.
Usage Example:
import { IRenderMime } from "@jupyterlab/rendermime-interfaces";
// Define custom icons for file types
const dataVisualizationIcon: IRenderMime.LabIcon.IIcon = {
name: 'my-extension:data-viz',
svgstr: `
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="12" width="2" height="2" fill="#4CAF50"/>
<rect x="6" y="8" width="2" height="6" fill="#2196F3"/>
<rect x="10" y="4" width="2" height="10" fill="#FF9800"/>
<rect x="14" y="6" width="2" height="8" fill="#9C27B0"/>
</svg>
`
};
const documentIcon: IRenderMime.LabIcon.IIcon = {
name: 'my-extension:document',
svgstr: `
<svg width="16" height="16" viewBox="0 0 16 16">
<path d="M3 2v12h10V5l-3-3H3z" fill="#E3F2FD" stroke="#1976D2"/>
<path d="M10 2v3h3" fill="none" stroke="#1976D2"/>
<line x1="5" y1="7" x2="11" y2="7" stroke="#1976D2"/>
<line x1="5" y1="9" x2="11" y2="9" stroke="#1976D2"/>
<line x1="5" y1="11" x2="9" y2="11" stroke="#1976D2"/>
</svg>
`
};
// Use in file type definitions
const fileTypeWithIcon: IRenderMime.IFileType = {
name: 'data-visualization',
mimeTypes: ['application/data-viz'],
extensions: ['.dviz'],
displayName: 'Data Visualization',
icon: dataVisualizationIcon
};Defines custom rendering behavior for icons.
Usage Example:
import { IRenderMime } from "@jupyterlab/rendermime-interfaces";
// Custom icon renderer with animation
const animatedIconRenderer: IRenderMime.LabIcon.IRenderer = {
render: (container: HTMLElement, options?: any) => {
// Create SVG element
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '16');
svg.setAttribute('height', '16');
svg.setAttribute('viewBox', '0 0 16 16');
// Add spinning animation
svg.innerHTML = `
<circle cx="8" cy="8" r="6" fill="none" stroke="#2196F3" stroke-width="2" opacity="0.3"/>
<path d="M14 8c0-3.3-2.7-6-6-6" stroke="#2196F3" stroke-width="2" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" values="0 8 8;360 8 8"
dur="1s" repeatCount="indefinite"/>
</path>
`;
container.appendChild(svg);
// Apply options if provided
if (options?.size) {
svg.setAttribute('width', options.size);
svg.setAttribute('height', options.size);
}
if (options?.color) {
svg.style.color = options.color;
}
},
unrender: (container: HTMLElement) => {
// Clean up rendered content
const svg = container.querySelector('svg');
if (svg) {
container.removeChild(svg);
}
}
};
// Icon with custom renderer
const animatedIcon: IRenderMime.LabIcon.IIcon & Partial<IRenderMime.LabIcon.IRenderer> = {
name: 'my-extension:loading',
svgstr: '', // Not used when custom renderer is provided
render: animatedIconRenderer.render,
unrender: animatedIconRenderer.unrender
};Flexible type that can be either a string reference or a full icon definition.
Usage Examples:
import { IRenderMime } from "@jupyterlab/rendermime-interfaces";
// Using string reference to existing icon
const stringIconFileType: IRenderMime.IFileType = {
name: 'text-file',
mimeTypes: ['text/plain'],
extensions: ['.txt'],
icon: 'document' // String reference
};
// Using full icon object
const customIconFileType: IRenderMime.IFileType = {
name: 'special-format',
mimeTypes: ['application/special'],
extensions: ['.spec'],
icon: {
name: 'my-extension:special',
svgstr: '<svg>...</svg>'
}
};
// Using icon with custom renderer
const animatedIconFileType: IRenderMime.IFileType = {
name: 'dynamic-content',
mimeTypes: ['application/dynamic'],
extensions: ['.dyn'],
icon: {
name: 'my-extension:dynamic',
svgstr: '<svg>...</svg>',
render: (container, options) => {
// Custom rendering logic
}
}
};
// Utility function to handle IResolvable
function resolveIcon(icon: IRenderMime.LabIcon.IResolvable): string {
if (typeof icon === 'string') {
return icon; // Return icon name for lookup
} else {
return icon.name; // Return the name from the icon object
}
}
// Function to render an icon
function renderIcon(
icon: IRenderMime.LabIcon.IResolvable,
container: HTMLElement,
options?: any
): void {
if (typeof icon === 'string') {
// Handle string reference - would typically lookup in icon registry
console.log(`Rendering icon by name: ${icon}`);
} else {
// Handle icon object
if (icon.render) {
// Use custom renderer
icon.render(container, options);
} else {
// Use default SVG rendering
container.innerHTML = icon.svgstr;
}
}
}// Different ways to specify icons for file types
const fileTypes: IRenderMime.IFileType[] = [
{
name: 'python-file',
mimeTypes: ['text/x-python'],
extensions: ['.py'],
icon: 'python' // Reference to built-in icon
},
{
name: 'config-file',
mimeTypes: ['application/json'],
extensions: ['.config.json'],
iconClass: 'jp-MaterialIcon jp-SettingsIcon' // CSS class approach
},
{
name: 'custom-format',
mimeTypes: ['application/custom'],
extensions: ['.cust'],
icon: {
name: 'my-package:custom-format',
svgstr: '<svg width="16" height="16">...</svg>'
}
}
];// Icon that changes based on file state
const stateAwareIcon: IRenderMime.LabIcon.IIcon & Partial<IRenderMime.LabIcon.IRenderer> = {
name: 'my-extension:state-aware',
svgstr: '', // Not used
render: (container: HTMLElement, options?: { state?: string }) => {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '16');
svg.setAttribute('height', '16');
// Change appearance based on state
switch (options?.state) {
case 'processing':
svg.innerHTML = '<circle cx="8" cy="8" r="4" fill="#FF9800"/>';
break;
case 'complete':
svg.innerHTML = '<circle cx="8" cy="8" r="4" fill="#4CAF50"/>';
break;
case 'error':
svg.innerHTML = '<circle cx="8" cy="8" r="4" fill="#F44336"/>';
break;
default:
svg.innerHTML = '<circle cx="8" cy="8" r="4" fill="#9E9E9E"/>';
}
container.appendChild(svg);
}
};