Skip to content

Commit 2126b3c

Browse files
authored
Merge pull request #96 from gjmooney/add_status_widget
Add status widget for loading indicator
2 parents a495f6c + 8598c00 commit 2126b3c

File tree

3 files changed

+141
-3
lines changed

3 files changed

+141
-3
lines changed

src/plugins/driveBrowserPlugin.ts

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
JupyterFrontEnd,
66
JupyterFrontEndPlugin
77
} from '@jupyterlab/application';
8+
import { IDocumentWidgetOpener } from '@jupyterlab/docmanager';
9+
import { IStatusBar } from '@jupyterlab/statusbar';
810
import {
911
IFileBrowserFactory,
1012
FileBrowser,
@@ -38,6 +40,67 @@ import { Drive } from '../contents';
3840
import { setListingLimit } from '../requests';
3941
import { CommandIDs } from '../token';
4042

43+
/**
44+
* Status bar widget for displaying drive information
45+
*/
46+
class DriveStatusWidget extends Widget {
47+
constructor() {
48+
super();
49+
this.node.classList.add(
50+
'drive-status-widget',
51+
'drive-status-loading',
52+
'lm-mod-hidden'
53+
);
54+
55+
this._textSpan = document.createElement('span');
56+
this._textSpan.textContent = '';
57+
this.node.appendChild(this._textSpan);
58+
59+
this._isLoading = false;
60+
}
61+
62+
updateStatus(text: string) {
63+
this._textSpan.textContent = text;
64+
}
65+
66+
/**
67+
* Update status when loading a directory or file
68+
*/
69+
setLoading(path: string, type: string) {
70+
this._isLoading = true;
71+
72+
if (type === 'directory') {
73+
const displayPath =
74+
path === '' ? 'Root' : path.split('/').pop() || 'Directory';
75+
this.updateStatus(`Opening: ${displayPath}`);
76+
} else {
77+
const fileName = path.split('/').pop() || 'File';
78+
this.updateStatus(`Opening: ${fileName}`);
79+
}
80+
this.removeClass('lm-mod-hidden');
81+
}
82+
83+
/**
84+
* Clear loading state and show current status
85+
*/
86+
setLoaded(path?: string) {
87+
this._isLoading = false;
88+
this.addClass('lm-mod-hidden');
89+
90+
this.updateStatus('');
91+
}
92+
93+
/**
94+
* Check if currently loading
95+
*/
96+
get isLoading(): boolean {
97+
return this._isLoading;
98+
}
99+
100+
private _isLoading: boolean;
101+
private _textSpan: HTMLSpanElement;
102+
}
103+
41104
/**
42105
* The file browser factory ID.
43106
*/
@@ -69,24 +132,28 @@ export const driveFileBrowser: JupyterFrontEndPlugin<void> = {
69132
IFileBrowserFactory,
70133
IToolbarWidgetRegistry,
71134
ISettingRegistry,
72-
ITranslator
135+
ITranslator,
136+
IDocumentWidgetOpener
73137
],
74138
optional: [
75139
IRouter,
76140
JupyterFrontEnd.ITreeResolver,
77141
ILabShell,
78-
ILayoutRestorer
142+
ILayoutRestorer,
143+
IStatusBar
79144
],
80145
activate: async (
81146
app: JupyterFrontEnd,
82147
fileBrowserFactory: IFileBrowserFactory,
83148
toolbarRegistry: IToolbarWidgetRegistry,
84149
settingsRegistry: ISettingRegistry,
85150
translator: ITranslator,
151+
docWidgetOpener: IDocumentWidgetOpener,
86152
router: IRouter | null,
87153
tree: JupyterFrontEnd.ITreeResolver | null,
88154
labShell: ILabShell | null,
89-
restorer: ILayoutRestorer | null
155+
restorer: ILayoutRestorer | null,
156+
statusBar: IStatusBar | null
90157
): Promise<void> => {
91158
console.log(
92159
'JupyterLab extension jupyter-drives:drives-file-browser is activated!'
@@ -125,6 +192,34 @@ export const driveFileBrowser: JupyterFrontEndPlugin<void> = {
125192
restorer.add(driveBrowser, 'drive-file-browser');
126193
}
127194

195+
// Register status bar widget
196+
if (statusBar) {
197+
const driveStatusWidget = new DriveStatusWidget();
198+
199+
statusBar.registerStatusItem('driveBrowserStatus', {
200+
item: driveStatusWidget,
201+
align: 'right',
202+
rank: 500
203+
});
204+
205+
// Item/dir being opened
206+
//@ts-expect-error listing is protected
207+
driveBrowser.listing.onItemOpened.connect((_, args) => {
208+
const { path, type } = args;
209+
driveStatusWidget.setLoading(path, type);
210+
});
211+
212+
const doneLoading = () => {
213+
driveStatusWidget.setLoaded();
214+
};
215+
216+
// Item done opening
217+
docWidgetOpener.opened.connect(doneLoading);
218+
219+
// Directory done opening
220+
driveBrowser.model.pathChanged.connect(doneLoading);
221+
}
222+
128223
const uploader = new Uploader({ model: driveBrowser.model, translator });
129224
toolbarRegistry.addFactory(FILE_BROWSER_FACTORY, 'uploader', () => {
130225
return uploader;

src/requests.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export async function getContents(
102102
'drives/' + driveName + '/' + options.path,
103103
'GET'
104104
);
105+
105106
// checking if we are dealing with a directory or a file
106107
const isDir: boolean = response.data.length !== undefined;
107108

style/base.css

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,45 @@ li {
162162
color: var(--md-red-600);
163163
font-size: 0.7rem;
164164
}
165+
166+
/* Status bar widget styling */
167+
.drive-status-widget {
168+
font-size: 12px;
169+
font-weight: 500;
170+
color: var(--jp-ui-font-color1);
171+
padding: 2px 8px;
172+
border-radius: 3px;
173+
background-color: var(--jp-layout-color2);
174+
border: 1px solid var(--jp-border-color1);
175+
transition: all 0.2s ease;
176+
}
177+
178+
.drive-status-widget:hover {
179+
background-color: var(--jp-layout-color3);
180+
}
181+
182+
.drive-status-error {
183+
color: var(--jp-error-color1);
184+
background-color: var(--jp-error-color0);
185+
border-color: var(--jp-error-color1);
186+
}
187+
188+
.drive-status-loading {
189+
color: var(--jp-ui-font-color1);
190+
background-color: var(--jp-layout-color3);
191+
animation: pulse 1.5s ease-in-out infinite;
192+
}
193+
194+
@keyframes pulse {
195+
0% {
196+
opacity: 1;
197+
}
198+
199+
50% {
200+
opacity: 0.7;
201+
}
202+
203+
100% {
204+
opacity: 1;
205+
}
206+
}

0 commit comments

Comments
 (0)