diff --git a/examples/introduction.ipynb b/examples/introduction.ipynb index 91b74b0..88e678a 100644 --- a/examples/introduction.ipynb +++ b/examples/introduction.ipynb @@ -397,7 +397,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.1" + "version": "3.13.2" } }, "nbformat": 4, diff --git a/src/bluetooth-extension/index.ts b/src/bluetooth-extension/index.ts index 019a62a..b6f355b 100644 --- a/src/bluetooth-extension/index.ts +++ b/src/bluetooth-extension/index.ts @@ -69,34 +69,20 @@ const BluetoothSidebarPlugin: JupyterFrontEndPlugin = { ); let runningItemsList: Array; - function createTestFunction(device: BluetoothManager.Device) { - return function test(node: HTMLElement): boolean { - const testString = buildCompleteIdentifier(device.native); - return node.title === testString; - }; - } - - commands.addCommand(CommandIDs.disconnectDevice, { - execute: async args => { - bluetoothManager.deviceList.filter(item => { - const testWithDevice = createTestFunction(item); - const node = app.contextMenuHitTest(testWithDevice); - const identifier = buildCompleteIdentifier(item.native); - if (identifier === node?.title) { - bluetoothManager.disconnectDevice(item); - } - }); + app.commands.addCommand(CommandIDs.disconnectDevice, { + execute: (args) => { + const selectedDevice= bluetoothManager.deviceList.find((device) => device.native.id === args.deviceID as string); + if (selectedDevice) { + bluetoothManager.disconnectDevice(selectedDevice); + return selectedDevice; + } else { + throw new Error('No device provided or device is invalid'); + } }, caption: trans.__('Disconnect device'), label: trans.__('Disconnect Device') }); - /* Adding commands to the context menu of the relevant connected device*/ - app.contextMenu.addItem({ - command: CommandIDs.disconnectDevice, - selector: 'jp-tree-item.jp-RunningSessions-item.jp-bluetooth-Move-Hub', - rank: 0 - }); app.commands.addCommand(CommandIDs.openDeviceRegistryDialog, { execute: async () => { @@ -121,6 +107,7 @@ const BluetoothSidebarPlugin: JupyterFrontEndPlugin = { } }); + managers.add({ name: trans.__('Bluetooth Devices'), supportsMultipleViews: false, @@ -130,10 +117,12 @@ const BluetoothSidebarPlugin: JupyterFrontEndPlugin = { runningItemsList.push( new BluetoothDeviceRunningItem( device, - bluetoothManager as BluetoothManager + bluetoothManager as BluetoothManager, + commands ) ); - }); + } + ); return runningItemsList; }, shutdownAll: () => { @@ -163,8 +152,7 @@ const BluetoothSidebarPlugin: JupyterFrontEndPlugin = { export class DropDownRegistry extends Widget - implements Dialog.IBodyWidget -{ + implements Dialog.IBodyWidget { constructor(registry: BluetoothManager.DeviceRegistry) { super(); this._selectList = document.createElement('select'); diff --git a/src/bluetooth/BluetoothDeviceRunningItem.ts b/src/bluetooth/BluetoothDeviceRunningItem.ts index 12bbab0..ab68714 100644 --- a/src/bluetooth/BluetoothDeviceRunningItem.ts +++ b/src/bluetooth/BluetoothDeviceRunningItem.ts @@ -2,22 +2,42 @@ import { IRunningSessions } from '@jupyterlab/running'; import { BluetoothConnectIcon } from './icon'; import { BluetoothManager } from './BluetoothManager'; import { buildCompleteIdentifier } from '../bluetooth-extension'; +import { Menu } from '@lumino/widgets'; +import { CommandRegistry } from '@lumino/commands'; + +export const disconnectDevice = 'bluetooth-manager:disconnect-device'; export class BluetoothDeviceRunningItem - implements IRunningSessions.IRunningItem -{ - constructor(device: BluetoothManager.Device, manager: BluetoothManager) { + implements IRunningSessions.IRunningItem { + constructor(device: BluetoothManager.Device, bluetoothManager: BluetoothManager, commands: CommandRegistry) { this._device = device; - this.manager = manager; - + this.bluetoothManager = bluetoothManager; if (this._device.native.name) { const deviceName = this._device.native.name; this.className = 'jp-bluetooth-' + deviceName.replace(/\s+/g, '-'); } + this.commands = commands; } className?: string | undefined; + open() { + const commands = this.commands; + const deviceID = this._device.native.id; + const menu = new Menu({ commands: commands }) + this._device.contextCommands.map((command: string) => { + menu.addItem({ command: command, args: {deviceID}}) + }) + menu.addClass('jp-bluetooth-device-running-item-menu') + const deviceElement = document.querySelector(`.${this.className}`); + if (deviceElement) { + const rect = deviceElement.getBoundingClientRect(); + const x = rect.left; + const y = rect.bottom; + menu.open(x, y); + } + } + icon() { return BluetoothConnectIcon; } @@ -33,9 +53,10 @@ export class BluetoothDeviceRunningItem } shutdown() { - this.manager.disconnectDevice(this._device); + this.bluetoothManager.disconnectDevice(this._device); } private _device: BluetoothManager.Device; - public manager: BluetoothManager; + public bluetoothManager: BluetoothManager; + public commands: CommandRegistry; } diff --git a/src/bluetooth/BluetoothManager.ts b/src/bluetooth/BluetoothManager.ts index 0b286d4..a977f5a 100644 --- a/src/bluetooth/BluetoothManager.ts +++ b/src/bluetooth/BluetoothManager.ts @@ -147,6 +147,7 @@ export namespace BluetoothManager { public connected: Signal; public disconnected: Signal; public isDisposed: boolean; + public contextCommands:Array; constructor(native: BluetoothDevice) { this.connected = new Signal(this); @@ -154,6 +155,7 @@ export namespace BluetoothManager { this.isConnected = false; this.isDisposed = false; this.native = native; + this.contextCommands = ['bluetooth-manager:disconnect-device', 'bluetooth-manager:add-lego-movehub-control-panel'] } async connectAndGetAllServices(): Promise< diff --git a/src/movehub-extension/components/ConnectionStatus.tsx b/src/movehub-extension/components/ConnectionStatus.tsx index e479779..b9ebad2 100644 --- a/src/movehub-extension/components/ConnectionStatus.tsx +++ b/src/movehub-extension/components/ConnectionStatus.tsx @@ -10,7 +10,7 @@ import { CommandRegistry } from '@lumino/commands'; import { BluetoothManager } from '../../bluetooth/BluetoothManager'; export const connectMoveHub = 'bluetooth-manager:connect-movehub'; export const disconnectMoveHub = 'bluetooth-manager:disconnect-movehub'; -import { movehubRegistryItem } from '..'; + export default function ConnectionStatus({ device }: IMoveHubPanelProps) { const [deviceState, setDeviceState] = useState(defaultDeviceInfo); @@ -46,54 +46,25 @@ export default function ConnectionStatus({ device }: IMoveHubPanelProps) { export class ConnectionStatusWidget extends ReactWidget { public device: MoveHub; public menu: Menu; - public commands: CommandRegistry; - constructor(device: MoveHub, bluetoothManager: BluetoothManager) { + constructor(device: MoveHub, bluetoothManager: BluetoothManager, commands: CommandRegistry) { super(); this.device = device; - this.commands = new CommandRegistry(); - this.commands.addCommand(disconnectMoveHub, { - execute: args => { - bluetoothManager.disconnectDevice(device); - return device; - }, - caption: 'Disconnect MoveHub', - label: 'Disconnect MoveHub', - isEnabled: () => { - if (device.deviceInfo.connected) { - return true; - } else { - return false; - } - } - }); - this.commands.addCommand(connectMoveHub, { - execute: args => { - const newDevice = bluetoothManager.connectDevice(movehubRegistryItem); - return newDevice; - }, - caption: 'Connect MoveHub', - label: 'Connect MoveHub', - isEnabled: () => { - if (device.deviceInfo.connected) { - return false; - } else { - return true; - } - } - }); - this.menu = new Menu({ commands: this.commands }); + const deviceID = this.device.native.id; + + this.menu = new Menu({ commands: commands }); this.menu.addItem({ - command: disconnectMoveHub + command: disconnectMoveHub, + args: {deviceID} }); this.menu.addItem({ - command: connectMoveHub + command: connectMoveHub, + args: {deviceID} }); this.menu.addClass('jp-connection-status-menu'); } openMenu(event: React.MouseEvent) { - console.log('You have clicked'); if (this.menu && typeof this.menu.isVisible !== 'undefined') { this.menu.open(event.clientX, event.clientY); } else { diff --git a/src/movehub-extension/index.ts b/src/movehub-extension/index.ts index 5eecae4..5c2e925 100644 --- a/src/movehub-extension/index.ts +++ b/src/movehub-extension/index.ts @@ -22,6 +22,8 @@ export * from './version'; export * from './widget'; export const addLEGOMoveHubControlPanel = 'bluetooth-manager:add-lego-movehub-control-panel'; +export const connectMoveHub = 'bluetooth-manager:connect-movehub'; +export const disconnectMoveHub = 'bluetooth-manager:disconnect-movehub'; export const moveHubServiceUUID = '00001623-1212-efde-1623-785feabcd123'; export const moveHubCharacteristicUUID = '00001624-1212-efde-1623-785feabcd123'; export const movehubRegistryItem: IDeviceRegistryItem = { @@ -81,39 +83,32 @@ const LEGOMoveHubControlPanelPlugin: JupyterFrontEndPlugin = { const device = result[ result.length - 1 ] as MoveHub; /* the last added MoveHub device*/ - /*let isThemeLight: boolean = themeManager.theme; - themeManager.themeChanged.connect((sender: any, args: IChangedArgs) => { - const theme = args.newValue; - console.log("Is the theme light?:", themeManager.isLight(theme)); - isThemeLight = themeManager.isLight(theme); - console.log('Theme is:', theme); - });*/ - const content = new MoveHubPanelWidget(device, themeManager); - content.addClass('jp-movehub-panel-content'); - const toolbar = new Toolbar(); - toolbar.addClass('jp-movehub-panel-toolbar'); - const main = new MainAreaWidget({ content, toolbar }); - main.addClass('jp-movehub-panel-main'); - main.toolbar.addItem( - 'connection-status', - new ConnectionStatusWidget(device, bluetoothManager) - ); - main.toolbar.addItem( - 'select-lego-model', - new LegoBuildSelectorWidget(device) - ); - toolbar.addItem('spacer', Toolbar.createSpacerItem()); - main.toolbar.addItem('battery-gauge', new BatteryWidget(device, themeManager)); - main.toolbar.addItem( - 'device-identifier', - new DeviceIdentifierWidget(device) - ); - main.id = 'lego-movehub-control-panel'; - main.title.label = 'LEGO® Move Hub'; - main.title.closable = true; - main.title.icon = LegoBrickIcon; - app.shell.add(main, 'main'); - + const content = new MoveHubPanelWidget(device, themeManager); + content.addClass('jp-movehub-panel-content'); + const toolbar = new Toolbar(); + toolbar.addClass('jp-movehub-panel-toolbar'); + const main = new MainAreaWidget({ content, toolbar }); + main.addClass('jp-movehub-panel-main'); + main.toolbar.addItem( + 'connection-status', + new ConnectionStatusWidget(device, bluetoothManager, app.commands) + ); + main.toolbar.addItem( + 'select-lego-model', + new LegoBuildSelectorWidget(device) + ); + toolbar.addItem('spacer', Toolbar.createSpacerItem()); + main.toolbar.addItem('battery-gauge', new BatteryWidget(device, themeManager)); + main.toolbar.addItem( + 'device-identifier', + new DeviceIdentifierWidget(device) + ); + main.id = 'lego-movehub-control-panel'; + main.title.label = 'LEGO® Move Hub'; + main.title.closable = true; + main.title.icon = LegoBrickIcon; + app.shell.add(main, 'main'); + } else { throw new Error('The device is not a Move Hub.'); @@ -123,17 +118,43 @@ const LEGOMoveHubControlPanelPlugin: JupyterFrontEndPlugin = { label: trans.__('Open a LEGO® Move Hub Control Panel') }); - app.contextMenu.addItem({ - command: addLEGOMoveHubControlPanel, - selector: - 'jp-tree-item.jp-RunningSessions-item.jp-bluetooth-LEGO-Move-Hub', - rank: 1 + app.commands.addCommand(disconnectMoveHub, { + execute: args => { + const selectedDevice = bluetoothManager.deviceList.find((device) => device.native.id === args.deviceID as string); + if (selectedDevice && selectedDevice instanceof MoveHub) { + bluetoothManager.disconnectDevice(selectedDevice); + return selectedDevice; + } else { + throw new Error('No device provided or device is invalid'); + } + }, + caption: 'Disconnect MoveHub', + label: 'Disconnect MoveHub', + isEnabled: (args) => { + const selectedDevice = bluetoothManager.deviceList.find((device) => device.native.id === args.deviceID as string); + if (selectedDevice && selectedDevice instanceof MoveHub && selectedDevice.deviceInfo.connected) { + return true; + } else { + return false; + } + } }); - - app.contextMenu.addItem({ - command: addLEGOMoveHubControlPanel, - selector: 'jp-tree-item.jp-RunningSessions-item.jp-bluetooth-Move-Hub', - rank: 1 + + app.commands.addCommand(connectMoveHub, { + execute: args => { + const newDevice = bluetoothManager.connectDevice(movehubRegistryItem); + return newDevice; + }, + caption: 'Connect MoveHub', + label: 'Connect MoveHub', + isEnabled: (args) => { + const selectedDevice = bluetoothManager.deviceList.find((device) => device.native.id === args.deviceID as string); + if (selectedDevice && selectedDevice instanceof MoveHub && selectedDevice.deviceInfo.connected) { + return false; + } else { + return true; + } + } }); } }; diff --git a/src/movehub-extension/moveHub.ts b/src/movehub-extension/moveHub.ts index 855df23..0afbc92 100644 --- a/src/movehub-extension/moveHub.ts +++ b/src/movehub-extension/moveHub.ts @@ -79,6 +79,7 @@ export class MoveHub extends BluetoothManager.Device { driveSpeed: DEFAULT_CONFIG.DRIVE_SPEED, turnSpeed: DEFAULT_CONFIG.TURN_SPEED }; + this.contextCommands = ['bluetooth-manager:disconnect-device','bluetooth-manager:add-lego-movehub-control-panel'] } logDebug(message?: any, ...optionalParams: any[]): void { if (message) {