mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-10-26 11:38:32 +00:00
- update Theia to `1.39.0`, - remove the application packager and fix the security vulnerabilities, - bundle the backed application with `webpack`, and - enhance the developer docs. Co-authored-by: Akos Kitta <a.kitta@arduino.cc> Co-authored-by: per1234 <accounts@perglass.com> Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
202 lines
6.0 KiB
TypeScript
202 lines
6.0 KiB
TypeScript
import React from '@theia/core/shared/react';
|
|
import {
|
|
injectable,
|
|
postConstruct,
|
|
inject,
|
|
} from '@theia/core/shared/inversify';
|
|
import { Widget } from '@theia/core/shared/@phosphor/widgets';
|
|
import { Message } from '@theia/core/shared/@phosphor/messaging';
|
|
import { Emitter } from '@theia/core/lib/common/event';
|
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
|
import { CommandService } from '@theia/core/lib/common/command';
|
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
|
import {
|
|
Installable,
|
|
Searchable,
|
|
ArduinoComponent,
|
|
ResponseServiceClient,
|
|
} from '../../../common/protocol';
|
|
import { FilterableListContainer } from './filterable-list-container';
|
|
import { ListItemRenderer } from './list-item-renderer';
|
|
import { NotificationCenter } from '../../notification-center';
|
|
import { FilterRenderer } from './filter-renderer';
|
|
|
|
@injectable()
|
|
export abstract class ListWidget<
|
|
T extends ArduinoComponent,
|
|
S extends Searchable.Options
|
|
> extends ReactWidget {
|
|
@inject(MessageService)
|
|
protected readonly messageService: MessageService;
|
|
@inject(NotificationCenter)
|
|
protected readonly notificationCenter: NotificationCenter;
|
|
@inject(CommandService)
|
|
private readonly commandService: CommandService;
|
|
@inject(ResponseServiceClient)
|
|
private readonly responseService: ResponseServiceClient;
|
|
|
|
/**
|
|
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
|
*/
|
|
private focusNode: HTMLElement | undefined;
|
|
private readonly didReceiveFirstFocus = new Deferred();
|
|
private readonly searchOptionsChangeEmitter = new Emitter<
|
|
Partial<S> | undefined
|
|
>();
|
|
private readonly onDidShowEmitter = new Emitter<void>();
|
|
/**
|
|
* Instead of running an `update` from the `postConstruct` `init` method,
|
|
* we use this variable to track first activate, then run.
|
|
*/
|
|
private firstUpdate = true;
|
|
|
|
constructor(protected options: ListWidget.Options<T, S>) {
|
|
super();
|
|
const { id, label, iconClass } = options;
|
|
this.id = id;
|
|
this.title.label = label;
|
|
this.title.caption = label;
|
|
this.title.iconClass = iconClass;
|
|
this.title.closable = true;
|
|
this.addClass('arduino-list-widget');
|
|
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
|
this.scrollOptions = undefined;
|
|
this.toDispose.pushAll([
|
|
this.searchOptionsChangeEmitter,
|
|
this.onDidShowEmitter,
|
|
]);
|
|
}
|
|
|
|
@postConstruct()
|
|
protected init(): void {
|
|
this.toDispose.pushAll([
|
|
this.notificationCenter.onIndexUpdateDidComplete(() =>
|
|
this.refresh(undefined)
|
|
),
|
|
this.notificationCenter.onDaemonDidStart(() => this.refresh(undefined)),
|
|
this.notificationCenter.onDaemonDidStop(() => this.refresh(undefined)),
|
|
]);
|
|
}
|
|
|
|
protected override onAfterShow(message: Message): void {
|
|
this.maybeUpdateOnFirstRender();
|
|
super.onAfterShow(message);
|
|
this.onDidShowEmitter.fire();
|
|
}
|
|
|
|
private maybeUpdateOnFirstRender() {
|
|
if (this.firstUpdate) {
|
|
this.firstUpdate = false;
|
|
this.update();
|
|
this.didReceiveFirstFocus.promise.then(() => this.focusNode?.focus());
|
|
}
|
|
}
|
|
|
|
protected override onActivateRequest(message: Message): void {
|
|
this.maybeUpdateOnFirstRender();
|
|
super.onActivateRequest(message);
|
|
(this.focusNode || this.node).focus();
|
|
}
|
|
|
|
protected override onUpdateRequest(message: Message): void {
|
|
super.onUpdateRequest(message);
|
|
this.render();
|
|
}
|
|
|
|
protected override onResize(message: Widget.ResizeMessage): void {
|
|
super.onResize(message);
|
|
this.updateScrollBar();
|
|
}
|
|
|
|
private readonly onFocusResolved = (
|
|
element: HTMLElement | undefined
|
|
): void => {
|
|
this.focusNode = element;
|
|
this.didReceiveFirstFocus.resolve();
|
|
};
|
|
|
|
protected async install({
|
|
item,
|
|
progressId,
|
|
version,
|
|
}: {
|
|
item: T;
|
|
progressId: string;
|
|
version: Installable.Version;
|
|
}): Promise<void> {
|
|
return this.options.installable.install({ item, progressId, version });
|
|
}
|
|
|
|
protected async uninstall({
|
|
item,
|
|
progressId,
|
|
}: {
|
|
item: T;
|
|
progressId: string;
|
|
}): Promise<void> {
|
|
return this.options.installable.uninstall({ item, progressId });
|
|
}
|
|
|
|
override render(): React.ReactNode {
|
|
return (
|
|
<FilterableListContainer<T, S>
|
|
defaultSearchOptions={this.options.defaultSearchOptions}
|
|
container={this}
|
|
resolveFocus={this.onFocusResolved}
|
|
searchable={this.options.searchable}
|
|
install={this.install.bind(this)}
|
|
uninstall={this.uninstall.bind(this)}
|
|
itemLabel={this.options.itemLabel}
|
|
itemRenderer={this.options.itemRenderer}
|
|
filterRenderer={this.options.filterRenderer}
|
|
searchOptionsDidChange={this.searchOptionsChangeEmitter.event}
|
|
messageService={this.messageService}
|
|
commandService={this.commandService}
|
|
responseService={this.responseService}
|
|
onDidShow={this.onDidShowEmitter.event}
|
|
/>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* If `filterText` is defined, sets the filter text to the argument.
|
|
* If it is `undefined`, updates the view state by re-running the search with the current `filterText` term.
|
|
*/
|
|
refresh(searchOptions: Partial<S> | undefined): void {
|
|
this.didReceiveFirstFocus.promise.then(() =>
|
|
this.searchOptionsChangeEmitter.fire(searchOptions)
|
|
);
|
|
}
|
|
|
|
updateScrollBar(): void {
|
|
if (this.scrollBar) {
|
|
this.scrollBar.update();
|
|
}
|
|
}
|
|
}
|
|
|
|
export namespace ListWidget {
|
|
export interface Options<
|
|
T extends ArduinoComponent,
|
|
S extends Searchable.Options
|
|
> {
|
|
readonly id: string;
|
|
readonly label: string;
|
|
readonly iconClass: string;
|
|
readonly installable: Installable<T>;
|
|
readonly searchable: Searchable<T, S>;
|
|
readonly itemLabel: (item: T) => string;
|
|
readonly itemRenderer: ListItemRenderer<T>;
|
|
readonly filterRenderer: FilterRenderer<S>;
|
|
readonly defaultSearchOptions: S;
|
|
}
|
|
}
|
|
|
|
export class UserAbortError extends Error {
|
|
constructor(message = 'User abort') {
|
|
super(message);
|
|
Object.setPrototypeOf(this, UserAbortError.prototype);
|
|
}
|
|
}
|