From e94b26010c5ad4c88243d2188c6ab0b9862ee80f Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Mon, 21 Jul 2025 11:35:42 +0200 Subject: [PATCH 01/32] add backend functionality to exclude drive from browser --- jupyter_drives/handlers.py | 5 ++++- jupyter_drives/manager.py | 36 +++++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/jupyter_drives/handlers.py b/jupyter_drives/handlers.py index db69167..1d42a42 100644 --- a/jupyter_drives/handlers.py +++ b/jupyter_drives/handlers.py @@ -52,7 +52,10 @@ def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager): @tornado.web.authenticated async def post(self): body = self.get_json_body() - result = self._manager.set_listing_limit(**body) + if 'new_limit' in body: + result = self._manager.set_listing_limit(**body) + if 'exclude_drive_name' in body: + result = self._manager.exclude_drive(**body) self.finish(result) class ListJupyterDrivesHandler(JupyterDrivesAPIHandler): diff --git a/jupyter_drives/manager.py b/jupyter_drives/manager.py index c2d81a2..80df7cd 100644 --- a/jupyter_drives/manager.py +++ b/jupyter_drives/manager.py @@ -53,6 +53,7 @@ def __init__(self, config: traitlets.config.Config) -> None: self._max_files_listed = 1025 self._drives = None self._external_drives = {} + self._excluded_drives = set() # instate fsspec file system self._file_system = fsspec.filesystem(self._config.provider, asynchronous=True) @@ -171,6 +172,22 @@ def set_listing_limit(self, new_limit): return + def exclude_drive(self, exclude_drive_name): + """Exclude drive from listing. + + Args: + exclude_bucket_name: drive to exclude + """ + try: + self._excluded_drives.add(exclude_drive_name); + except Exception as e: + raise tornado.web.HTTPError( + status_code= httpx.codes.BAD_REQUEST, + reason= f"The following error occured when excluding the drive: {e}" + ) + + return + async def list_drives(self): """Get list of available drives. @@ -196,15 +213,16 @@ async def list_drives(self): ) for result in results: - data.append( - { - "name": result.name, - "region": self._config.region_name, - "creationDate": result.extra["creation_date"], - "mounted": False if result.name not in self._content_managers else True, - "provider": self._config.provider - } - ) + if result.name not in self._excluded_drives: + data.append( + { + "name": result.name, + "region": self._config.region_name, + "creationDate": result.extra["creation_date"], + "mounted": False if result.name not in self._content_managers else True, + "provider": self._config.provider + } + ) if len(self._external_drives) != 0: for drive in self._external_drives.values(): From 5257f5dba33a36b8c59295c53d42f44700b26730 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Mon, 21 Jul 2025 11:40:43 +0200 Subject: [PATCH 02/32] add frontend function for excluding drive --- src/contents.ts | 23 ++++++++++++++++++++++- src/requests.ts | 25 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/contents.ts b/src/contents.ts index 35593aa..aac5f84 100644 --- a/src/contents.ts +++ b/src/contents.ts @@ -22,7 +22,8 @@ import { copyObjects, presignedLink, createDrive, - getDrivesList + getDrivesList, + excludeDrive } from './requests'; let data: Contents.IModel = { @@ -683,6 +684,26 @@ export class Drive implements Contents.IDrive { return data; } + /** + * Exclude drive from browser. + * + * @param driveName: The name of drive to exclude. + * + * @returns A promise which resolves with the contents model. + */ + async excludeDrive(driveName: string): Promise { + data = await excludeDrive(driveName); + + Contents.validateContentsModel(data); + this._fileChanged.emit({ + type: 'new', + oldValue: null, + newValue: data + }); + + return data; + } + /** * Create a checkpoint for a file. * diff --git a/src/requests.ts b/src/requests.ts index 872618e..8bddb82 100644 --- a/src/requests.ts +++ b/src/requests.ts @@ -37,6 +37,31 @@ export async function setListingLimit(newLimit: number) { }); } +/** + * Exclude drive from being listed inside the DriveBrowser. + * + * @returns + */ +export async function excludeDrive(driveName: string) { + await requestAPI('drives/config', 'POST', { + exclude_drive_name: driveName + }); + + data = { + name: driveName, + path: driveName, + last_modified: '', + created: '', + content: [], + format: 'json', + mimetype: '', + size: 0, + writable: true, + type: 'directory' + }; + return data; +} + /** * Fetch the list of available drives. * @returns The list of available drives. From c6b4ac16bed92767c0faeea4280f82a4c8e78dc1 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Mon, 21 Jul 2025 12:55:09 +0200 Subject: [PATCH 03/32] add backend functionality for including drive in listing --- jupyter_drives/handlers.py | 2 ++ jupyter_drives/manager.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/jupyter_drives/handlers.py b/jupyter_drives/handlers.py index 1d42a42..47da150 100644 --- a/jupyter_drives/handlers.py +++ b/jupyter_drives/handlers.py @@ -56,6 +56,8 @@ async def post(self): result = self._manager.set_listing_limit(**body) if 'exclude_drive_name' in body: result = self._manager.exclude_drive(**body) + if 'include_drive_name' in body: + result = self._manager.include_drive(**body) self.finish(result) class ListJupyterDrivesHandler(JupyterDrivesAPIHandler): diff --git a/jupyter_drives/manager.py b/jupyter_drives/manager.py index 80df7cd..b5020df 100644 --- a/jupyter_drives/manager.py +++ b/jupyter_drives/manager.py @@ -188,6 +188,22 @@ def exclude_drive(self, exclude_drive_name): return + def include_drive(self, include_drive_name): + """Include drive in listing. + + Args: + include_bucket_name: drive to include in listing + """ + try: + self._excluded_drives.remove(include_drive_name); + except Exception as e: + raise tornado.web.HTTPError( + status_code= httpx.codes.BAD_REQUEST, + reason= f"The following error occured when excluding the drive: {e}" + ) + + return + async def list_drives(self): """Get list of available drives. From 565f46a4a7c9ff0d309f9ed3660927ee22715d2a Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Mon, 21 Jul 2025 12:55:35 +0200 Subject: [PATCH 04/32] add frontend functionality to include drive in listing --- src/contents.ts | 22 +++++++++++++++++++++- src/requests.ts | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/contents.ts b/src/contents.ts index aac5f84..ae86c10 100644 --- a/src/contents.ts +++ b/src/contents.ts @@ -688,7 +688,7 @@ export class Drive implements Contents.IDrive { * Exclude drive from browser. * * @param driveName: The name of drive to exclude. - * + * * @returns A promise which resolves with the contents model. */ async excludeDrive(driveName: string): Promise { @@ -704,6 +704,26 @@ export class Drive implements Contents.IDrive { return data; } + /** + * Include drive in browser listing. + * + * @param driveName: The name of drive to include. + * + * @returns A promise which resolves with the contents model. + */ + async includeDrive(driveName: string): Promise { + data = await this.includeDrive(driveName); + + Contents.validateContentsModel(data); + this._fileChanged.emit({ + type: 'new', + oldValue: null, + newValue: data + }); + + return data; + } + /** * Create a checkpoint for a file. * diff --git a/src/requests.ts b/src/requests.ts index 8bddb82..9104780 100644 --- a/src/requests.ts +++ b/src/requests.ts @@ -62,6 +62,31 @@ export async function excludeDrive(driveName: string) { return data; } +/** + * Include drive in DriveBrowser listing. + * + * @returns + */ +export async function includeDrive(driveName: string) { + await requestAPI('drives/config', 'POST', { + include_drive_name: driveName + }); + + data = { + name: driveName, + path: driveName, + last_modified: '', + created: '', + content: [], + format: 'json', + mimetype: '', + size: 0, + writable: true, + type: 'directory' + }; + return data; +} + /** * Fetch the list of available drives. * @returns The list of available drives. From b8a9955abc0999502ec08ba30e3ed370e9d3e0d5 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Mon, 21 Jul 2025 13:41:17 +0200 Subject: [PATCH 05/32] iterate on include drive functionality --- jupyter_drives/manager.py | 2 +- src/contents.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/jupyter_drives/manager.py b/jupyter_drives/manager.py index b5020df..ebc856d 100644 --- a/jupyter_drives/manager.py +++ b/jupyter_drives/manager.py @@ -199,7 +199,7 @@ def include_drive(self, include_drive_name): except Exception as e: raise tornado.web.HTTPError( status_code= httpx.codes.BAD_REQUEST, - reason= f"The following error occured when excluding the drive: {e}" + reason= f"The following error occured when including the drive: {e}" ) return diff --git a/src/contents.ts b/src/contents.ts index ae86c10..b98fdef 100644 --- a/src/contents.ts +++ b/src/contents.ts @@ -23,7 +23,8 @@ import { presignedLink, createDrive, getDrivesList, - excludeDrive + excludeDrive, + includeDrive } from './requests'; let data: Contents.IModel = { @@ -712,7 +713,7 @@ export class Drive implements Contents.IDrive { * @returns A promise which resolves with the contents model. */ async includeDrive(driveName: string): Promise { - data = await this.includeDrive(driveName); + data = await includeDrive(driveName); Contents.validateContentsModel(data); this._fileChanged.emit({ From d2f2f9a75517015db72ae7a7f5b36dc58c6bde8a Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Mon, 21 Jul 2025 13:41:58 +0200 Subject: [PATCH 06/32] add commands for new funcitonalities --- src/plugins/driveBrowserPlugin.ts | 60 +++++++++++++++++++++++++++++++ src/token.ts | 2 ++ 2 files changed, 62 insertions(+) diff --git a/src/plugins/driveBrowserPlugin.ts b/src/plugins/driveBrowserPlugin.ts index 9b54d5c..0e251c4 100644 --- a/src/plugins/driveBrowserPlugin.ts +++ b/src/plugins/driveBrowserPlugin.ts @@ -542,5 +542,65 @@ namespace Private { icon: fileIcon.bindprops({ stylesheet: 'menuItem' }), label: 'Copy Path' }); + + app.commands.addCommand(CommandIDs.excludeDrive, { + isEnabled: () => { + return browser.model.path === 's3:'; + }, + execute: () => { + const widget = tracker.currentWidget; + if (!widget) { + return; + } + const item = widget.selectedItems().next(); + if (item.done) { + return; + } + + const driveName: string = item.value.name; + drive.excludeDrive(driveName); + }, + label: 'Exclude Drive', + icon: driveBrowserIcon.bindprops({ stylesheet: 'menuItem' }) + }); + + app.contextMenu.addItem({ + command: CommandIDs.excludeDrive, + selector: + '#drive-file-browser.jp-SidePanel .jp-DirListing-content .jp-DirListing-item[data-isdir]', + rank: 110 + }); + + app.commands.addCommand(CommandIDs.includeDrive, { + isEnabled: () => { + return browser.model.path === 's3:'; + }, + execute: async () => { + return showDialog({ + title: 'Include Drive', + body: new Private.AddPublicDriveHandler(drive.name), + focusNodeSelector: 'input', + buttons: [ + Dialog.cancelButton(), + Dialog.okButton({ + label: 'Add', + ariaLabel: 'Include Drive' + }) + ] + }).then(result => { + if (result.value) { + drive.includeDrive(result.value); + } + }); + }, + label: 'Include Drive', + icon: driveBrowserIcon.bindprops({ stylesheet: 'menuItem' }) + }); + + app.contextMenu.addItem({ + command: CommandIDs.includeDrive, + selector: '#drive-file-browser.jp-SidePanel .jp-DirListing-content', + rank: 110 + }); } } diff --git a/src/token.ts b/src/token.ts index 979143a..cbd38f2 100644 --- a/src/token.ts +++ b/src/token.ts @@ -16,6 +16,8 @@ export namespace CommandIDs { export const createNewNotebook = 'drives:create-new-notebook'; export const rename = 'drives:rename'; export const copyPath = 'drives:copy-path'; + export const excludeDrive = 'drives:exclude-drive'; + export const includeDrive = 'drives:include-drive'; } /** From dd328ac9792705ff55b17671a24fd4164009547e Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Tue, 22 Jul 2025 16:57:07 +0200 Subject: [PATCH 07/32] add backend function to retrieve list of excluded drives --- jupyter_drives/handlers.py | 5 +++++ jupyter_drives/manager.py | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/jupyter_drives/handlers.py b/jupyter_drives/handlers.py index 47da150..509d695 100644 --- a/jupyter_drives/handlers.py +++ b/jupyter_drives/handlers.py @@ -49,6 +49,11 @@ class ConfigJupyterDrivesHandler(JupyterDrivesAPIHandler): def initialize(self, logger: logging.Logger, manager: JupyterDrivesManager): return super().initialize(logger, manager) + @tornado.web.authenticated + async def get(self): + result = self._manager.get_excluded_drives() + self.finish(json.dumps(result["data"])) + @tornado.web.authenticated async def post(self): body = self.get_json_body() diff --git a/jupyter_drives/manager.py b/jupyter_drives/manager.py index ebc856d..ee458ca 100644 --- a/jupyter_drives/manager.py +++ b/jupyter_drives/manager.py @@ -188,6 +188,33 @@ def exclude_drive(self, exclude_drive_name): return + def get_excluded_drives(self): + """Get list of excluded drives. + + Returns: + List of excluded drives and their properties. + """ + data = [] + for drive in self._excluded_drives: + try: + data.append({ + "name": drive, + "region": self._config.region_name, + "creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'), + "mounted": False if drive not in self._content_managers else True, + "provider": self._config.provider + }) + except Exception as e: + raise tornado.web.HTTPError( + status_code=httpx.codes.BAD_REQUEST, + reason=f"The following error occured when listing excluded drives: {e}", + ) + + response = { + "data": data + } + return response + def include_drive(self, include_drive_name): """Include drive in listing. From 1c1e10704a6c72c115735fcc0569989d12638f3d Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Tue, 22 Jul 2025 16:58:01 +0200 Subject: [PATCH 08/32] add frontend request for retrieving list of excluded drives --- src/requests.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/requests.ts b/src/requests.ts index 9104780..db1b31e 100644 --- a/src/requests.ts +++ b/src/requests.ts @@ -87,6 +87,14 @@ export async function includeDrive(driveName: string) { return data; } +/** + * Fetch the list of excluded drives from the filebrowser. + * @returns The list of excluded drives. + */ +export async function getExcludedDrives() { + return await requestAPI('drives/config', 'GET'); +} + /** * Fetch the list of available drives. * @returns The list of available drives. From 332cb5f1883789ea4980429cf254fdf59d54487f Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Tue, 22 Jul 2025 17:20:47 +0200 Subject: [PATCH 09/32] refactor dialog to manage listed drives --- src/plugins/drivelistmanager.tsx | 286 +++++++++++++++++-------------- 1 file changed, 160 insertions(+), 126 deletions(-) diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index 66258c3..73a187a 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -8,20 +8,23 @@ import { Search } from '@jupyter/react-components'; import { useState } from 'react'; +import { IDriveInfo } from '../token'; +import { getDrivesList, getExcludedDrives, includeDrive } from '../requests'; +import { ISignal, Signal } from '@lumino/signaling'; interface IProps { model: DriveListModel; } -export interface IDrive { - name: string; - url: string; -} +// export interface IDrive { +// name: string; +// url: string; +// } export interface IDriveInputProps { isName: boolean; value: string; getValue: (event: any) => void; - updateSelectedDrives: (item: string, isName: boolean) => void; + // updateSelectedDrives: (item: string, isName: boolean) => void; } export function DriveInputComponent(props: IDriveInputProps) { return ( @@ -34,10 +37,10 @@ export function DriveInputComponent(props: IDriveInputProps) { @@ -46,9 +49,11 @@ export function DriveInputComponent(props: IDriveInputProps) { interface ISearchListProps { isName: boolean; value: string; - filteredList: Array; - filter: (value: any) => void; - updateSelectedDrives: (item: string, isName: boolean) => void; + // filteredList: Array; + setValue: (value: any) => void; + availableDrives: Partial[]; + // updateSelectedDrives: (item: string, isName: boolean) => void; + model: DriveListModel; } export function DriveSearchListComponent(props: ISearchListProps) { @@ -56,34 +61,45 @@ export function DriveSearchListComponent(props: ISearchListProps) {
- + props.setValue(event.target.value)} + />
- {props.filteredList.map((item, index) => ( -
  • -
    -
    -
    {item}
    -
    -
    - + {props.availableDrives + .filter(item => { + return ( + item.name!.toLowerCase().indexOf(props.value.toLowerCase()) !== -1 + ); + }) + .map((drive, index) => ( +
  • +
    +
    +
    {drive.name}
    +
    +
    + +
    -
  • - - ))} + + ))} ); } interface IDriveDataGridProps { - drives: IDrive[]; + drives: Partial[]; } export function DriveDataGridComponent(props: IDriveDataGridProps) { @@ -95,7 +111,7 @@ export function DriveDataGridComponent(props: IDriveDataGridProps) { name - url + region @@ -105,7 +121,7 @@ export function DriveDataGridComponent(props: IDriveDataGridProps) { {item.name} - {item.url} + {item.region} ))} @@ -114,123 +130,92 @@ export function DriveDataGridComponent(props: IDriveDataGridProps) { ); } -export function DriveListManagerComponent(props: IProps) { +export function DriveListManagerComponent({ model }: IProps) { const [driveUrl, setDriveUrl] = useState(''); - const [driveName, setDriveName] = useState(''); - let updatedSelectedDrives = [...props.model.selectedDrives]; - const [selectedDrives, setSelectedDrives] = useState(updatedSelectedDrives); - - const nameList: Array = []; + const [searchDrive, setSearchDrive] = useState(''); + // let updatedSelectedDrives = [...model.selectedDrives]; + const [selectedDrives, setSelectedDrives] = useState[]>( + model.selectedDrives + ); + const [availableDrives, setAvailableDrives] = useState[]>( + model.availableDrives + ); + // const [drivesFilteredList, setDrivesFilteredList] = useState[]>(availableDrives); + // const nameList: Array = []; - for (const item of props.model.availableDrives) { - if (item.name !== '') { - nameList.push(item.name); - } - } - const [nameFilteredList, setNameFilteredList] = useState(nameList); + // Called after mounting. + React.useEffect(() => { + model.refresh(); - const isDriveAlreadySelected = (pickedDrive: IDrive, driveList: IDrive[]) => { - const isbyNameIncluded: boolean[] = []; - const isbyUrlIncluded: boolean[] = []; - let isIncluded: boolean = false; - driveList.forEach(item => { - if (pickedDrive.name !== '' && pickedDrive.name === item.name) { - isbyNameIncluded.push(true); - } else { - isbyNameIncluded.push(false); - } - if (pickedDrive.url !== '' && pickedDrive.url === item.url) { - isbyUrlIncluded.push(true); - } else { - isbyUrlIncluded.push(false); - } + model.selectedDrivesChanged.connect((_, args) => { + setSelectedDrives(args); }); + model.availableDrivesChanged.connect((_, args) => { + setAvailableDrives(args); + }); + }, [model]); - if (isbyNameIncluded.includes(true) || isbyUrlIncluded.includes(true)) { - isIncluded = true; - } - - return isIncluded; - }; - - const updateSelectedDrives = (item: string, isName: boolean) => { - updatedSelectedDrives = [...props.model.selectedDrives]; - let pickedDrive: IDrive; - if (isName) { - pickedDrive = { name: item, url: '' }; - } else { - if (item !== driveUrl) { - setDriveUrl(item); - } - pickedDrive = { name: '', url: driveUrl }; - } - - const checkDrive = isDriveAlreadySelected( - pickedDrive, - updatedSelectedDrives - ); - if (checkDrive === false) { - updatedSelectedDrives.push(pickedDrive); - } else { - console.log('The selected drive is already in the list'); - } - - setSelectedDrives(updatedSelectedDrives); - props.model.setSelectedDrives(updatedSelectedDrives); - }; + // for (const item of availableDrives) { + // if (item.name !== '') { + // nameList.push(item.name!); + // } + // } const getValue = (event: any) => { setDriveUrl(event.target.value); }; - const filter = (event: any) => { - const query = event.target.value; - let updatedList: Array; + // const filter = (event: any) => { + // const query = event.target.value; + // let updatedList: Partial[]; - updatedList = [...nameList]; - updatedList = updatedList.filter(item => { - return item.toLowerCase().indexOf(query.toLowerCase()) !== -1; - }); - setNameFilteredList(updatedList); - if (nameFilteredList.length === 1 && nameFilteredList[0] !== '') { - setDriveName(nameFilteredList[0]); - setDriveUrl(''); - } - }; + // updatedList = [...drivesFilteredList]; + // updatedList = updatedList.filter((item: Partial) => { + // return item.name!.toLowerCase().indexOf(query.toLowerCase()) !== -1; + // }); + // setDrivesFilteredList(updatedList); + // // if (nameFilteredList.length === 1 && nameFilteredList[0] !== '') { + // // setDriveName(nameFilteredList[0]); + // // setDriveUrl(''); + // // } + // }; return ( <>
    -

    Select drive(s) to be added to your filebrowser

    +

    Managed listed drives

    -
    Enter a drive URL
    +
    Enter public drive name
    - updateSelectedDrives(value, isName) - } + // updateSelectedDrives={(value, isName) => + // updateSelectedDrives(value, isName) + // } /> -
    Select drive(s) from list
    +
    Available drives
    - updateSelectedDrives(value, isName) - } + value={searchDrive} + setValue={setSearchDrive} + // filteredList={drivesFilteredList} + // filter={filter} + availableDrives={availableDrives} + // updateSelectedDrives={(value, isName) => + // // updateSelectedDrives(value, isName) + // } + model={model} />
    - +
    @@ -242,18 +227,71 @@ export function DriveListManagerComponent(props: IProps) { } export class DriveListModel extends VDomModel { - public availableDrives: IDrive[]; - public selectedDrives: IDrive[]; + public availableDrives: Partial[]; + public selectedDrives: Partial[]; + private _selectedDrivesChanged = new Signal< + DriveListModel, + Partial[] + >(this); + private _availableDrivesChanged = new Signal< + DriveListModel, + Partial[] + >(this); - constructor(availableDrives: IDrive[], selectedDrives: IDrive[]) { + constructor( + availableDrives: Partial[], + selectedDrives: Partial[] + ) { super(); this.availableDrives = availableDrives; this.selectedDrives = selectedDrives; } - setSelectedDrives(selectedDrives: IDrive[]) { + + setSelectedDrives(selectedDrives: Partial[]) { this.selectedDrives = selectedDrives; } + + setAvailableDrives(availableDrives: Partial[]) { + this.availableDrives = availableDrives; + } + + get selectedDrivesChanged(): ISignal[]> { + return this._selectedDrivesChanged; + } + + get availableDrivesChanged(): ISignal[]> { + return this._availableDrivesChanged; + } + + refreshSelectedDrives() { + getDrivesList().then((drives: IDriveInfo[]) => { + this.setSelectedDrives( + drives.map((drive: IDriveInfo) => ({ + name: drive.name, + region: drive.region + })) + ); + this._selectedDrivesChanged.emit(this.selectedDrives); + }); + } + + refreshAvailanbleDrives() { + getExcludedDrives().then((drives: IDriveInfo[]) => { + this.setAvailableDrives( + drives.map((drive: IDriveInfo) => ({ + name: drive.name, + region: drive.region + })) + ); + this._availableDrivesChanged.emit(this.availableDrives); + }); + } + + async refresh() { + await this.refreshSelectedDrives(); + await this.refreshAvailanbleDrives(); + } } export class DriveListView extends VDomRenderer { @@ -262,10 +300,6 @@ export class DriveListView extends VDomRenderer { this.model = model; } render() { - return ( - <> - - - ); + return ; } } From 4bde19c55212f0d4b0e1e95292842f92ec17e486 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Tue, 22 Jul 2025 17:21:45 +0200 Subject: [PATCH 10/32] refactor openDriveDialogPlugin --- src/plugins/driveDialogPlugin.ts | 92 +++++++++++--------------------- 1 file changed, 30 insertions(+), 62 deletions(-) diff --git a/src/plugins/driveDialogPlugin.ts b/src/plugins/driveDialogPlugin.ts index 81b125f..739e01f 100644 --- a/src/plugins/driveDialogPlugin.ts +++ b/src/plugins/driveDialogPlugin.ts @@ -7,13 +7,14 @@ import { ITranslator } from '@jupyterlab/translation'; import { addJupyterLabThemeChangeListener } from '@jupyter/web-components'; import { Dialog, showDialog } from '@jupyterlab/apputils'; -import { DriveListModel, DriveListView, IDrive } from './drivelistmanager'; +import { DriveListModel, DriveListView } from './drivelistmanager'; import { driveBrowserIcon } from '../icons'; -import { CommandIDs } from '../token'; +import { CommandIDs, IDriveInfo } from '../token'; +import { getDrivesList, getExcludedDrives } from '../requests'; export const openDriveDialogPlugin: JupyterFrontEndPlugin = { id: 'jupyter-drives:widget', - description: 'Open a dialog to select drives to be added in the filebrowser.', + description: 'Open a dialog to managed listed drives in the filebrowser.', requires: [IFileBrowserFactory, ITranslator], autoStart: true, activate: ( @@ -25,68 +26,29 @@ export const openDriveDialogPlugin: JupyterFrontEndPlugin = { const { commands } = app; const { tracker } = factory; const trans = translator.load('jupyter_drives'); - const selectedDrivesModelMap = new Map(); + const selectedDrivesModelMap = new Map< + Partial[], + DriveListModel + >(); - let selectedDrives: IDrive[] = [ - { - name: 'CoconutDrive', - url: '/coconut/url' - } - ]; - - const availableDrives: IDrive[] = [ - { - name: 'CoconutDrive', - url: '/coconut/url' - }, - { - name: 'PearDrive', - url: '/pear/url' - }, - { - name: 'StrawberryDrive', - url: '/strawberrydrive/url' - }, - { - name: 'BlueberryDrive', - url: '/blueberrydrive/url' - }, - { - name: '', - url: '/mydrive/url' - }, - { - name: 'RaspberryDrive', - url: '/raspberrydrive/url' - }, + let selectedDrives: Partial[] = []; + getDrivesList().then((drives: IDriveInfo[]) => { + selectedDrives = drives.map((drive: IDriveInfo) => ({ + name: drive.name, + region: drive.region + })); + }); - { - name: 'PineAppleDrive', - url: '' - }, + let availableDrives: Partial[] = []; + getExcludedDrives().then((drives: IDriveInfo[]) => { + availableDrives = drives.map((drive: IDriveInfo) => ({ + name: drive.name, + region: drive.region + })); + }); - { name: 'PomeloDrive', url: '/https://pomelodrive/url' }, - { - name: 'OrangeDrive', - url: '' - }, - { - name: 'TomatoDrive', - url: '' - }, - { - name: '', - url: 'superDrive/url' - }, - { - name: 'AvocadoDrive', - url: '' - } - ]; let model = selectedDrivesModelMap.get(selectedDrives); - //const model = new DriveListModel(availableDrives, selectedDrives); - commands.addCommand(CommandIDs.openDrivesDialog, { execute: args => { const widget = tracker.currentWidget; @@ -109,8 +71,14 @@ export const openDriveDialogPlugin: JupyterFrontEndPlugin = { }, icon: driveBrowserIcon.bindprops({ stylesheet: 'menuItem' }), - caption: trans.__('Add drives to filebrowser.'), - label: trans.__('Add Drives To Filebrowser') + caption: trans.__('Manage drives listed in filebrowser.'), + label: trans.__('Manage listed drives') + }); + + app.contextMenu.addItem({ + command: CommandIDs.openDrivesDialog, + selector: '#drive-file-browser.jp-SidePanel', + rank: 100 }); } }; From 6003dc0ebe48db53a1b3da68960a964b09e5fae8 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Tue, 22 Jul 2025 17:24:00 +0200 Subject: [PATCH 11/32] remove unnecessary code --- src/plugins/drivelistmanager.tsx | 47 +------------------------------- 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index 73a187a..3a94795 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -15,16 +15,11 @@ import { ISignal, Signal } from '@lumino/signaling'; interface IProps { model: DriveListModel; } -// export interface IDrive { -// name: string; -// url: string; -// } export interface IDriveInputProps { isName: boolean; value: string; getValue: (event: any) => void; - // updateSelectedDrives: (item: string, isName: boolean) => void; } export function DriveInputComponent(props: IDriveInputProps) { return ( @@ -34,12 +29,7 @@ export function DriveInputComponent(props: IDriveInputProps) {
    -
    @@ -49,10 +39,8 @@ export function DriveInputComponent(props: IDriveInputProps) { interface ISearchListProps { isName: boolean; value: string; - // filteredList: Array; setValue: (value: any) => void; availableDrives: Partial[]; - // updateSelectedDrives: (item: string, isName: boolean) => void; model: DriveListModel; } @@ -86,7 +74,6 @@ export function DriveSearchListComponent(props: ISearchListProps) { onClick={async () => { await includeDrive(drive.name!); await props.model.refresh(); - // props.updateSelectedDrives(item, true); }} > add @@ -133,15 +120,12 @@ export function DriveDataGridComponent(props: IDriveDataGridProps) { export function DriveListManagerComponent({ model }: IProps) { const [driveUrl, setDriveUrl] = useState(''); const [searchDrive, setSearchDrive] = useState(''); - // let updatedSelectedDrives = [...model.selectedDrives]; const [selectedDrives, setSelectedDrives] = useState[]>( model.selectedDrives ); const [availableDrives, setAvailableDrives] = useState[]>( model.availableDrives ); - // const [drivesFilteredList, setDrivesFilteredList] = useState[]>(availableDrives); - // const nameList: Array = []; // Called after mounting. React.useEffect(() => { @@ -155,31 +139,10 @@ export function DriveListManagerComponent({ model }: IProps) { }); }, [model]); - // for (const item of availableDrives) { - // if (item.name !== '') { - // nameList.push(item.name!); - // } - // } - const getValue = (event: any) => { setDriveUrl(event.target.value); }; - // const filter = (event: any) => { - // const query = event.target.value; - // let updatedList: Partial[]; - - // updatedList = [...drivesFilteredList]; - // updatedList = updatedList.filter((item: Partial) => { - // return item.name!.toLowerCase().indexOf(query.toLowerCase()) !== -1; - // }); - // setDrivesFilteredList(updatedList); - // // if (nameFilteredList.length === 1 && nameFilteredList[0] !== '') { - // // setDriveName(nameFilteredList[0]); - // // setDriveUrl(''); - // // } - // }; - return ( <>
    @@ -193,9 +156,6 @@ export function DriveListManagerComponent({ model }: IProps) { isName={false} value={driveUrl} getValue={getValue} - // updateSelectedDrives={(value, isName) => - // updateSelectedDrives(value, isName) - // } />
    Available drives
    @@ -203,12 +163,7 @@ export function DriveListManagerComponent({ model }: IProps) { isName={true} value={searchDrive} setValue={setSearchDrive} - // filteredList={drivesFilteredList} - // filter={filter} availableDrives={availableDrives} - // updateSelectedDrives={(value, isName) => - // // updateSelectedDrives(value, isName) - // } model={model} />
    From 9e71fbdf5167bb1b3f8115eabd6e249f299e01f3 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 12:33:24 +0200 Subject: [PATCH 12/32] fix drives region in backend --- jupyter_drives/manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jupyter_drives/manager.py b/jupyter_drives/manager.py index ee458ca..51a4df8 100644 --- a/jupyter_drives/manager.py +++ b/jupyter_drives/manager.py @@ -199,7 +199,7 @@ def get_excluded_drives(self): try: data.append({ "name": drive, - "region": self._config.region_name, + "region": self._config.region_name if drive not in self._content_managers else self._content_managers[drive]["location"], "creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'), "mounted": False if drive not in self._content_managers else True, "provider": self._config.provider @@ -260,7 +260,7 @@ async def list_drives(self): data.append( { "name": result.name, - "region": self._config.region_name, + "region": self._config.region_name if result.name not in self._content_managers else self._content_managers[result.name]["location"], "creationDate": result.extra["creation_date"], "mounted": False if result.name not in self._content_managers else True, "provider": self._config.provider @@ -272,7 +272,7 @@ async def list_drives(self): try: data.append({ "name": drive['url'], - "region": self._config.region_name, + "region": self._config.region_name if drive['url'] not in self._content_managers else self._content_managers[drive['url']]["location"], "creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'), "mounted": False if result.name not in self._content_managers else True, "provider": self._config.provider From 32c59f2a8cba1cfc266b2b01ae552f9621ee466a Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 15:15:52 +0200 Subject: [PATCH 13/32] update dialog for managing listed drives --- src/plugins/drivelistmanager.tsx | 84 +++++++++++++++------------ style/base.css | 98 ++++++++++++++++++++++++++------ 2 files changed, 128 insertions(+), 54 deletions(-) diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index 3a94795..65eec38 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -11,6 +11,7 @@ import { useState } from 'react'; import { IDriveInfo } from '../token'; import { getDrivesList, getExcludedDrives, includeDrive } from '../requests'; import { ISignal, Signal } from '@lumino/signaling'; +import { driveBrowserIcon } from '../icons'; interface IProps { model: DriveListModel; @@ -24,11 +25,8 @@ export interface IDriveInputProps { export function DriveInputComponent(props: IDriveInputProps) { return (
    -
    -
    - -
    -
    +
    + @@ -70,7 +68,7 @@ export function DriveSearchListComponent(props: ISearchListProps) {
    + ))} @@ -144,40 +148,46 @@ export function DriveListManagerComponent({ model }: IProps) { }; return ( - <> -
    -
    -

    Managed listed drives

    +
    + + +
    + {'Manage listed drives'} +
    + {'Add or remove drives from the filebrowser.'} +
    +
    +
    +
    +
    +
    -
    -
    -
    Enter public drive name
    - -
    Available drives
    - -
    +
    +
    Enter public drive name
    + +
    -
    -
    - - - -
    -
    +
    +
    Available drives
    +
    - +
    ); } diff --git a/style/base.css b/style/base.css index 5c6483c..759bd82 100644 --- a/style/base.css +++ b/style/base.css @@ -11,64 +11,128 @@ li { list-style-type: none; } -.column { - float: left; - width: 50%; -} +/* .two-column-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.6rem; + margin-top: 10px; +} */ /* Clear floats after the columns */ -.row::after { +/* .row::after { content: ''; display: table; clear: both; -} +} */ .drive-search-list { width: auto; } .drive-list-manager { - width: 800px; - height: 800px; + min-width: 400px; + min-height: 300px; + display: flex; + flex-direction: column; } .search-add-drive-button { background-color: var(--md-blue-700); color: white; - /* width: 8em; */ - height: 1em; + width: 8px; + /* height: 1em; */ border-radius: 4px; } .input-add-drive-button { - position: relative; + width: 50px; + height: 24px !important; background-color: var(--jp-brand-color1); text-align: center; color: white; - height: calc( - (var(--base-height-multiplier) + var(--density)) * var(--design-unit) * 1px - ); border-radius: 4px; } .drive-search-input { - height: 14px; + height: 24px !important; + width: 100%; + flex: 1; + justify-content: center; } .drive-data-grid { - width: 400px; + width: 380px; + flex-direction: row; + grid-auto-flow: row; } .data-grid-cell { text-align: justify; height: 2em; min-width: 200px; + border-right: 5px; + border-left: 2px; + /* background-color: var(--jp-layout-color2); */ +} + +.data-grid-cell-secondary { + text-align: justify; + height: 2em; + min-width: 100px; + border-right: 2px; + border-left: 2px; + /* background-color: var(--jp-layout-color2); */ +} + +.data-grid-cell-button { + text-align: justify; + height: 2em; + width: 50px; border-right: 2px; border-left: 2px; - background-color: var(--jp-layout-color2); + /* background-color: var(--jp-layout-color2); */ } .jp-drive-browser-search-box { width: 230px; } + +.drives-manager-header { + display: flex; + flex-direction: row; + align-items: center; + margin-left: 10px; +} + +.drives-manager-header-title { + display: flex; + flex-direction: column; + padding-top: 18px; + font-size: 1rem; + font-weight: 600; +} + +.drives-manager-header-info { + text-align: left; + font-weight: 400; + font-size: 0.8rem; + + /* padding-top: 5px; */ + color: var(--md-blue-grey-600); +} + +.drives-manager-section { + width: 380px; + flex: 1; + overflow-y: auto; + padding: 0.5rem; +} + +.add-public-drive-section { + display: flex; + width: 380px; + flex-direction: row; + align-items: center; + gap: 0.2rem; +} From 0ccb108ea0b89b466436c4425b1bb8ffb952a3cd Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 16:25:27 +0200 Subject: [PATCH 14/32] update publc drive component --- src/plugins/drivelistmanager.tsx | 23 ++++++++++++++++++----- style/base.css | 10 +++++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index 65eec38..19541af 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -9,7 +9,12 @@ import { } from '@jupyter/react-components'; import { useState } from 'react'; import { IDriveInfo } from '../token'; -import { getDrivesList, getExcludedDrives, includeDrive } from '../requests'; +import { + addPublicDrive, + getDrivesList, + getExcludedDrives, + includeDrive +} from '../requests'; import { ISignal, Signal } from '@lumino/signaling'; import { driveBrowserIcon } from '../icons'; @@ -22,12 +27,20 @@ export interface IDriveInputProps { value: string; getValue: (event: any) => void; } + export function DriveInputComponent(props: IDriveInputProps) { return (
    - -
    @@ -168,7 +181,7 @@ export function DriveListManagerComponent({ model }: IProps) {
    -
    Enter public drive name
    +
    Add public drive
    -
    Available drives
    +
    Browser available drives
    Date: Wed, 23 Jul 2025 16:46:06 +0200 Subject: [PATCH 15/32] update available drives component --- src/plugins/drivelistmanager.tsx | 73 ++++++++++++++++---------------- style/base.css | 30 ++++++------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index 19541af..9a30439 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -57,43 +57,44 @@ interface ISearchListProps { export function DriveSearchListComponent(props: ISearchListProps) { return ( -
    -
    -
    - props.setValue(event.target.value)} - /> -
    -
    + <> + props.setValue(event.target.value)} + placeholder="Search drive name" + /> +
    + {props.availableDrives.length === 0 ? ( +
    + {'No available drives.'} +
    + ) : ( + props.availableDrives + .filter(item => { + return ( + item.name!.toLowerCase().indexOf(props.value.toLowerCase()) !== + -1 + ); + }) + .map((drive, index) => ( +
  • +
    +
    {drive.name}
    + +
    +
  • + )) + )}
    - {props.availableDrives - .filter(item => { - return ( - item.name!.toLowerCase().indexOf(props.value.toLowerCase()) !== -1 - ); - }) - .map((drive, index) => ( -
  • -
    -
    -
    {drive.name}
    -
    -
    - -
    -
    -
  • - ))} -
    + ); } interface IDriveDataGridProps { diff --git a/style/base.css b/style/base.css index af82a06..a67acd7 100644 --- a/style/base.css +++ b/style/base.css @@ -11,23 +11,12 @@ li { list-style-type: none; } -/* .two-column-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 0.6rem; - margin-top: 10px; -} */ - -/* Clear floats after the columns */ - -/* .row::after { - content: ''; - display: table; - clear: both; -} */ - .drive-search-list { - width: auto; + display: flex; + width: 380px; + flex-direction: column; + align-items: center; + gap: 0.2rem; } .drive-list-manager { @@ -144,3 +133,12 @@ li { .drives-section-title { font-weight: 500; } + +.available-drives-section { + display: flex; + width: 370px; + height: 24px; + flex-direction: row; + align-items: center; + justify-content: space-between; +} From 45b54ef89f22f7b305844f01caafc8cf95407ea0 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 20:46:30 +0200 Subject: [PATCH 16/32] update available drives listed component --- src/icons.ts | 6 ++++++ src/plugins/drivelistmanager.tsx | 8 ++++++-- style/addIcon.svg | 1 + style/base.css | 29 ++++++++++++++++++++++------- 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 style/addIcon.svg diff --git a/src/icons.ts b/src/icons.ts index 23a6a94..daf7d92 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -1,7 +1,13 @@ import { LabIcon } from '@jupyterlab/ui-components'; import driveBrowserSvg from '../style/driveIconFileBrowser.svg'; +import addIconSvg from '../style/addIcon.svg'; export const driveBrowserIcon = new LabIcon({ name: 'jupyter-drives:drive-browser', svgstr: driveBrowserSvg }); + +export const addIcon = new LabIcon({ + name: 'jupyter-drives:add-drive', + svgstr: addIconSvg +}); diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index 9a30439..272f24e 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -16,7 +16,7 @@ import { includeDrive } from '../requests'; import { ISignal, Signal } from '@lumino/signaling'; -import { driveBrowserIcon } from '../icons'; +import { driveBrowserIcon, addIcon } from '../icons'; interface IProps { model: DriveListModel; @@ -87,7 +87,11 @@ export function DriveSearchListComponent(props: ISearchListProps) { await props.model.refresh(); }} > - add +
    diff --git a/style/addIcon.svg b/style/addIcon.svg new file mode 100644 index 0000000..15711f4 --- /dev/null +++ b/style/addIcon.svg @@ -0,0 +1 @@ + diff --git a/style/base.css b/style/base.css index a67acd7..570ab69 100644 --- a/style/base.css +++ b/style/base.css @@ -27,18 +27,18 @@ li { } .search-add-drive-button { - background-color: var(--md-blue-700); - color: white; - width: 8px; - - /* height: 1em; */ + background-color: var(--jp-inverse-layout-color1); + width: 50px; + height: 20px; border-radius: 4px; + margin-right: 0; } .input-add-drive-button { width: 50px; height: 24px !important; - background-color: var(--jp-brand-color1); + background-color: var(--jp-inverse-layout-color1); + color: white !important; text-align: center; color: white; border-radius: 4px; @@ -136,9 +136,24 @@ li { .available-drives-section { display: flex; - width: 370px; + width: 375px; height: 24px; flex-direction: row; align-items: center; justify-content: space-between; + border-radius: 4px; + padding-left: 4px; + padding-right: 4px; +} + +.available-drives-section:hover { + background-color: var(--md-blue-grey-100); +} + +.available-drives-icon { + display: flex; + align-items: center; + justify-content: center; + fill: var(--jp-ui-inverse-font-color0) !important; + color: var(--jp-ui-inverse-font-color0) !important; } From 816fc65a74ef5445613a13412693e24f7b3784e9 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 22:46:11 +0200 Subject: [PATCH 17/32] update selected drives component --- src/icons.ts | 6 +++ src/plugins/drivelistmanager.tsx | 70 +++++++++++++++----------------- style/removeIcon.svg | 1 + 3 files changed, 40 insertions(+), 37 deletions(-) create mode 100644 style/removeIcon.svg diff --git a/src/icons.ts b/src/icons.ts index daf7d92..955264f 100644 --- a/src/icons.ts +++ b/src/icons.ts @@ -1,6 +1,7 @@ import { LabIcon } from '@jupyterlab/ui-components'; import driveBrowserSvg from '../style/driveIconFileBrowser.svg'; import addIconSvg from '../style/addIcon.svg'; +import removeIconSvg from '../style/removeIcon.svg'; export const driveBrowserIcon = new LabIcon({ name: 'jupyter-drives:drive-browser', @@ -11,3 +12,8 @@ export const addIcon = new LabIcon({ name: 'jupyter-drives:add-drive', svgstr: addIconSvg }); + +export const removeIcon = new LabIcon({ + name: 'jupyter-drives:remove-drive', + svgstr: removeIconSvg +}); diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index 272f24e..14633ab 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -1,22 +1,17 @@ import * as React from 'react'; import { VDomModel, VDomRenderer } from '@jupyterlab/ui-components'; -import { - Button, - DataGrid, - DataGridCell, - DataGridRow, - Search -} from '@jupyter/react-components'; +import { Button, Search } from '@jupyter/react-components'; import { useState } from 'react'; import { IDriveInfo } from '../token'; import { addPublicDrive, + excludeDrive, getDrivesList, getExcludedDrives, includeDrive } from '../requests'; import { ISignal, Signal } from '@lumino/signaling'; -import { driveBrowserIcon, addIcon } from '../icons'; +import { driveBrowserIcon, addIcon, removeIcon } from '../icons'; interface IProps { model: DriveListModel; @@ -103,38 +98,38 @@ export function DriveSearchListComponent(props: ISearchListProps) { } interface IDriveDataGridProps { drives: Partial[]; + model: DriveListModel; } export function DriveDataGridComponent(props: IDriveDataGridProps) { return ( -
    - - - - name - - - region - - - - - {props.drives.map((item, index) => ( - - - {item.name} - - - {item.region} - - - - - - ))} - +
    + + )) + )}
    ); } @@ -180,9 +175,10 @@ export function DriveListManagerComponent({ model }: IProps) {
    -
    +
    - +
    Selected drives
    +
    diff --git a/style/removeIcon.svg b/style/removeIcon.svg new file mode 100644 index 0000000..de4f7d3 --- /dev/null +++ b/style/removeIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file From 8490a248cdd388656b3f541082e332bf9824b96c Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 23:00:41 +0200 Subject: [PATCH 18/32] remove duplicate --- style/base.css | 1 - 1 file changed, 1 deletion(-) diff --git a/style/base.css b/style/base.css index 570ab69..7cfa923 100644 --- a/style/base.css +++ b/style/base.css @@ -40,7 +40,6 @@ li { background-color: var(--jp-inverse-layout-color1); color: white !important; text-align: center; - color: white; border-radius: 4px; } From 2195ec3bb054bb76acd7dda641f458a06b927e68 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 23:46:02 +0200 Subject: [PATCH 19/32] iterate on public drive functionality --- src/plugins/drivelistmanager.tsx | 36 +++++++++++++++++++++++--------- style/base.css | 2 +- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index 14633ab..efe1efe 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -20,21 +20,32 @@ interface IProps { export interface IDriveInputProps { isName: boolean; value: string; - getValue: (event: any) => void; + setPublicDrive: (value: string) => void; + onSubmit: () => void; } -export function DriveInputComponent(props: IDriveInputProps) { +export function DriveInputComponent({ + value, + setPublicDrive, + onSubmit +}: IDriveInputProps) { return (
    - { + setPublicDrive(event.target.value); + }} placeholder="Enter drive name" + value={value} /> @@ -135,7 +146,7 @@ export function DriveDataGridComponent(props: IDriveDataGridProps) { } export function DriveListManagerComponent({ model }: IProps) { - const [driveUrl, setDriveUrl] = useState(''); + const [publicDrive, setPublicDrive] = useState(''); const [searchDrive, setSearchDrive] = useState(''); const [selectedDrives, setSelectedDrives] = useState[]>( model.selectedDrives @@ -156,8 +167,12 @@ export function DriveListManagerComponent({ model }: IProps) { }); }, [model]); - const getValue = (event: any) => { - setDriveUrl(event.target.value); + const onAddedPublicDrive = async () => { + console.log(publicDrive); + await addPublicDrive(publicDrive); + setPublicDrive(''); + console.log('publicDrve: ', publicDrive); + await model.refresh(); }; return ( @@ -185,8 +200,9 @@ export function DriveListManagerComponent({ model }: IProps) {
    Add public drive
    diff --git a/style/base.css b/style/base.css index 7cfa923..b020049 100644 --- a/style/base.css +++ b/style/base.css @@ -44,7 +44,7 @@ li { } .drive-search-input { - height: 24px !important; + height: 20px !important; width: 100%; flex: 1; justify-content: center; From e5c6957de576c8c6ace5e71333d93f4ae331a415 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 23:51:32 +0200 Subject: [PATCH 20/32] check if external drives are excluded --- jupyter_drives/manager.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/jupyter_drives/manager.py b/jupyter_drives/manager.py index 51a4df8..3a4658a 100644 --- a/jupyter_drives/manager.py +++ b/jupyter_drives/manager.py @@ -270,13 +270,14 @@ async def list_drives(self): if len(self._external_drives) != 0: for drive in self._external_drives.values(): try: - data.append({ - "name": drive['url'], - "region": self._config.region_name if drive['url'] not in self._content_managers else self._content_managers[drive['url']]["location"], - "creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'), - "mounted": False if result.name not in self._content_managers else True, - "provider": self._config.provider - }) + if drive['url'] not in self._excluded_drives: + data.append({ + "name": drive['url'], + "region": self._config.region_name if drive['url'] not in self._content_managers else self._content_managers[drive['url']]["location"], + "creationDate": datetime.now().isoformat(timespec='milliseconds').replace('+00:00', 'Z'), + "mounted": False if result.name not in self._content_managers else True, + "provider": self._config.provider + }) except Exception as e: raise tornado.web.HTTPError( status_code=httpx.codes.BAD_REQUEST, From 775489ab7196610c98f6317b282f86d5bf336056 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 23:51:51 +0200 Subject: [PATCH 21/32] iterate --- src/plugins/drivelistmanager.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index efe1efe..b19e9c1 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -40,13 +40,7 @@ export function DriveInputComponent({ placeholder="Enter drive name" value={value} /> -
    @@ -168,10 +162,8 @@ export function DriveListManagerComponent({ model }: IProps) { }, [model]); const onAddedPublicDrive = async () => { - console.log(publicDrive); await addPublicDrive(publicDrive); setPublicDrive(''); - console.log('publicDrve: ', publicDrive); await model.refresh(); }; From 4a2b8b7548dcf11d3b3e6e429937ece5643c169e Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 23:53:57 +0200 Subject: [PATCH 22/32] iterate on style --- style/base.css | 1 + 1 file changed, 1 insertion(+) diff --git a/style/base.css b/style/base.css index b020049..f7c082e 100644 --- a/style/base.css +++ b/style/base.css @@ -95,6 +95,7 @@ li { flex-direction: row; align-items: center; margin-left: 10px; + margin-bottom: 6px; } .drives-manager-header-title { From e6f9bf89466e836b55007c9c0419827d40b58cf8 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Wed, 23 Jul 2025 23:59:49 +0200 Subject: [PATCH 23/32] fix icons --- style/addIcon.svg | 2 +- style/removeIcon.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/style/addIcon.svg b/style/addIcon.svg index 15711f4..f5bd421 100644 --- a/style/addIcon.svg +++ b/style/addIcon.svg @@ -1 +1 @@ - + diff --git a/style/removeIcon.svg b/style/removeIcon.svg index de4f7d3..f126f7b 100644 --- a/style/removeIcon.svg +++ b/style/removeIcon.svg @@ -1 +1 @@ - \ No newline at end of file + From 6996e99997e14ea4826b506af8f7ce85c72d0577 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Thu, 24 Jul 2025 00:00:04 +0200 Subject: [PATCH 24/32] update command icons --- src/plugins/driveBrowserPlugin.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/driveBrowserPlugin.ts b/src/plugins/driveBrowserPlugin.ts index 0e251c4..49dcf70 100644 --- a/src/plugins/driveBrowserPlugin.ts +++ b/src/plugins/driveBrowserPlugin.ts @@ -33,7 +33,7 @@ import { PageConfig, PathExt } from '@jupyterlab/coreutils'; import { CommandRegistry } from '@lumino/commands'; import { Widget } from '@lumino/widgets'; -import { driveBrowserIcon } from '../icons'; +import { addIcon, driveBrowserIcon, removeIcon } from '../icons'; import { Drive } from '../contents'; import { setListingLimit } from '../requests'; import { CommandIDs } from '../token'; @@ -561,7 +561,7 @@ namespace Private { drive.excludeDrive(driveName); }, label: 'Exclude Drive', - icon: driveBrowserIcon.bindprops({ stylesheet: 'menuItem' }) + icon: removeIcon.bindprops({ stylesheet: 'menuItem' }) }); app.contextMenu.addItem({ @@ -594,7 +594,7 @@ namespace Private { }); }, label: 'Include Drive', - icon: driveBrowserIcon.bindprops({ stylesheet: 'menuItem' }) + icon: addIcon.bindprops({ stylesheet: 'menuItem' }) }); app.contextMenu.addItem({ From a5215e96f32fba3b8e215d6a6575e2a60dba519e Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Thu, 24 Jul 2025 00:10:26 +0200 Subject: [PATCH 25/32] change commands visibility --- src/plugins/driveBrowserPlugin.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/driveBrowserPlugin.ts b/src/plugins/driveBrowserPlugin.ts index 49dcf70..57163fd 100644 --- a/src/plugins/driveBrowserPlugin.ts +++ b/src/plugins/driveBrowserPlugin.ts @@ -423,7 +423,7 @@ namespace Private { }); app.commands.addCommand(CommandIDs.addPublicDrive, { - isEnabled: () => { + isVisible: () => { return browser.model.path === 's3:'; }, execute: async () => { @@ -544,7 +544,7 @@ namespace Private { }); app.commands.addCommand(CommandIDs.excludeDrive, { - isEnabled: () => { + isVisible: () => { return browser.model.path === 's3:'; }, execute: () => { @@ -572,7 +572,7 @@ namespace Private { }); app.commands.addCommand(CommandIDs.includeDrive, { - isEnabled: () => { + isVisible: () => { return browser.model.path === 's3:'; }, execute: async () => { From 6700e662661fc83744b5302ad42bce6b9607ae35 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Thu, 24 Jul 2025 00:28:30 +0200 Subject: [PATCH 26/32] iterate --- src/contents.ts | 6 +++--- src/plugins/driveBrowserPlugin.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/contents.ts b/src/contents.ts index b98fdef..df08cb3 100644 --- a/src/contents.ts +++ b/src/contents.ts @@ -697,9 +697,9 @@ export class Drive implements Contents.IDrive { Contents.validateContentsModel(data); this._fileChanged.emit({ - type: 'new', - oldValue: null, - newValue: data + type: 'delete', + oldValue: data, + newValue: null }); return data; diff --git a/src/plugins/driveBrowserPlugin.ts b/src/plugins/driveBrowserPlugin.ts index 57163fd..d3c7c95 100644 --- a/src/plugins/driveBrowserPlugin.ts +++ b/src/plugins/driveBrowserPlugin.ts @@ -547,7 +547,7 @@ namespace Private { isVisible: () => { return browser.model.path === 's3:'; }, - execute: () => { + execute: async () => { const widget = tracker.currentWidget; if (!widget) { return; @@ -558,7 +558,7 @@ namespace Private { } const driveName: string = item.value.name; - drive.excludeDrive(driveName); + await drive.excludeDrive(driveName); }, label: 'Exclude Drive', icon: removeIcon.bindprops({ stylesheet: 'menuItem' }) @@ -587,9 +587,9 @@ namespace Private { ariaLabel: 'Include Drive' }) ] - }).then(result => { + }).then(async result => { if (result.value) { - drive.includeDrive(result.value); + await drive.includeDrive(result.value); } }); }, From aac52c69fbf5fdb241c5ae384590b4f006945856 Mon Sep 17 00:00:00 2001 From: Denisa Checiu <91504950+DenisaCG@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:58:24 +0200 Subject: [PATCH 27/32] fix typos Co-authored-by: Afshin Taylor Darian --- jupyter_drives/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyter_drives/manager.py b/jupyter_drives/manager.py index 3a4658a..1348338 100644 --- a/jupyter_drives/manager.py +++ b/jupyter_drives/manager.py @@ -207,7 +207,7 @@ def get_excluded_drives(self): except Exception as e: raise tornado.web.HTTPError( status_code=httpx.codes.BAD_REQUEST, - reason=f"The following error occured when listing excluded drives: {e}", + reason=f"The following error occurred when listing excluded drives: {e}", ) response = { @@ -226,7 +226,7 @@ def include_drive(self, include_drive_name): except Exception as e: raise tornado.web.HTTPError( status_code= httpx.codes.BAD_REQUEST, - reason= f"The following error occured when including the drive: {e}" + reason= f"The following error occurred when including the drive: {e}" ) return From ba05f37803cf4fee3d1fea7f117f88b485bd11e7 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Thu, 24 Jul 2025 15:25:54 +0200 Subject: [PATCH 28/32] remove include drive command --- src/plugins/driveBrowserPlugin.ts | 34 +------------------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/src/plugins/driveBrowserPlugin.ts b/src/plugins/driveBrowserPlugin.ts index d3c7c95..bed0e04 100644 --- a/src/plugins/driveBrowserPlugin.ts +++ b/src/plugins/driveBrowserPlugin.ts @@ -33,7 +33,7 @@ import { PageConfig, PathExt } from '@jupyterlab/coreutils'; import { CommandRegistry } from '@lumino/commands'; import { Widget } from '@lumino/widgets'; -import { addIcon, driveBrowserIcon, removeIcon } from '../icons'; +import { driveBrowserIcon, removeIcon } from '../icons'; import { Drive } from '../contents'; import { setListingLimit } from '../requests'; import { CommandIDs } from '../token'; @@ -570,37 +570,5 @@ namespace Private { '#drive-file-browser.jp-SidePanel .jp-DirListing-content .jp-DirListing-item[data-isdir]', rank: 110 }); - - app.commands.addCommand(CommandIDs.includeDrive, { - isVisible: () => { - return browser.model.path === 's3:'; - }, - execute: async () => { - return showDialog({ - title: 'Include Drive', - body: new Private.AddPublicDriveHandler(drive.name), - focusNodeSelector: 'input', - buttons: [ - Dialog.cancelButton(), - Dialog.okButton({ - label: 'Add', - ariaLabel: 'Include Drive' - }) - ] - }).then(async result => { - if (result.value) { - await drive.includeDrive(result.value); - } - }); - }, - label: 'Include Drive', - icon: addIcon.bindprops({ stylesheet: 'menuItem' }) - }); - - app.contextMenu.addItem({ - command: CommandIDs.includeDrive, - selector: '#drive-file-browser.jp-SidePanel .jp-DirListing-content', - rank: 110 - }); } } From 8004f7ab9ee116410a68cb9e9ad3ce632928118a Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Thu, 24 Jul 2025 15:26:34 +0200 Subject: [PATCH 29/32] rename exclude command to remove --- src/plugins/driveBrowserPlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/driveBrowserPlugin.ts b/src/plugins/driveBrowserPlugin.ts index bed0e04..32a705a 100644 --- a/src/plugins/driveBrowserPlugin.ts +++ b/src/plugins/driveBrowserPlugin.ts @@ -560,7 +560,7 @@ namespace Private { const driveName: string = item.value.name; await drive.excludeDrive(driveName); }, - label: 'Exclude Drive', + label: 'Remove Drive', icon: removeIcon.bindprops({ stylesheet: 'menuItem' }) }); From 4914bfeb2ba9bdb5ccddb2795c8323603c38ced9 Mon Sep 17 00:00:00 2001 From: DenisaCG Date: Thu, 24 Jul 2025 15:29:08 +0200 Subject: [PATCH 30/32] add titles to buttons --- src/plugins/drivelistmanager.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/plugins/drivelistmanager.tsx b/src/plugins/drivelistmanager.tsx index b19e9c1..ddb55e4 100644 --- a/src/plugins/drivelistmanager.tsx +++ b/src/plugins/drivelistmanager.tsx @@ -40,7 +40,11 @@ export function DriveInputComponent({ placeholder="Enter drive name" value={value} /> -
    @@ -86,6 +90,7 @@ export function DriveSearchListComponent(props: ISearchListProps) { await includeDrive(drive.name!); await props.model.refresh(); }} + title="Add drive" > Date: Thu, 24 Jul 2025 15:33:03 +0200 Subject: [PATCH 31/32] move drives manager command to toolbar --- schema/drives-file-browser.json | 4 ++-- src/plugins/driveDialogPlugin.ts | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/schema/drives-file-browser.json b/schema/drives-file-browser.json index ff24fdf..742988a 100644 --- a/schema/drives-file-browser.json +++ b/schema/drives-file-browser.json @@ -28,8 +28,8 @@ "rank": 40 }, { - "name": "new-drive", - "command": "drives:create-new-drive", + "name": "drives-manager", + "command": "drives:open-drives-dialog", "label": "", "rank": 5 } diff --git a/src/plugins/driveDialogPlugin.ts b/src/plugins/driveDialogPlugin.ts index 739e01f..bcf9515 100644 --- a/src/plugins/driveDialogPlugin.ts +++ b/src/plugins/driveDialogPlugin.ts @@ -74,11 +74,5 @@ export const openDriveDialogPlugin: JupyterFrontEndPlugin = { caption: trans.__('Manage drives listed in filebrowser.'), label: trans.__('Manage listed drives') }); - - app.contextMenu.addItem({ - command: CommandIDs.openDrivesDialog, - selector: '#drive-file-browser.jp-SidePanel', - rank: 100 - }); } }; From 93effd69e9260d0b5bd6c8d3f9b5aeeaf7fec7db Mon Sep 17 00:00:00 2001 From: Denisa Checiu <91504950+DenisaCG@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:11:40 +0200 Subject: [PATCH 32/32] remove commentes Co-authored-by: Afshin Taylor Darian --- style/base.css | 8 -------- 1 file changed, 8 deletions(-) diff --git a/style/base.css b/style/base.css index f7c082e..da3bf7c 100644 --- a/style/base.css +++ b/style/base.css @@ -62,8 +62,6 @@ li { min-width: 200px; border-right: 5px; border-left: 2px; - - /* background-color: var(--jp-layout-color2); */ } .data-grid-cell-secondary { @@ -72,8 +70,6 @@ li { min-width: 100px; border-right: 2px; border-left: 2px; - - /* background-color: var(--jp-layout-color2); */ } .data-grid-cell-button { @@ -82,8 +78,6 @@ li { width: 50px; border-right: 2px; border-left: 2px; - - /* background-color: var(--jp-layout-color2); */ } .jp-drive-browser-search-box { @@ -110,8 +104,6 @@ li { text-align: left; font-weight: 400; font-size: 0.8rem; - - /* padding-top: 5px; */ color: var(--md-blue-grey-600); }