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
1313import {
@@ -21,6 +21,7 @@ import { ReactWidget } from '@jupyterlab/ui-components';
2121import React from 'react' ;
2222import RunCellButton from '../../components/RunCellButton' ;
2323import { enableLineNumbersIfNeeded } from '../../utils/lineNumbers' ;
24+ import { MitoPalettes } from './palettes' ;
2425import '../../../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 */
9095const 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