Skip to content

Commit 90a4b24

Browse files
authored
Merge pull request #2160 from mito-ds/mito-dark
Mito dark
2 parents ec5f761 + 9380aec commit 90a4b24

File tree

11 files changed

+479
-140
lines changed

11 files changed

+479
-140
lines changed

mito-ai/mito_ai/_version.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# Copyright (c) Saga Inc.
2-
# Distributed under the terms of the GNU Affero General Public License v3.0 License.
3-
41
# This file is auto-generated by Hatchling. As such, do not:
52
# - modify
63
# - track in version control e.g. be sure to add to .gitignore

mito-ai/src/Extensions/MitoThemeLight/index.ts renamed to mito-ai/src/Extensions/MitoThemes/index.ts

Lines changed: 132 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// Distributed under the terms of the Modified BSD License.
88
/**
99
* @packageDocumentation
10-
* @module mito-theme-extension
10+
* @module mito-themes-extension
1111
*/
1212

1313
import {
@@ -21,6 +21,7 @@ import { ReactWidget } from '@jupyterlab/ui-components';
2121
import React from 'react';
2222
import RunCellButton from '../../components/RunCellButton';
2323
import { enableLineNumbersIfNeeded } from '../../utils/lineNumbers';
24+
import { MitoPalettes } from './palettes';
2425
import '../../../style/RunCellButton.css';
2526

2627
/**
@@ -66,6 +67,9 @@ function setupCellNumbering(notebookPanel: NotebookPanel): (() => void) | null {
6667
});
6768
observer.observe(notebook.node, { childList: true, subtree: true });
6869

70+
// Initial update
71+
updateAllCellNumbers(notebookPanel);
72+
6973
// Return cleanup function
7074
return () => {
7175
notebook.model?.cells.changed.disconnect(handleCellsChanged);
@@ -82,14 +86,15 @@ function setupCellNumbering(notebookPanel: NotebookPanel): (() => void) | null {
8286
}
8387

8488
/**
85-
* A plugin for the Mito Light Theme.
89+
* A plugin for the Mito Themes (Light and Dark).
8690
*
87-
* The Run Cell Button, cell numbering, and hidden default toolbar buttons only apply
88-
* when the Mito Light theme is active.
91+
* Registers both Mito Light and Mito Dark themes.
92+
* The Run Cell Button, cell numbering, and hidden default toolbar buttons apply
93+
* when either Mito theme is active.
8994
*/
9095
const plugin: JupyterFrontEndPlugin<void> = {
91-
id: 'mito_ai:theme',
92-
description: 'Adds the Mito Light theme.',
96+
id: 'mito_ai:themes',
97+
description: 'Adds the Mito Light and Dark themes.',
9398
requires: [IThemeManager, ITranslator, INotebookTracker],
9499
activate: (
95100
app: JupyterFrontEnd,
@@ -98,10 +103,14 @@ const plugin: JupyterFrontEndPlugin<void> = {
98103
notebookTracker: INotebookTracker
99104
) => {
100105
const trans = translator.load('jupyterlab');
106+
107+
// CSS path - single CSS file for both themes (uses CSS variables set by palettes)
101108
const style = 'mito_ai/index.css';
109+
const palettes = new MitoPalettes();
102110

103-
// Store connection for cleanup
104-
let widgetAddedConnection: ((sender: INotebookTracker, widget: NotebookPanel) => void) | null = null;
111+
// Store connections for cleanup
112+
let lightWidgetAddedConnection: ((sender: INotebookTracker, widget: NotebookPanel) => void) | null = null;
113+
let darkWidgetAddedConnection: ((sender: INotebookTracker, widget: NotebookPanel) => void) | null = null;
105114

106115
// Store cell numbering cleanup functions for each notebook
107116
const cellNumberingCleanups = new Map<NotebookPanel, () => void>();
@@ -172,56 +181,68 @@ const plugin: JupyterFrontEndPlugin<void> = {
172181
}
173182
};
174183

175-
// Add buttons and cell numbering to all notebooks
176-
const addButtonsToAllNotebooks = (): void => {
184+
// Add cell numbering to a notebook panel (guard against duplicates)
185+
const addCellNumbering = (notebookPanel: NotebookPanel): void => {
186+
if (cellNumberingCleanups.has(notebookPanel)) {
187+
return;
188+
}
189+
const cleanup = setupCellNumbering(notebookPanel);
190+
if (cleanup) {
191+
cellNumberingCleanups.set(notebookPanel, cleanup);
192+
// Also cleanup when notebook is disposed
193+
notebookPanel.disposed.connect(() => {
194+
cellNumberingCleanups.delete(notebookPanel);
195+
});
196+
}
197+
};
198+
199+
// Add buttons and cell numbering to all notebooks (for a specific theme)
200+
const addButtonsToAllNotebooks = (themeName: string): void => {
177201
notebookTracker.forEach(widget => {
178202
addRunCellButton(widget);
179203
// Enable line numbers if needed
180204
void enableLineNumbersIfNeeded(app, widget);
181205
// Setup cell numbering
182-
const cleanup = setupCellNumbering(widget);
183-
if (cleanup) {
184-
cellNumberingCleanups.set(widget, cleanup);
185-
// Also cleanup when notebook is disposed
186-
widget.disposed.connect(() => {
187-
cellNumberingCleanups.delete(widget);
188-
});
189-
}
206+
addCellNumbering(widget);
190207
});
191208

192209
// Connect to new notebooks
193-
widgetAddedConnection = (sender: INotebookTracker, widget: NotebookPanel): void => {
210+
const widgetAddedHandler = (sender: INotebookTracker, widget: NotebookPanel): void => {
194211
setTimeout(() => {
195212
// Check if widget is still valid before proceeding
196213
if (widget.isDisposed) {
197214
return;
198215
}
199-
// Only add if Mito Light theme is still active
200-
if (manager.theme === 'Mito Light') {
216+
// Only add if the specified theme is still active
217+
if (manager.theme === themeName) {
201218
addRunCellButton(widget);
202219
// Enable line numbers if needed
203220
void enableLineNumbersIfNeeded(app, widget);
204221
// Setup cell numbering
205-
const cleanup = setupCellNumbering(widget);
206-
if (cleanup) {
207-
cellNumberingCleanups.set(widget, cleanup);
208-
// Also cleanup when notebook is disposed
209-
widget.disposed.connect(() => {
210-
cellNumberingCleanups.delete(widget);
211-
});
212-
}
222+
addCellNumbering(widget);
213223
}
214224
}, 100);
215225
};
216-
notebookTracker.widgetAdded.connect(widgetAddedConnection);
226+
227+
if (themeName === 'Mito Light') {
228+
lightWidgetAddedConnection = widgetAddedHandler;
229+
notebookTracker.widgetAdded.connect(lightWidgetAddedConnection);
230+
} else if (themeName === 'Mito Dark') {
231+
darkWidgetAddedConnection = widgetAddedHandler;
232+
notebookTracker.widgetAdded.connect(darkWidgetAddedConnection);
233+
}
217234
};
218235

219236
// Remove buttons and cell numbering from all notebooks
220237
const removeButtonsFromAllNotebooks = (): void => {
221238
// Disconnect from new notebooks
222-
if (widgetAddedConnection) {
223-
notebookTracker.widgetAdded.disconnect(widgetAddedConnection);
224-
widgetAddedConnection = null;
239+
if (lightWidgetAddedConnection) {
240+
notebookTracker.widgetAdded.disconnect(lightWidgetAddedConnection);
241+
lightWidgetAddedConnection = null;
242+
}
243+
if (darkWidgetAddedConnection) {
244+
notebookTracker.widgetAdded.disconnect(darkWidgetAddedConnection);
245+
darkWidgetAddedConnection = null;
225246
}
226247

227248
// Remove from all existing notebooks
@@ -234,28 +255,99 @@ const plugin: JupyterFrontEndPlugin<void> = {
234255
cellNumberingCleanups.clear();
235256
};
236257

258+
// Register Mito Light theme
237259
manager.register({
238260
name: 'Mito Light',
239261
displayName: trans.__('Mito Light'),
240262
isLight: true,
241263
themeScrollbars: false,
242264
load: async () => {
243-
// Load theme CSS (hides default buttons)
265+
// Set CSS variables for light theme before loading CSS
266+
palettes.setColorsLight();
267+
// Load theme CSS (hides default buttons, applies light theme variables)
244268
await manager.loadCSS(style);
245269
// Add Run Cell buttons to all notebooks and enable line numbers
246-
addButtonsToAllNotebooks();
270+
addButtonsToAllNotebooks('Mito Light');
247271
},
248272
unload: async () => {
249273
// Remove Run Cell buttons from all notebooks
250274
removeButtonsFromAllNotebooks();
251275
}
252276
});
253277

254-
// Set Mito Light as default theme if user hasn't explicitly chosen a different theme
255-
// This runs after registration so the theme is available
256-
if (manager.theme === 'JupyterLab Light' || !manager.theme) {
257-
void manager.setTheme('Mito Light');
258-
}
278+
// Register Mito Dark theme
279+
manager.register({
280+
name: 'Mito Dark',
281+
displayName: trans.__('Mito Dark'),
282+
isLight: false,
283+
themeScrollbars: false,
284+
load: async () => {
285+
// Set CSS variables for dark theme before loading CSS
286+
palettes.setColorsDark();
287+
// Load theme CSS (hides default buttons, applies dark theme variables)
288+
await manager.loadCSS(style);
289+
// Add Run Cell buttons to all notebooks and enable line numbers
290+
addButtonsToAllNotebooks('Mito Dark');
291+
},
292+
unload: async () => {
293+
// Remove Run Cell buttons from all notebooks
294+
removeButtonsFromAllNotebooks();
295+
}
296+
});
297+
298+
// Flag to prevent infinite recursion when we convert themes
299+
let isConvertingTheme = false;
300+
301+
// Helper function to convert non-Mito themes to corresponding Mito theme
302+
const convertToMitoTheme = (themeName: string | null): void => {
303+
// Prevent infinite recursion - if we're already converting, don't do it again
304+
if (isConvertingTheme) {
305+
return;
306+
}
307+
308+
if (!themeName) {
309+
// No theme set, default to Mito Light
310+
isConvertingTheme = true;
311+
void manager.setTheme('Mito Light').finally(() => {
312+
isConvertingTheme = false;
313+
});
314+
return;
315+
}
316+
317+
const isMitoTheme = themeName === 'Mito Light' || themeName === 'Mito Dark';
318+
if (isMitoTheme) {
319+
// Already a Mito theme, don't change - this ensures user's Mito theme preference is preserved
320+
return;
321+
}
322+
323+
// Convert non-Mito themes to corresponding Mito theme
324+
isConvertingTheme = true;
325+
if (themeName === 'JupyterLab Dark' || themeName.includes('Dark')) {
326+
void manager.setTheme('Mito Dark').finally(() => {
327+
isConvertingTheme = false;
328+
});
329+
} else {
330+
// Default to light theme for any other non-Mito theme (including JupyterLab Light)
331+
void manager.setTheme('Mito Light').finally(() => {
332+
isConvertingTheme = false;
333+
});
334+
}
335+
};
336+
337+
// Wait for app restoration to complete before checking/setting theme
338+
// This ensures saved theme preferences are loaded first
339+
void app.restored.then(() => {
340+
// Convert theme on initial load if needed
341+
convertToMitoTheme(manager.theme);
342+
});
343+
344+
// Listen for theme changes and automatically convert to Mito theme
345+
// This ensures that when users switch themes, they get converted to Mito themes
346+
// and the preference is saved properly
347+
manager.themeChanged.connect(() => {
348+
// Use manager.theme to get the current theme after the change
349+
convertToMitoTheme(manager.theme);
350+
});
259351
},
260352
autoStart: true
261353
};

0 commit comments

Comments
 (0)