import { injectable, postConstruct, inject, } from '@theia/core/shared/inversify'; import { Message } from '@theia/core/shared/@phosphor/messaging'; import { addEventListener } from '@theia/core/lib/browser/widgets/widget'; import { DialogProps } from '@theia/core/lib/browser/dialogs'; import { AbstractDialog } from '../theia/dialogs/dialogs'; import { LibraryPackage, LibrarySearch, LibraryService, } from '../../common/protocol/library-service'; import { ListWidget } from '../widgets/component-list/list-widget'; import { Installable } from '../../common/protocol'; import { ListItemRenderer } from '../widgets/component-list/list-item-renderer'; import { nls } from '@theia/core/lib/common'; import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer'; import { findChildTheiaButton, splitByBoldTag } from '../utils/dom'; import { UserAbortError } from '../../common/protocol/progressible'; @injectable() export class LibraryListWidget extends ListWidget< LibraryPackage, LibrarySearch > { static WIDGET_ID = 'library-list-widget'; static WIDGET_LABEL = nls.localize( 'arduino/library/title', 'Library Manager' ); constructor( @inject(LibraryService) private service: LibraryService, @inject(ListItemRenderer) itemRenderer: ListItemRenderer, @inject(LibraryFilterRenderer) filterRenderer: LibraryFilterRenderer ) { super({ id: LibraryListWidget.WIDGET_ID, label: LibraryListWidget.WIDGET_LABEL, iconClass: 'fa fa-arduino-library', searchable: service, installable: service, itemLabel: (item: LibraryPackage) => item.name, itemRenderer, filterRenderer, defaultSearchOptions: { query: '', type: 'All', topic: 'All' }, }); } @postConstruct() protected override init(): void { super.init(); this.toDispose.pushAll([ this.notificationCenter.onLibraryDidInstall(() => this.refresh(undefined) ), this.notificationCenter.onLibraryDidUninstall(() => this.refresh(undefined) ), ]); } protected override async install({ item, progressId, version, }: { item: LibraryPackage; progressId: string; version: Installable.Version; }): Promise { const dependencies = await this.service.listDependencies({ item, version, filterSelf: true, }); let installDependencies: boolean | undefined = undefined; if (dependencies.length) { const message = document.createElement('div'); const textContent = dependencies.length === 1 ? nls.localize( 'arduino/library/needsOneDependency', 'The library {0}:{1} needs another dependency currently not installed:', item.name, version ) : nls.localize( 'arduino/library/needsMultipleDependencies', 'The library {0}:{1} needs some other dependencies currently not installed:', item.name, version ); const segments = splitByBoldTag(textContent); if (!segments) { message.textContent = textContent; } else { segments.map((segment) => { const span = document.createElement('span'); if (typeof segment === 'string') { span.textContent = segment; } else { const bold = document.createElement('b'); bold.textContent = segment.textContent; span.appendChild(bold); } message.appendChild(span); }); } const listContainer = document.createElement('div'); listContainer.style.maxHeight = '300px'; listContainer.style.overflowY = 'auto'; const list = document.createElement('ul'); list.style.listStyleType = 'none'; for (const { name } of dependencies) { const listItem = document.createElement('li'); listItem.textContent = ` - ${name}`; listItem.style.fontWeight = 'bold'; list.appendChild(listItem); } listContainer.appendChild(list); message.appendChild(listContainer); const question = document.createElement('div'); question.textContent = dependencies.length === 1 ? nls.localize( 'arduino/library/installOneMissingDependency', 'Would you like to install the missing dependency?' ) : nls.localize( 'arduino/library/installMissingDependencies', 'Would you like to install all the missing dependencies?' ); message.appendChild(question); const result = await new MessageBoxDialog({ title: nls.localize( 'arduino/library/installLibraryDependencies', 'Install library dependencies' ), message, buttons: [ nls.localize( 'arduino/library/installWithoutDependencies', 'Install without dependencies' ), nls.localize('arduino/library/installAll', 'Install All'), ], maxWidth: 740, // Aligned with `settings-dialog.css`. }).open(); if (result) { const { response } = result; if (response === 0) { // Current only installDependencies = false; } else if (response === 1) { // All installDependencies = true; } } else { throw new UserAbortError(); } } else { // The lib does not have any dependencies. installDependencies = false; } if (typeof installDependencies === 'boolean') { await this.service.install({ item, version, progressId, installDependencies, }); this.messageService.info( nls.localize( 'arduino/library/installedSuccessfully', 'Successfully installed library {0}:{1}', item.name, version ), { timeout: 3000 } ); } } protected override async uninstall({ item, progressId, }: { item: LibraryPackage; progressId: string; }): Promise { await super.uninstall({ item, progressId }); this.messageService.info( nls.localize( 'arduino/library/uninstalledSuccessfully', 'Successfully uninstalled library {0}:{1}', item.name, item.installedVersion! ), { timeout: 3000 } ); } } class MessageBoxDialog extends AbstractDialog { protected response: number; constructor(protected readonly options: MessageBoxDialog.Options) { super(options); this.contentNode.appendChild(this.createMessageNode(this.options.message)); ( options.buttons || [nls.localize('vscode/issueMainService/ok', 'OK')] ).forEach((text, index) => { const button = this.createButton(text); const isPrimaryButton = index === (options.buttons ? options.buttons.length - 1 : 0); button.title = text; button.classList.add( isPrimaryButton ? 'main' : 'secondary', 'message-box-dialog-button' ); this.controlPanel.appendChild(button); this.toDisposeOnDetach.push( addEventListener(button, 'click', () => { this.response = index; this.accept(); }) ); }); } protected override onCloseRequest(message: Message): void { super.onCloseRequest(message); this.accept(); } get value(): MessageBoxDialog.Result { return { response: this.response }; } protected createMessageNode(message: string | HTMLElement): HTMLElement { if (typeof message === 'string') { const messageNode = document.createElement('div'); messageNode.textContent = message; return messageNode; } return message; } protected override handleEnter(event: KeyboardEvent): boolean | void { this.response = 0; super.handleEnter(event); } protected override onAfterAttach(message: Message): void { super.onAfterAttach(message); findChildTheiaButton(this.controlPanel)?.focus(); } } export namespace MessageBoxDialog { export interface Options extends DialogProps { /** * When empty, `['OK']` will be inferred. */ buttons?: string[]; message: string | HTMLElement; } export interface Result { /** * The index of `buttons` that was clicked. */ readonly response: number; } }