or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-framework.mdindex.mdlayout-restoration.mdmime-rendering.mdservice-tokens.mdshell-management.mdstatus-management.mdurl-routing.mdutility-functions.md
tile.json

mime-rendering.mddocs/

0

# MIME Rendering

1

2

Plugin creation utilities for MIME type rendering extensions, enabling support for custom content types and document formats.

3

4

## Capabilities

5

6

### IMimeDocumentTracker Interface

7

8

Widget tracker interface for managing MIME document widgets across the application.

9

10

```typescript { .api }

11

/**

12

* MIME document widget tracker interface extending standard widget tracker

13

*/

14

interface IMimeDocumentTracker extends IWidgetTracker<MimeDocument> {

15

// Inherits all IWidgetTracker methods:

16

// - currentWidget: MimeDocument | null

17

// - widgetAdded: ISignal<this, MimeDocument>

18

// - widgetUpdated: ISignal<this, MimeDocument>

19

// - activeWidget: MimeDocument | null

20

// - size: number

21

// - filter(fn: (widget: MimeDocument) => boolean): MimeDocument[]

22

// - find(fn: (widget: MimeDocument) => boolean): MimeDocument | undefined

23

// - forEach(fn: (widget: MimeDocument) => void): void

24

// - has(widget: MimeDocument): boolean

25

// - inject(widget: MimeDocument): void

26

}

27

28

/**

29

* Service token for MIME document tracker

30

*/

31

const IMimeDocumentTracker: Token<IMimeDocumentTracker>;

32

```

33

34

**Usage Examples:**

35

36

```typescript

37

import { IMimeDocumentTracker } from "@jupyterlab/application";

38

import { JupyterFrontEndPlugin } from "@jupyterlab/application";

39

40

const mimeTrackerPlugin: JupyterFrontEndPlugin<void> = {

41

id: 'mime-tracker-example',

42

autoStart: true,

43

requires: [IMimeDocumentTracker],

44

activate: (app, tracker: IMimeDocumentTracker) => {

45

// Listen for new MIME documents

46

tracker.widgetAdded.connect((sender, widget) => {

47

console.log('New MIME document:', widget.context.path);

48

console.log('MIME type:', widget.content.mimeType);

49

});

50

51

// Access current MIME document

52

if (tracker.currentWidget) {

53

const current = tracker.currentWidget;

54

console.log('Current MIME document path:', current.context.path);

55

console.log('Current MIME type:', current.content.mimeType);

56

}

57

58

// Find specific MIME documents

59

const htmlDocs = tracker.filter(widget =>

60

widget.content.mimeType === 'text/html'

61

);

62

console.log(`Found ${htmlDocs.length} HTML documents`);

63

64

// Find first markdown document

65

const markdownDoc = tracker.find(widget =>

66

widget.content.mimeType === 'text/markdown'

67

);

68

if (markdownDoc) {

69

console.log('Found markdown document:', markdownDoc.title.label);

70

}

71

}

72

};

73

```

74

75

### createRendermimePlugins Function

76

77

Creates multiple plugins for rendering MIME type extensions in the JupyterLab environment.

78

79

```typescript { .api }

80

/**

81

* Creates plugins for multiple rendermime extensions

82

* @param extensions - Array of rendermime extension modules

83

* @returns Array of JupyterFrontEnd plugins

84

*/

85

function createRendermimePlugins(

86

extensions: IRenderMime.IExtensionModule[]

87

): JupyterFrontEndPlugin<void | IMimeDocumentTracker, any, any>[];

88

```

89

90

**Usage Examples:**

91

92

```typescript

93

import { createRendermimePlugins } from "@jupyterlab/application";

94

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

95

96

// Define custom MIME extensions

97

const customExtensions: IRenderMime.IExtensionModule[] = [

98

{

99

// CSV renderer extension

100

id: 'csv-renderer',

101

rendererFactory: {

102

safe: true,

103

mimeTypes: ['text/csv'],

104

createRenderer: (options) => new CSVRenderer(options)

105

},

106

dataType: 'string',

107

fileTypes: [{

108

name: 'csv',

109

extensions: ['.csv'],

110

mimeTypes: ['text/csv'],

111

icon: 'ui-components:csv'

112

}],

113

documentWidgetFactoryOptions: {

114

name: 'CSV Viewer',

115

primaryFileType: 'csv',

116

fileTypes: ['csv']

117

}

118

},

119

{

120

// JSON renderer extension

121

id: 'json-renderer',

122

rendererFactory: {

123

safe: true,

124

mimeTypes: ['application/json'],

125

createRenderer: (options) => new JSONRenderer(options)

126

},

127

dataType: 'string',

128

fileTypes: [{

129

name: 'json',

130

extensions: ['.json'],

131

mimeTypes: ['application/json'],

132

icon: 'ui-components:json'

133

}],

134

documentWidgetFactoryOptions: {

135

name: 'JSON Viewer',

136

primaryFileType: 'json',

137

fileTypes: ['json']

138

}

139

}

140

];

141

142

// Create plugins for all extensions

143

const mimePlugins = createRendermimePlugins(customExtensions);

144

145

// Register plugins with application

146

mimePlugins.forEach(plugin => {

147

app.registerPlugin(plugin);

148

});

149

150

// Example custom renderer implementation

151

class CSVRenderer implements IRenderMime.IRenderer {

152

constructor(options: IRenderMime.IRendererOptions) {

153

this.mimeType = 'text/csv';

154

}

155

156

readonly mimeType: string;

157

158

render(model: IRenderMime.IMimeModel): Promise<void> {

159

const data = model.data[this.mimeType] as string;

160

const element = document.createElement('div');

161

162

// Simple CSV to table conversion

163

const rows = data.split('\n').map(row => row.split(','));

164

const table = document.createElement('table');

165

166

rows.forEach((row, index) => {

167

const tr = document.createElement('tr');

168

row.forEach(cell => {

169

const td = document.createElement(index === 0 ? 'th' : 'td');

170

td.textContent = cell.trim();

171

tr.appendChild(td);

172

});

173

table.appendChild(tr);

174

});

175

176

element.appendChild(table);

177

return Promise.resolve();

178

}

179

}

180

```

181

182

### createRendermimePlugin Function

183

184

Creates a single plugin for a specific MIME type renderer.

185

186

```typescript { .api }

187

/**

188

* Creates a plugin for a single rendermime extension

189

* @param tracker - Widget tracker for MIME documents

190

* @param item - Single rendermime extension

191

* @returns JupyterFrontEnd plugin

192

*/

193

function createRendermimePlugin(

194

tracker: WidgetTracker<MimeDocument>,

195

item: IRenderMime.IExtension

196

): JupyterFrontEndPlugin<void>;

197

```

198

199

**Usage Examples:**

200

201

```typescript

202

import { createRendermimePlugin } from "@jupyterlab/application";

203

import { WidgetTracker } from "@jupyterlab/apputils";

204

import { MimeDocument } from "@jupyterlab/docregistry";

205

206

// Create a widget tracker for custom MIME documents

207

const customMimeTracker = new WidgetTracker<MimeDocument>({

208

namespace: 'custom-mime-documents'

209

});

210

211

// Define a single MIME extension

212

const xmlExtension: IRenderMime.IExtension = {

213

id: 'xml-renderer',

214

rendererFactory: {

215

safe: true,

216

mimeTypes: ['application/xml', 'text/xml'],

217

createRenderer: (options) => new XMLRenderer(options)

218

},

219

dataType: 'string',

220

fileTypes: [{

221

name: 'xml',

222

extensions: ['.xml', '.xsd', '.xsl'],

223

mimeTypes: ['application/xml', 'text/xml'],

224

icon: 'ui-components:xml'

225

}],

226

documentWidgetFactoryOptions: {

227

name: 'XML Viewer',

228

primaryFileType: 'xml',

229

fileTypes: ['xml'],

230

defaultFor: ['xml']

231

}

232

};

233

234

// Create plugin for the extension

235

const xmlPlugin = createRendermimePlugin(customMimeTracker, xmlExtension);

236

237

// Register with application

238

app.registerPlugin(xmlPlugin);

239

240

// Example XML renderer

241

class XMLRenderer implements IRenderMime.IRenderer {

242

constructor(options: IRenderMime.IRendererOptions) {

243

this.mimeType = 'application/xml';

244

}

245

246

readonly mimeType: string;

247

248

async render(model: IRenderMime.IMimeModel): Promise<void> {

249

const data = model.data[this.mimeType] as string;

250

const element = document.createElement('div');

251

element.className = 'xml-viewer';

252

253

try {

254

// Parse and format XML

255

const parser = new DOMParser();

256

const xmlDoc = parser.parseFromString(data, 'application/xml');

257

258

if (xmlDoc.documentElement.nodeName === 'parsererror') {

259

throw new Error('Invalid XML');

260

}

261

262

// Create formatted display

263

element.innerHTML = this.formatXML(data);

264

} catch (error) {

265

element.textContent = `Error parsing XML: ${error.message}`;

266

element.className += ' error';

267

}

268

269

return Promise.resolve();

270

}

271

272

private formatXML(xml: string): string {

273

// Simple XML formatting implementation

274

const formatted = xml

275

.replace(/></g, '>\n<')

276

.replace(/^\s*\n/gm, '');

277

278

return `<pre><code class="xml">${this.escapeHtml(formatted)}</code></pre>`;

279

}

280

281

private escapeHtml(text: string): string {

282

return text

283

.replace(/&/g, '&amp;')

284

.replace(/</g, '&lt;')

285

.replace(/>/g, '&gt;')

286

.replace(/"/g, '&quot;')

287

.replace(/'/g, '&#39;');

288

}

289

}

290

```

291

292

### MIME Extension Configuration

293

294

Detailed configuration options for creating comprehensive MIME type support.

295

296

```typescript { .api }

297

/**

298

* Complete MIME extension configuration interface

299

*/

300

interface IRenderMime.IExtensionModule {

301

/** Unique identifier for the extension */

302

id: string;

303

304

/** Factory for creating renderers */

305

rendererFactory: IRenderMime.IRendererFactory;

306

307

/** Data type the renderer expects ('string' | 'json') */

308

dataType?: 'string' | 'json';

309

310

/** File types associated with this MIME type */

311

fileTypes?: DocumentRegistry.IFileType[];

312

313

/** Options for document widget factory */

314

documentWidgetFactoryOptions?: DocumentRegistry.IWidgetFactoryOptions;

315

}

316

317

/**

318

* Renderer factory interface

319

*/

320

interface IRenderMime.IRendererFactory {

321

/** Whether the renderer is safe (doesn't execute code) */

322

safe: boolean;

323

324

/** MIME types this renderer handles */

325

mimeTypes: string[];

326

327

/** Function to create renderer instances */

328

createRenderer: (options: IRenderMime.IRendererOptions) => IRenderMime.IRenderer;

329

330

/** Default render timeout in milliseconds */

331

defaultRank?: number;

332

333

/** Whether renderer can render untrusted content */

334

trusted?: boolean;

335

}

336

337

/**

338

* File type configuration for MIME extensions

339

*/

340

interface DocumentRegistry.IFileType {

341

/** Internal name for the file type */

342

name: string;

343

344

/** Display name for the file type */

345

displayName?: string;

346

347

/** File extensions (with dots) */

348

extensions: string[];

349

350

/** MIME types for this file type */

351

mimeTypes: string[];

352

353

/** Icon identifier */

354

icon?: string;

355

356

/** Icon class name */

357

iconClass?: string;

358

359

/** Icon label */

360

iconLabel?: string;

361

362

/** Content type category */

363

contentType?: 'file' | 'directory' | 'notebook';

364

365

/** File format ('text' | 'base64' | 'json') */

366

fileFormat?: 'text' | 'base64' | 'json';

367

}

368

```

369

370

### Advanced MIME Rendering Example

371

372

Complete example showing how to create a sophisticated MIME renderer with multiple features.

373

374

```typescript { .api }

375

// Advanced MIME rendering example

376

interface AdvancedMimeRenderer {

377

/** Support for multiple MIME variants */

378

supportedMimeTypes: string[];

379

380

/** Configurable rendering options */

381

renderingOptions: RenderingOptions;

382

383

/** Error handling and fallbacks */

384

errorHandling: ErrorHandlingOptions;

385

386

/** Performance optimization */

387

optimizations: PerformanceOptions;

388

}

389

390

interface RenderingOptions {

391

theme?: 'light' | 'dark' | 'auto';

392

maxSize?: number;

393

enableSyntaxHighlighting?: boolean;

394

enableLineNumbers?: boolean;

395

}

396

397

interface ErrorHandlingOptions {

398

showErrorDetails?: boolean;

399

fallbackRenderer?: string;

400

retryAttempts?: number;

401

}

402

403

interface PerformanceOptions {

404

lazyLoading?: boolean;

405

virtualScrolling?: boolean;

406

renderTimeout?: number;

407

}

408

```

409

410

**Complete Advanced Example:**

411

412

```typescript

413

import {

414

createRendermimePlugin,

415

IMimeDocumentTracker

416

} from "@jupyterlab/application";

417

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

418

import { WidgetTracker } from "@jupyterlab/apputils";

419

import { MimeDocument } from "@jupyterlab/docregistry";

420

421

// Advanced GeoJSON renderer with mapping capabilities

422

class GeoJSONRenderer implements IRenderMime.IRenderer {

423

private options: RenderingOptions;

424

425

constructor(rendererOptions: IRenderMime.IRendererOptions) {

426

this.mimeType = 'application/geo+json';

427

this.options = {

428

theme: 'auto',

429

maxSize: 10 * 1024 * 1024, // 10MB

430

enableSyntaxHighlighting: true,

431

enableLineNumbers: false

432

};

433

}

434

435

readonly mimeType = 'application/geo+json';

436

437

async render(model: IRenderMime.IMimeModel): Promise<void> {

438

const data = model.data[this.mimeType] as any;

439

const element = document.createElement('div');

440

element.className = 'geojson-viewer';

441

442

try {

443

// Check size limits

444

const dataSize = JSON.stringify(data).length;

445

if (dataSize > this.options.maxSize!) {

446

throw new Error(`Data too large: ${dataSize} bytes`);

447

}

448

449

// Create map container

450

const mapContainer = document.createElement('div');

451

mapContainer.className = 'geojson-map';

452

mapContainer.style.height = '400px';

453

mapContainer.style.width = '100%';

454

455

// Create data inspector

456

const inspector = document.createElement('div');

457

inspector.className = 'geojson-inspector';

458

459

// Add map (simplified - would use real mapping library)

460

await this.renderMap(mapContainer, data);

461

462

// Add data inspector

463

this.renderInspector(inspector, data);

464

465

// Create tabs for map vs data view

466

const tabs = this.createTabs([

467

{ label: 'Map View', content: mapContainer },

468

{ label: 'Data View', content: inspector }

469

]);

470

471

element.appendChild(tabs);

472

473

} catch (error) {

474

this.renderError(element, error as Error);

475

}

476

477

return Promise.resolve();

478

}

479

480

private async renderMap(container: HTMLElement, data: any): Promise<void> {

481

// Simplified map rendering - in real implementation would use

482

// libraries like Leaflet, Mapbox, or OpenLayers

483

container.innerHTML = `

484

<div class="mock-map">

485

<p>GeoJSON Map View</p>

486

<p>Features: ${data.features?.length || 0}</p>

487

<p>Type: ${data.type}</p>

488

</div>

489

`;

490

}

491

492

private renderInspector(container: HTMLElement, data: any): void {

493

const formatted = JSON.stringify(data, null, 2);

494

const pre = document.createElement('pre');

495

const code = document.createElement('code');

496

code.className = 'language-json';

497

code.textContent = formatted;

498

pre.appendChild(code);

499

container.appendChild(pre);

500

501

// Add syntax highlighting if enabled

502

if (this.options.enableSyntaxHighlighting) {

503

this.applySyntaxHighlighting(code);

504

}

505

}

506

507

private createTabs(tabs: { label: string; content: HTMLElement }[]): HTMLElement {

508

const container = document.createElement('div');

509

container.className = 'tab-container';

510

511

const tabHeaders = document.createElement('div');

512

tabHeaders.className = 'tab-headers';

513

514

const tabContents = document.createElement('div');

515

tabContents.className = 'tab-contents';

516

517

tabs.forEach((tab, index) => {

518

// Create header

519

const header = document.createElement('button');

520

header.textContent = tab.label;

521

header.className = 'tab-header';

522

if (index === 0) header.classList.add('active');

523

524

// Create content wrapper

525

const content = document.createElement('div');

526

content.className = 'tab-content';

527

content.style.display = index === 0 ? 'block' : 'none';

528

content.appendChild(tab.content);

529

530

// Add click handler

531

header.addEventListener('click', () => {

532

// Update headers

533

tabHeaders.querySelectorAll('.tab-header').forEach(h =>

534

h.classList.remove('active')

535

);

536

header.classList.add('active');

537

538

// Update contents

539

tabContents.querySelectorAll('.tab-content').forEach(c =>

540

(c as HTMLElement).style.display = 'none'

541

);

542

content.style.display = 'block';

543

});

544

545

tabHeaders.appendChild(header);

546

tabContents.appendChild(content);

547

});

548

549

container.appendChild(tabHeaders);

550

container.appendChild(tabContents);

551

return container;

552

}

553

554

private applySyntaxHighlighting(element: HTMLElement): void {

555

// Simplified syntax highlighting - would use library like Prism.js

556

element.innerHTML = element.innerHTML

557

.replace(/"([^"]+)":/g, '<span class="key">"$1":</span>')

558

.replace(/: "([^"]+)"/g, ': <span class="string">"$1"</span>')

559

.replace(/: (\d+)/g, ': <span class="number">$1</span>');

560

}

561

562

private renderError(container: HTMLElement, error: Error): void {

563

container.innerHTML = `

564

<div class="error">

565

<h3>Error rendering GeoJSON</h3>

566

<p>${error.message}</p>

567

<details>

568

<summary>Error Details</summary>

569

<pre>${error.stack}</pre>

570

</details>

571

</div>

572

`;

573

}

574

}

575

576

// Create comprehensive GeoJSON extension

577

const geoJSONExtension: IRenderMime.IExtensionModule = {

578

id: 'geojson-renderer',

579

rendererFactory: {

580

safe: true,

581

mimeTypes: ['application/geo+json'],

582

createRenderer: (options) => new GeoJSONRenderer(options),

583

defaultRank: 0,

584

trusted: true

585

},

586

dataType: 'json',

587

fileTypes: [{

588

name: 'geojson',

589

displayName: 'GeoJSON',

590

extensions: ['.geojson', '.json'],

591

mimeTypes: ['application/geo+json'],

592

icon: 'ui-components:json',

593

contentType: 'file',

594

fileFormat: 'text'

595

}],

596

documentWidgetFactoryOptions: {

597

name: 'GeoJSON Viewer',

598

primaryFileType: 'geojson',

599

fileTypes: ['geojson'],

600

defaultFor: ['geojson']

601

}

602

};

603

604

// Create plugin

605

const tracker = new WidgetTracker<MimeDocument>({

606

namespace: 'geojson-documents'

607

});

608

609

const geoJSONPlugin = createRendermimePlugin(tracker, geoJSONExtension);

610

611

// Register with application

612

app.registerPlugin(geoJSONPlugin);

613

```

614

615

## Best Practices

616

617

### Performance Considerations

618

619

```typescript

620

// Performance optimization patterns

621

class PerformantMimeRenderer implements IRenderMime.IRenderer {

622

private renderCache = new Map<string, HTMLElement>();

623

624

readonly mimeType = 'custom/large-data';

625

626

async render(model: IRenderMime.IMimeModel): Promise<void> {

627

const data = model.data[this.mimeType];

628

const cacheKey = this.getCacheKey(data);

629

630

// Check cache first

631

if (this.renderCache.has(cacheKey)) {

632

const cached = this.renderCache.get(cacheKey)!;

633

return Promise.resolve();

634

}

635

636

// Lazy loading for large datasets

637

if (this.isLargeDataset(data)) {

638

return this.renderLazy(data, cacheKey);

639

}

640

641

// Regular rendering for small datasets

642

return this.renderImmediate(data, cacheKey);

643

}

644

645

private isLargeDataset(data: any): boolean {

646

return JSON.stringify(data).length > 100 * 1024; // 100KB

647

}

648

649

private async renderLazy(data: any, cacheKey: string): Promise<void> {

650

// Implement virtual scrolling or pagination

651

// Load data in chunks

652

}

653

654

private async renderImmediate(data: any, cacheKey: string): Promise<void> {

655

// Render all data immediately

656

}

657

658

private getCacheKey(data: any): string {

659

// Create cache key from data hash

660

return btoa(JSON.stringify(data)).substring(0, 32);

661

}

662

}

663

```